hiro99ma blog

Something technical

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:XX OP_CHECKSIGpk_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);

これもエラーにならなかった。
そして末尾に 0xacOP_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日早ければよかったのだが、昨晩バージョンを戻したりして確認したので今朝になってしまったのだ。

issue#486

おわりに

少しは miniscript の使い方が分かったような気がする。
気のせいかもしれない。

おまけ

vscode の “files.associations”

miniscript のコードを vscode で見ていただけなのだが、.vscode/settings.json が作られていた。
そういえば、libwally-core や bitcoind でもそうだった気がする。
ファイルを開いただけで作られるのだ。
中身を見ると "files.associations" に開いたファイルだけでなく #include に入っている標準ライブラリなども登録されている。 C++ だと <iostream> みたいに拡張子が無いのでワイルドカードが使えないためだろう。
.hpp でよいから拡張子を付けてくれると気が楽だったのに。

そこに文句を言っても仕方が無いので、自動でファイルを作るのを何とか止めたい。
開くだけで git status で差分があると心配になるのだ。

これはどうも C/C++ の Extension でのデフォルト値がそうなっているからのようだ。


 < Top page


コメント(Google Formへ飛びます)

GitHub

X/Twitter

Homepage