btc: BIP-341が難しい (3)
2025/01/16
はじめに
P2TR の script path でアドレスを作って送金し、そこから送金するところまでやろうという試み。
BIP-341 を読み解くのが難しいのでがんばっているところ。
BIP-341 再び
script path spend
scriptPubKey には、witness program しかないのでスクリプトの全貌は分からない。
witness version とデータ長さえ合っていれば乱数でも送金先として有効だと思う。
これは P2PKH や P2WSH など他の送金先でもエンコード手順や方法が違うだけで条件は同じだ。
なので作った P2TR アドレスへの送金は失敗しない。
失敗するのはその scriptPubKey に署名して送金するときだ。
“key path”, “script path” と書いているが、データとしてはどちらも混ぜられる。
送金するときにシングル鍵の方を使うなら key path spend、スクリプトの方を使うなら script path spend、というところか。
BIP-341 でも “Spending using the key path” や “Spending using one of the scripts” などという書き方だし。
script path spend だけにしたくても key path のデータは省略できないので、 P
を特定の値(H
)にして H + rG
を internal keyとして使う(internal public key だよな)。
スクリプトの分解
分岐が複数あるスクリプトでの P2TR サンプルが見つからないのだが、1分岐で一通りやったらイメージが沸くかもしれないので先に進むことにした。
気にしているのは、たとえば BIP-112 にあったこういう分岐の中でさらに分岐があった場合だ。
HASH160 DUP <R-HASH> EQUAL
IF
"24h" CHECKSEQUENCEVERIFY
2DROP
<Alice's pubkey>
ELSE
<Commit-Revocation-Hash> EQUAL
NOTIF
"2015/10/20 10:33" CHECKLOCKTIMEVERIFY DROP
ENDIF
<Bob's pubkey>
ENDIF
CHECKSIG
ルートはこの 3つになる。
- IF ルート
- ELSE - NOTIFを通るルート
- ELSE - NOTIFを通らないルート
分岐が深くなるほどルートは増えるのだが、これを全部単独のスクリプトとして書き出さないといけないのだろうか。
まあ、実際のスクリプトでそこまで複雑に作ることは少ないだろうし、仕方ないのか。
サンプル
ではサンプルを見てみよう。
ちなみに、昨日書いた「スクリプトのデバッグをしたい」はこの btcdeb のことである。
OP_IF
からいきなり始まっているが、それ以外はほぼ同じ構成だ。
ということは、送金するときは OP_FALSE(0x00)
かそれ以外をスタックに載せれば良いのかな?
OP_IF
144
OP_CHECKSEQUENCEVERIFY
OP_DROP
<pubkey_alice>
OP_ELSE
OP_SHA256
preimage_hash
OP_EQUALVERIFY
<pubkey_bob>
OP_ENDIF
OP_CHECKSIG
これを 2つに分ける。
BIP-342 の方に書かれている。
Script execution には OP_CSV
や OP_CLTV
を含むと書いているので、そのまま使ってよいだろう。
OP_CSV
や OP_CLTV
のようにチェックして NG だったら終了するような命令は分岐と考えなくてよさそうだ。
OP_CHECKSIG
や OP_CHECKSIGVERIFY
はシュノア署名での検証になったりするが、命令の書き換えなどは不要。
そうするとこうなる。
IF文側:
144 OP_CHECKSEQUENCEVERIFY OP_DROP <pubkey_alice> OP_CHECKSIG
ELSE文側
OP_SHA256 preimage_hash OP_EQUALVERIFY <pubkey_bob> OP_CHECKSIG
これはそのままでよかったが、命令を実行した結果によって OP_IF
などで分岐をする場合、OP_IF
をなくしても問題ないようなスクリプトに書き換えることもある。
OP_EQUAL OP_IF
だったら OP_EQUALVERIFY
に置き換えるなどだ。
BIP-341 に書いてあるのは general guideline なので明確にこうしろというのはないのだと思う。
ただせっかく merkle tree にして全部のスクリプトを見せなくてもよいようにできるのだからそうしましょうよ、ということだと思う。
元のスクリプトがあるから「分割」だけど、スクリプト全体を載せることがないので全然関係ないスクリプトたちでもよいはずだ。
全部のスクリプトを載せないといけなかったら、それらがうまく動くようにいろいろ複雑に考えないといけないけど script path なら気にしなくてよいはずだ。
merkle tree
分割したスクリプトが merkle tree の leaf になる。
BIP-341 の def taproot_output_script(internal_pubkey, script_tree)
で witness program を出力するようなのだが、再帰呼び出しなどしていて分かりづらい。
internal_pubkey
: internal private key の公開鍵 X座標script_tree
leaf_version
とscript
の組script_tree
と同じ構成を 2つ持つリスト- 無し
script_tree
が無しの場合は key path と同じ。
2 番目の「script_tree
と同じ構成」というのは 1 番目の tuple のことを言ってるのだろう。
tuple だけだった場合、最初が leaf version、次が script になっている。
leaf version は 0xc0
を想定している。
internal public key を tweak public key にしたように、ここでは “TapLeaf” を使って merkle tree の leaf になる値の計算をする。
TapLeaf の計算は “leaf_version” + “script” を使うのだが、ここでは Compact Size型のスクリプト長を使うとのこと。
なぜここになって Compact Size型にしたのだろうか?
ともかく btcdebのサンプル では prefix_compact_size()
という関数になっていて先頭のデータは命令でのデータ長にはなっていないのでスクリプトの本体だけでよさそうだ。
144
から始まるので 029000
(0190
だとマイナス値扱いだったか)になっている。
script_tree
がリストだった場合、[0]
が左要素、[1]
が右要素として TapLeaf の計算をしている。
ここがゴチャゴチャしてよくわからない。
とにかくここは左と右の TapLeaf を連結した値を TapTweak でも TapLeaf でもなく TapBranch で計算する。
連結する左と右も単純に連結するのではなく、左が小さい値になるようにして連結する。
こうやってできた TapBranch の merkle root を internal public key と連結して TapTweak 計算やらあれこれすると tweak public key になる。
これを key path と同じく bech32m でエンコードすると P2TR アドレスのできあがりだ。
この辺でもう実装しようという気力がなくなってきた。。。まだ署名に行き着いていないのに。
署名と書いたが、スクリプトを解くことができるなら署名である必要はない。
検証など
こうやって作った merkle tree を検証する。
するのだが、BIP-341 の図で leaf の A, B, C, D, E が同じ高さにいないのはなんでだろう?
2. Organization によると leaf が 3以上だとツリーの作り方に自由度があるそうだ。
実行される可能性が高いスクリプトを階層の浅いところに置くことで演算量を減らすことができるそうだ。
といっても、ツリーの構造全体はスクリプトに載らないのでお気持ち程度でよかろう。
witness stack が 2つ以上だと script path で、stack の一番最後が control block、その1つ前が使用したスクリプトになる。
control block のところに merkle tree 関係の値が載っている。
BIP-341 では c
となっている。
c[0]
:0xc0
+ parity(1bit)c[1:33]
: internal public keyc[34+32m]
: TapBranch する反対側の値
昔、とりあえずやってみよう、ということでこちらのサイトを見て bitcoin-js でやってみたのがこちらである。
たぶん自分でやったんじゃないかと思うが記憶にない。。。
- A Guide to creating TapRoot Scripts with bitcoinjs-lib - DEV Community
- トランザクション:66a187d52ecc038290fc009b1eb4ee55aef7399235f490f35661b50d9d7e981d - mempool - Bitcoin Testnet3
おわりに
書いていてだんだんと訳がわからなくなってきた。
実装する気力も失せてきているが、どうしたものか。
なんとなくイメージは分かったので次に進んでしまえとも思うし、 ここまでやったんだから簡易的にでも実装しておいた方が心残りが無いのではとも思う。
明日考えよう。