P2TR および BIP-341周辺
最終更新日:2025/03/06
P2TR に関するトランザクションを作るための説明である。
説明量が多かったのが BIP-341 だったのでタイトルにしたが関連する BIP は複数ある。
主なものを以下に列挙する。
P2TR とは
Pay to Taproot。
Bitcoin で新しく追加された方式(2025年03月現在)。
- トランザクションの全体の構造としては segwit と同じ。
- これまでは楕円曲線secp256k1での署名(ECDSA)だったがシュノア署名に変更された。
- アドレスとしてシングル鍵とスクリプトの違いがなくなった。
- スクリプトから支払う際、これまではスクリプト全体を載せていたが、P2TRスクリプトの場合は条件を満たすスクリプトだけで済む。
- witness version 1
シュノア署名とスクリプト
シュノアは “Shnorr” で人名から来ている。
楕円曲線の署名は ECDSA だがシュノア署名は何DSAなのかわからなかった。
いろいろシュノア署名について書いてあるサイトがあるが、 トランザクションを作る実装者目線としては MuSig というマルチシグのような動作がシングル鍵と同じトランザクションでできるのが大きいかなと思った。 シュノア署名は複数の署名を集約することができるからである。
それ以外はちょっとしたことだが、署名のサイズが固定になったというところか。
ECDSA の場合は ASN.1 というフォーマットだったので、署名データの先頭に最上位ビットが立っているとマイナス値と見なされないようにするため 0x00
を付けていた。
それが R
と S
のそれぞれにあったので署名サイズがトランザクションによって微妙に違う(0~+2)。
シュノア署名になってそれを断ち切ることができたのか、きっちり 64バイトで済むようになった。
署名型も “SIGHASH_DEFAULT
” ならトランザクションから省略できるようになっている。
シュノア署名の影響か taproot の影響かわからないが、Bitcoin スクリプトの命令に変化が生じている。
まず、署名を検証する命令の OP_CHECKSIG
や OP_CHECKSIGVERIFY
が tapscript で使われる場合はシュノア署名用になっている。
またマルチシグ用の OP_CHECKMULTISIG
や OP_CHECKMULTISIGVERIFY
が tapscript では無効になった。OP_RETURN
と同じ扱いになるそうだ。
その代わりに OP_CHECKSIGADD
が使えるようになり、マルチシグ命令ではなく署名の検証に成功した数を加算して M-of-N のマルチシグを自分でスクリプトを作って実現する方式になっている。
この辺りは BIP-342 を参考にするのが良い。
シングル鍵
P2TR のシングル鍵での支払いは Key Path Spend などと呼ばれる。
(スクリプトの方は Script Path。)
今までは、秘密鍵から公開鍵を作り、公開鍵から送金先を指す scriptPubKey
向けのデータを作っていた。
P2TR では今までの秘密鍵を “Internal Private Key”、そこから作った公開鍵を “Internal Public Key” と呼ぶようになった。
Internal Public Key から P2TR 向けの加工をした Tweaked Public Key を作り、この Tweaked Public Key が表に出てくる公開鍵になる。
データ長は圧縮公開鍵のように 0x02
や 0x03
がない X座標だけになった 32バイトである。
Tweaked Public Key を作る過程で出てきた値を使って Internal Private Key を加工して Tweaked Private Key になり、シュノア署名では Tweaked Private Key を使うことになる。
scriptPubKey
は Witness Program のルールが使われる。
Witness Version が 1 なので OP_1
= 0x51
、
続けてデータ長 は 32 バイト = 0x20
、
そして Tweaked Public Key がそのまま 32バイト続く。
そのおかげで署名の検証ではスクリプトに Public Key を載せずに済み、トランザクションサイズが少し小さくなっている。
以前は scriptPubKey
には公開鍵を HASH160
した値が載っていたので、
スクリプトには公開鍵が必要だったし、検証するときに公開鍵から scriptPubKey
になることの確認が必要だったので、
サイズだけでなく検証の負荷もちょっと下がったのだと思う。
スクリプト
P2TR のスクリプトでの支払いは Script Path Spend などと呼ばれる。
実は P2TR ではシングル鍵、スクリプトだけでなく、そのどちらかで支払うことができるというトランザクションも作ることができる。
シングル鍵で説明した Tweaked Public Key などの作り方だが、全体としてはスクリプトがある場合の鍵の作り方があり、
上に載せたのはスクリプトが無い場合の方法になる。
スクリプトの場合は何が加わるかというと、スクリプトをマークルツリーにして得られたマークルルートの値である。
スクリプトをどうやってマークルツリーにするかというと、けっこう力業である。
例えば OP_IF
のルートを通ると pubkey-A、OP_ELSE
のルートを通ると pubkey-B を使うというスクリプトだった場合、いつもはこういうスクリプトだ。
OP_IF
<pubkey-A>
OP_ELSE
<pubkey-B>
OP_ENDIF
OP_CHECKSIG
witness のスタックに <signature> OP_1
を積めば OP_IF
のルートを、<signature> <空>
を積めば OP_ELSE
のルートを通る。
これを、
<pubkey-A>
OP_CHECKSIG
と
<pubkey-B>
OP_CHECKSIG
に分け、それぞれをツリーのリーフにします(ハッシュ計算などはあるが)。
スクリプトから支払う場合、witness のスタックに積むデータはこの場合 <signature>
だけになる。
もし他のデータが必要であれば積めば良いのは P2WSH と同じである。
P2TR の場合、witness スタックにスクリプトを解くためのデータを積んだ後、その次にスクリプト本体を積む。
今回は <pubkey-A> OP_CHECKSIG
か <pubkey-B> OP_CHECKSIG
のどちらかだ。
そして最後に control block というデータを積む。
これには Internal Public Key や今回使ったスクリプトからマークルルートを求めるのに必要なデータである。
マークルツリーを作るとき、スクリプトが 1つや 2つの場合は平たく並べるが、
3つ以上ある場合はどのスクリプトが使われる可能性が高いかを考慮するのが良い。
図の一番右のツリーでは、A
のルートを使う場合はマークルルートを求めるのに BC
のデータがあれば良いが、
B
のルートを使う場合は C
と A
のデータが必要になってトランザクションが大きくなるからだ。
ブロックデータのマークルツリーは平たくしないと他の人が検証できないが、 P2TR のスクリプトはマークルルートが計算できるかどうかしか検証できないので構成は比較的自由である。
マルチシグ
シュノア署名のところにも書いたが、今までのマルチシグ命令が使えなくなっている。
それについてはいくつか提案されている。
大きくは OP_CHECKSIGADD
を使って署名の検証が成功した数をチェックする方式と、もう1つがシュノア署名の集約を使って複数の署名を一度に検証する方式(いわゆる MuSig)だ。
ただし後者の場合、M-of-N で M≠N はできず N-of-N と同数だけしか成功しない。
前者の方式はそこそこ力業のスクリプトになる。
たとえば A, B, C の 2-of-3 マルチシグにしたい場合、
- A と B が署名したスクリプト
- A と C が署名したスクリプト
- B と C が署名したスクリプト
でマークルツリーを作る。
スタックに署名を積んだ順に処理するので、 A と B が B と A だとうまくいかない。
それを避けるには並びを決めておくか、全パターンのスクリプトを作るしかない。
仲間内ではスクリプトを共有するから確認すれば分かるだろう。
MuSig は方式として 3つ あるが、メジャーそうな MuSig2 のシーケンス図を描いた。
署名を集めたりする通信を行い、最終的にはシングル鍵で支払うのと同じ扱いになる。
PSBT のフィールドに追加する提案も出ていて、Bitcoin Core に Pull Request もあったので、MuSig2 が使いやすくなるかもしれない。
まとめ
BIP-341 周辺の P2TR について概要をまとめた。
リンク
- P2TR - Pay To Taproot
- Taproot - Technical Explanation
- デジタル署名~シュノア署名とECDSAの比較~ - Yuya - Spotlight
- 開発日記
- btc: BIP-341が難しい
- btc: BIP-341が難しい (2)
- btc: BIP-341が難しい (3)
- btc: BIP-341が難しい (4)
- btc: BIP-341が難しい (5)
- btc: bitcoinjs-lib を使う (1)
- btc: bitcoinjs-lib を使う (2)
- btc: bitcoinjs-lib を使う (3)
- btc: bitcoinjs-lib を使う (4)
- btc: libwally-core を使う (1)
- btc: libwally-core を使う (2)
- btc: libwally-core を使う (3)
- btc: libwally-core を使う (4)
- btc: MuSig
- btc: MuSigは難しい
- btc: libsecp256k1 は MuSig 2 だった
- btc: libwally-core で script path (1)
- btc: libwally-core で script path (2)