btc: MuSig
2025/01/31
はじめに
P2TR の勉強をしている。
最近は実装寄りのところだけやっていたが、そういえば複数の公開鍵を使ってアドレスを作り、そのうちのいくつかの署名が無いと使うことができない MultiSig というしくみがあったことを忘れていた。
P2TR では MuSig とか MuSig2 とかいう単語が出てきていたが、どうなっていただろうか。
MultiSig
使い道としては「3人のうち 2人から承認を得ないといけない」というようなときだ。
言葉で表すときは “M-of-N”(N人中 M人)のような形で書き、上記であれば 2-of-3 MultiSig というような呼び方をする。
ちなみに Mastering Bitcoin は m とか n ではわからんということで「t-of-k」という書き方もしていた。
k は “keys” で t は “threshold of required signatures to spend the output” のことらしい。
OP_CHECKMULTISIG
という専用の命令もあるのでスクリプトで解く P2SH と同じ扱いでよさそうだが、bitcoind では MULTISIG
と別扱いになっている。
経緯はよく知らないが、Mastering Bitcoin の解説によるとここの MULTISIG
は純粋(bare)な MultiSig 仕様を指しているとのこと。
enum class TxoutType {
NONSTANDARD,
// 'standard' transaction types:
ANCHOR, //!< anyone can spend script
PUBKEY,
PUBKEYHASH,
SCRIPTHASH,
MULTISIG,
NULL_DATA, //!< unspendable OP_RETURN script that carries data
WITNESS_V0_SCRIPTHASH,
WITNESS_V0_KEYHASH,
WITNESS_V1_TAPROOT,
WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
};
もう少し詳細を調べる
関連する仕様は BIP-11 に書かれている。
2011年とかなり早い段階である。
スクリプトはこうだ。
m {pubkey}...{pubkey} n OP_CHECKMULTISIG
これを解くのに m 個の署名を列挙する。
が、その手前に OP_0
という命令を置く必要がある。
OP_0 ...signatures...
この OP_0
はもうおまじない的な扱いで、OP_CHECKMULTISIG
が持つバグを回避するために使われている。
そしてそのまま仕様に反映されたので OP_0
でないとダメになった。
BIP-11 には n が 3以下と書かれているが、この値の経緯がよくわからない。
書かれている内容からするとこういうことだったのだろう。
- 当時は
scriptSig
の上限が 200バイトだった - 3-of-3 で署名を 3つ載せようとしたら DER 形式の署名だと 1つ 73バイト程度なので 200バイトを超す
- 困るので 500バイトまで拡張した
そういった経緯から n も上限は 3ということにしたのだと思う。
が、m が 3以下であればいいんじゃなかろうか。
その辺りは Mastering Bitcoin に書かれていた。
BIP-11 の MultiSig 仕様は 3-of-3 が上限だが、P2SH 構造などのスクリプトであれば 15-of-15 まで許容しているとのこと。
IsStandard()
関数を見よと書いてあるので確認しよう。
Biggest ‘standard’ txin involving only keys is a 15-of-15 P2SH multisig with compressed keys (remember the MAX_SCRIPT_ELEMENT_SIZE byte limit on redeemScript size). That works out to a (15(33+1))+3=513 byte redeemScript, 513+1+15(73+1)+3=1627 bytes of scriptSig, which we round off to 1650(MAX_STANDARD_SCRIPTSIG_SIZE) bytes for some minor future-proofing. That’s also enough to spend a 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not considered standard.
キーのみを含む最大の「標準」トランザクションは、圧縮されたキーを持つ 15-of-15 P2SH マルチシグです (redeemScript のサイズに対する MAX_SCRIPT_ELEMENT_SIZE バイト制限に注意してください)。これは、(15(33+1))+3=513 バイトの redeemScript、513+1+15(73+1)+3=1627 バイトの scriptSig に相当し、将来のために 1650(MAX_STANDARD_SCRIPTSIG_SIZE) バイトに切り上げます。これは、20-of-20 CHECKMULTISIG scriptPubKey を使用するのにも十分ですが、このような scriptPubKey は標準とは見なされません。
コメントを信用するなら圧縮された鍵を使った 15-of-15 P2SH MultiSigが最大だそうだ。
BIP-11 の仕様は公開鍵が圧縮されていない場合ということだろうか?
私が Bitcoin を始めたときは既に 33バイトの公開鍵だったためか、今まで MultiSig が P2SH かどうか気にしたことがなかった。
learn me a bitcoin に例となるトランザクションが載っていた。
なるほど、scriptPubKey に公開鍵などがべたっとならんでいたのか。
P2SH の形にすると scriptPubKey にハッシュ値(HASH160: SHA256 した値を RIPEMD160 する)を載せるようになった。
こうすることで scriptPubKey も小さくなるし、スクリプトも少なくともこの時点では見えなくなる。
トランザクションサイズを小さくする仕様が追加されたとする。
ブロックのサイズは固定なので、マイナーはできるだけトランザクションをたくさん詰められるようにしたい(トランザクションの手数料がマイナーの手に入るため)。
トランザクションサイズは手数料に直結するので、送金する人もその仕様を使うようになっていく。
そうやって、だんだんその仕様が主流になっていくのだ。
「ブロックサイズが固定ならそれを増やせばいいじゃないの」という議論もあるが、面倒なので省略だ。
ともかく、今では純粋な MultiSig のトランザクションはほぼ使われていないだろう。
P2SH 形式もほぼないと思う。
P2WSH か P2TR か。
MuSig
m-of-n MultiSig は m個の署名が必要になる。
今までは複数の署名を scriptSig なり witness なりにそのまま載せていた。
まず、BIP-342 によると OP_CHECKMULTISIG
系の命令は OP_RETURN
相当、つまり失敗する命令になっている。
つまり従来形式の MultiSig は P2TR では使えない。
サポートしてくれていれば取りあえずそれでやっておけばよかったのだが、使えないなら仕方ない。
他の命令と比べると複雑なので止めたかったのかな?
別のやり方は? に 4つ紹介されている。
- Using a single OP_CHECKSIGADD-based script
- 鍵ごとに処理するスクリプトにして、最初は
OP_CHECKSIG
、次からはOP_CHECKSIGADD
で加算していって最後にOP_NUMEQUAL
で M-of-N の M と一致するか確認する OP_CHECKSIGADD
は tapscript 用の命令。<sig> <n> <pub>
をスタックに載せて実行する。
- 鍵ごとに処理するスクリプトにして、最初は
- Using a k-of-k script for every combination
- m-of-n → 複数の m-of-m に分ける
- A, B, C の 2-of-3 ならばこう分けられる
- A, B の 2-of-2
<pub_A> OP_CHECKSIGVERIFY <pub_B> OP_CHECKSIG
- B, C の 2-of-2
<pub_B> OP_CHECKSIGVERIFY <pub_C> OP_CHECKSIG
- A, C の 2-of-2
<pub_A> OP_CHECKSIGVERIFY <pub_C> OP_CHECKSIG
- A, B の 2-of-2
- A, B, C の 2-of-3 ならばこう分けられる
- m-of-n → 複数の m-of-m に分ける
- Using an aggregated public key for every combination
- シュノア署名の public key を集約する機能を使う
- MuSig とか MuSig 1 とか呼ばれるのがこれか
- single signature と書いてあるので key path だろう
- Native Schnorr threshold signatures
- threshold signatures ??
- “indistinguishable from single key payments” と書いてあるので key path だろう
4番目が MuSig2 と呼ばれるものかどうか私には分からなかった。
ちょっと難しい。
MuSig2 は BIP-327 にある。
元の論文とかもあるのだが・・・ちょっと難しい。
script path だったら、その提案は今の bitcoind にされている実装で実現できるか気になるところだが、
key path だったら verify できるかどうかだけの問題だ。
MuSig1 は鍵を集約するから tweak key の作り方が違うだろうし、署名も複数あるから違うはずだ。
よくわからん MuSig2 もそういう感じなんだろう。
実装でそういう情報がたくさんありそうな bitcoinjs-lib を検索した。
- How to make taproot P2TR multiSig wallet · Issue #2111 · bitcoinjs/bitcoinjs-lib
- BIP-342 で紹介していた
OP_CHECKSIGADD
を使う例 - MuSig の API は提供しない
- BIP-342 で紹介していた
- Does bitcoinjs-lib support MuSig2 signature ? · Issue #2049 · bitcoinjs/bitcoinjs-lib
- 2024年2月の段階では MuSig2 をサポートする予定はないとのこと
libsecp256k1 に MuSig2 の API がマージされている。
Blockstream の libsecp256k1-zkp から持ってきたそうだ。
libwally-core に MUSIG OFF という行があったので、MuSig に関する機能は使わないようにしているのかもしれない。
リポジトリを検索しても issue にほぼ出てこないし。
そう考えると、MuSig は実装はできるものの使うというか運用が難しいということなんだろうか?
いや、そもそも複数の人から署名を集めるという行為自体が難しいな。
PSBT というフォーマットがあるから、まだやりやすいのかもしれないが、それにしても大変だろう。
libsecp256k1 のサンプル
サンプルコードを少しだけ見てみよう。
ドキュメントはこちら。
- 3-of-3 MuSigサンプル
- misuse resistance(誤用耐性)に焦点を当てているが、単独で署名するのに比べると障害点は多いのは仕方が無い
- ものすごく注意してね!
- やること
- 鍵集約
- 公開鍵の集約:
secp256k1_musig_pubkey_agg()
- tweak の追加:
secp256k1_ec_pubkey_tweak_add()
(secp256k1_xonly_pubkey_tweak_add()
と似てる)
- 公開鍵の集約:
- 署名
- public nonce の作成:
secp256k1_musig_nonce_gen()
- 作ったら他の signer に配る
- public nonce の集約:
secp256k1_musig_nonce_agg()
,secp256k1_musig_nonce_process()
- 部分署名:
secp256k1_musig_partial_sign()
- 部分署名の検証:
secp256k1_musig_partial_sig_verify()
- 署名の集約:
secp256k1_musig_partial_sig_agg()
- 集約後の検証:
secp256k1_schnorrsig_verify()
- public nonce の作成:
- 鍵集約
おわりに
MuSig の API があってちょっと試してみようか、くらいの気持ちだったのだが壁は高かった。