btc: miniscript (2)
はじめに
昨日の続き。
出力をいじる
ブラウザページの方は出力がわかりやすかったがコマンドラインでは何が出力されているかわかりにくかったので リポジトリを fork して出力を変更した。
%
で桁数を指定していたので、何か別のツールに合わせてあったのかもしれない。
policy?
miniscript compiler は、miniscript を Bitcoinスクリプトにするものと思っていたが、 そう単純な話でもなさそうだ。
「Policy to Miniscript compiler」というタイトルの章が最初に出てくるが、 文字通り policy というものを miniscript に変換するコンパイラだ。
例えば and(pk(A),or(pk(B),or(9@pk(C),older(1000))))
が policy で、
その出力の and_v(or_c(pk(B),or_c(pk(C),v:older(1000))),pk(A))
が miniscript ということになる。
まだピンとこないので、BIP の方を見てみる。
(全然関係ないけど、このページだけ拡張子が .md
だ)
“policy” で引っかかるのは “spending policy” だけだ。
たぶん、policy は miniscript と同じものではない。
BIP には policy の記述については書いていない。
sipa氏のページでも “supported policies” は別欄に書いてあったし。
とはいっても rust-miniscript にもあるし、
いきなり miniscript を書くのは大変だったり、
likely で確率を書くことでスクリプトを調整してくれそうだったりとメリットがある。
policy を miniscript にするところは実装依存になりそうだから BIP に入れていないとかだろうか。
まあ、あまり推測は好きではないので止めておこう。
fragment
最初の表では列見出しが Semantics
, Miniscript Fragment
, Bitcoin Script
となっている。
この意味は miniscript での表記のこれとBitcoin スクリプトでの表記のこれが対応します、という表だ。
いや、単純に “fragment” という呼び方をするんだなあ、と思っただけだ。
演算子のような、関数のような、なんと呼ぶんだろうかと気になったのだ。
“A single key” の復習
昨日試した “A single key” のサンプルを復習しよう。
policy は pk(key_1)
。
コンパイルされた miniscript は pk(key_1)
。
Translation table では pk(key) = c:pk_k(key)
が該当するか。
これは表の下にある c:X
があるので、c:pk_k(key)
という書き方もできるけど簡易表現(syntactic sugar)として pk(key)
でもよいよ、ということなのだろう。
c:X
が X OP_CHECKSIG
、 pk_k(key>
が <key>
なので、 pk(key_1)
は Bitcoinスクリプトだと <key_1> OP_CHECKSIG
になる、ということである。
miniscript の使い道
さて、Bitcoinスクリプトにできることは分かったが、どう使ったものか。
rust-miniscriptサンプル では pubkey のデータをそのまま使えるような感じがするのだけれども
C++ の方はエラーになった。
自分で置換するタイプ?
でもバイナリの Bitcoin スクリプトを直に吐き出してほしいのだがなあ。
libwally-core の descriptor API は miniscript もいけるのだ。
rust-miniscript はともかく sipa/miniscript の方は BIP のために作られているのだと思う。
だとすると使うだけの私としてはライブラリになっている方を調べた方がよいのだろう。 miniscript に対応していても policy からのコンパイルはやらないかもしれないので、 そのときに使うとよいか。
試しに動かしてみよう。
wally_descriptor_parse(
"pk(020202020202020202020202020202020202020202020202020202020202020202)",
NULL,
WALLY_NETWORK_NONE,
WALLY_MINISCRIPT_ONLY,
&ms
);
wally_descriptor_canonicalize(ms, 0, &script);
printf("script=%s\n", script);
エラーは起きずに何か作られた。
うーん??
script=pk(020202020202020202020202020202020202020202020202020202020202020202)#ldku6hfz
API が違うな。 スクリプトにしたいのだった。
wally_descriptor_to_script(
ms,
0, // depth
0, // index
0, // variant
0, // multi_index
0, // child_num
0, // flags
script, sizeof(script), &sz);
printf("script=");
dump(script, sz);
これもエラーにならなかった。
そして末尾に 0xac
と OP_CHECKSIG
が付いている。
script=21020202020202020202020202020202020202020202020202020202020202020202ac
鍵が毎回違うとかいう場合もあるだろう。
そういうときは wally_descriptor_parse()
の第 2引数に key-value の wally_map
を与えることができる。
policy から変換することも考えるとそちらの方が扱いやすいか。
wally_map_init_alloc(1, NULL, &vars_in);
wally_map_add(vars_in,
(const uint8_t *)"key_1", 5,
(const uint8_t *)"020202020202020202020202020202020202020202020202020202020202020202", 66);
wally_descriptor_parse(
"pk(key_1)",
vars_in,
WALLY_NETWORK_NONE,
WALLY_MINISCRIPT_ONLY,
&ms
);
サンプルを試しているのだが、鍵が 3つあると wally_descriptor_parse()
が失敗するような気がする。
WALLY_EINVAL
が返ってくる。
いや、鍵 3つじゃないな。
thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3),sln:older(12960))
はエラーになるが
thresh(3,pk(key_1),s:pk(key_2),s:pk(key_3))
は通る。
鍵を減らした thresh(2,pk(key_1),s:pk(key_2),sln:older(12960))
はエラー。
t:or_c(pk(key_1),and_v(v:pk(key_2),or_c(pk(key_3),v:hash160(H))))
も大丈夫だ。
andor(pk(key_remote),or_i(and_v(v:pkh(key_local),hash160(H)),older(1008)),pk(key_revocation))
も OK。
policy で thresh(4)
にして全部必要にすると、それはそれで OK。
鍵 3つだけにすると thresh(3)
でも thresh(2)
でも OK。
libwally-core の この行 で child->builtin == 0
だったので if文の中に入って WALLY_EINVAL
になった、というのはデバッガで確認できた。
が・・・それが何なのかは分からん。
verify_thresh()
なので thresh()
関連なことは分かるが、自分で miniscript を書けないのでなにがどうなのやら。
追記(2025/03/11)
この件は確認すると次に修正してくれると言うことになった。
issue を出すのがもう1日早ければよかったのだが、昨晩バージョンを戻したりして確認したので今朝になってしまったのだ。
おわりに
少しは miniscript の使い方が分かったような気がする。
気のせいかもしれない。
おまけ
vscode の “files.associations”
miniscript のコードを vscode で見ていただけなのだが、.vscode/settings.json
が作られていた。
そういえば、libwally-core や bitcoind でもそうだった気がする。
ファイルを開いただけで作られるのだ。
中身を見ると "files.associations"
に開いたファイルだけでなく #include
に入っている標準ライブラリなども登録されている。
C++ だと <iostream>
みたいに拡張子が無いのでワイルドカードが使えないためだろう。
.hpp
でよいから拡張子を付けてくれると気が楽だったのに。
そこに文句を言っても仕方が無いので、自動でファイルを作るのを何とか止めたい。
開くだけで git status
で差分があると心配になるのだ。
これはどうも C/C++ の Extension でのデフォルト値がそうなっているからのようだ。
"C_Cpp.autoAddFileAssociations": true