lnd v0.17.0のリリースで、実験的にTaproot Channelのサポートが追加される↓
従来のLNでは、チャネルを開く際、チャネル参加者の両名が2-of-2のマルチシグスクリプトに資金をロックするファンディング・トランザクションを作成する。このファンディング・アウトプットは、2-of-2のマルチシグスクリプトを含むP2WSH。
Taproot Channelは、このファンディングアウトプットをTaprootとMuSig2を利用するP2TRアウトプットにするP2TRチャネルを構成するというもの。P2TRチャネルにすると、以下のようなメリットがある。
- 通常の支払いとチャネルの支払いの区別が(チャネルを強制クローズしない限り)つかない。
- チャネルをクローズする際の署名データの数が1/2になるので、手数料を抑えられる
ただ、今回の仕様は二者間のチャネルの形態をTaproot Channelにするだけで、以下のようなことはまだできない。
- 既存のLNのゴシッププロトコルは、Taproot Channelにまだ対応していないので、開設したチャネルを通知/公開することはできない(つまり未公開チャネルとしてしか現状は使えない)
- PTLCの利用。HTLCに代わってPTLCを利用する提案はあるが、これらの仕様はまだ固まっていない。
Simple Taproot Channel
今回LNDが実装しているのは、Simple Taproot Channelと呼んでいるもので、以下のBOLTの提案に基づくもの(まだマージされてはいないので実験的な位置づけで、仕様が変更される可能性もある)。
https://github.com/lightning/bolts/pull/995
この仕組みはTaprootの機能を利用したチャネルになるので、Taprootの機能を理解しておく必要がある(参考:ブログ記事、GBEC動画、そしてMuSig2についても)。
この記事では、チャネル開設までのメッセージの拡張や処理内容のアップデートについて見ていく。
チャネルの開設
チャネルの開設ステップは従来のステップと同じで、アリスをイニシエーターとすると以下のメッセージ交換になる(マスタリング・ライトニングネットワーク7章参照)。
アリス ボブ (1) open_channel ----------------------------------> (2) accept_channel <---------------------------------- (3) funding_created ----------------------------------> (4) funding_signed <---------------------------------- (5) channel_ready ----------------------------------> (6) channel_ready <----------------------------------
Simple Taproot Channelでは、2-of-2のマルチシグスクリプトの代わりにMuSig2を使ってSchnorr署名でマルチシグを実現する。そのため、チャネルに参加する両者(アリスとボブ)は、open_channel
とaccept_channel
で交換する互いのfunding_pubkey
を1つの公開鍵として集約する必要がある。
また、最初のコミットメントトランザクションに対する署名は、従来のチャネルでは両者が自分の秘密鍵を使って単独の署名を作成し、funding_created
とfunding_signed
でそれぞれ交換していたけど、Simple Taproot Channelでは、MuSig2を使ってそれぞれ集約公開鍵に対して有効な部分署名を交換するようになる。
open_channel/accept_channelの拡張
MuSig2を利用した部分署名の作成のためには、事前にpublic nonceにコミットし、交換する必要がある。そのため、両者はそれぞれMuSig2のNonceGenアルゴリズムを使ってpublic nonceを生成し、open_channel
とaccept_channel
メッセージのnext_local_nonce
にそれぞれ生成したpublic nonceをセットする(新しいTLVタイプでメッセージが拡張されている)。これらのpublic nonceは2つの圧縮形式の公開鍵を連結した66バイトのデータになる。
ファンディングトランザクションの作成
open_channel
/accept_channel
を交換した後は、以下の手順で2-of-2のマルチシグに相当する集約公開鍵を生成する。
- 両者の
funding_pubkey
をMuSig2のKeySortアルゴリズムでソート(辞書順にソート。ソートは既存のマルチシグスクリプトの鍵の順序付けでも使用されてる) - ソートした公開鍵をMuSig2のKeyAggアルゴリズムを使って集約(
combined_funding_key
)
集約したcombined_funding_key
を使って、ファンディングアウトプットのP2TRスクリプトを作成する。
Taprootのアンロック方法はキー・パスとスクリプト・パスの2パターンがあるが、ファンディングアウトプットにはマルチシグ以外のアンロック条件は無いので、スクリプト・パスは使用しない。そのためBIP-341の推奨事項に従って、スクリプト・パスによるアンロックができないように、スクリプトツリーのルートハッシュの値を集約公開鍵(combined_funding_key
)に設定したP2TRの公開鍵を作成する↓
funding_key = combined_funding_key + tagged_hash("TapTweak", combined_funding_key)*G
そして、ファンディングアウトプットは、OP_1 <funding_key>
となる。
コミットメントトランザクションの作成
続いて、コミットメントトランザクションの変更について。コミットメントトランザクションは↑のファンディングアウトプットをインプットとして、チャネル内の両者の残高を管理するトランザクションで、自身の残高を保持するto_local
アウトプットと相手の残高を保持するto_remote
アウトプットで構成される。
to_local
BOLT#3で定義されている、現状のto_local
アウトプットのスクリプトは↓
OP_IF <revocationpubkey> # ペナルティ用 OP_ELSE <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP <local_delayedpubkey> OP_ENDIF OP_CHECKSIG
Simple Taproot Channelでは条件分岐はスクリプトツリーによりそれぞれ分解されるので、
ペナルティ用の失効スクリプト(revoke_script
)
<local_delayedpubkey> OP_DROP <revocation_pubkey> OP_CHECKSIG
と、遅延スクリプト(to_delay_script
)
<local_delayedpubkey> OP_CHECKSIG <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
に分解され、これら2つをリーフとしたスクリプトツリーが構成され、そのルートハッシュto_delay_script_root
が計算される。
失効スクリプトの方にもlocal_delayedpubkey
が含まれている点が、既存のスクリプトと異なっている。ただlocal_delayedpubkey
に対して署名検証するのではなく、その後OP_DROP
が続くので、スクリプト内にただのデータとしてlocal_delayedpubkey
を埋め込んでいるだけ。これはサードパーティがアンカー・アウトプットを引き出すために必要な情報をオンチェーンで明らかにするためらしい。
そして、このP2TRアウトプットの公開鍵は、
to_local_output_key = taproot_nums_point + tagged_hash("TapTweak", taproot_nums_point || to_delay_script_root)*G
となり、P2TRアウトプットはOP_1 <to_local_output_key>
となる。
ここで、内部鍵がtaproot_nums_point
になっているのは、このロックスクリプトはスクリプト・パスでしかアンロックしないため、キー・パスでアンロックできないよう誰もその秘密鍵を知らないNUMSポイントが指定されている。
to_remote
BOLT#3で定義されている、現状のto_remote
アウトプットのスクリプトは↓
<remotepubkey> OP_CHECKSIGVERIFY 1 OP_CHECKSEQUENCEVERIFY
こちらは条件分岐がないので、ほぼ似た形態のスクリプト(to_remote_script
)になる。
<remotepubkey> OP_CHECKSIG 1 OP_CHECKSEQUENCEVERIFY OP_DROP
この単一のスクリプトをリーフとしたスクリプトツリーが構成され、そのルートハッシュto_remote_script_root
が計算される。そして、P2TRアウトプットの公開鍵は、
to_remote_output_key = taproot_nums_point + tagged_hash("TapTweak", taproot_nums_point || to_remote_script_root)*G
となり、P2TRアウトプットはOP_1 <to_remote_output_key>
となる。
to_remote
も既存のアンカー・アウトプットを継承するので、キー・パスによるアンロックではなく、1ブロック分の遅延が設定されたスクリプト・パスによるアンロックになる。
アンカー・アウトプット
BOLT#3で定義されている、現状のアンカー・アウトプットは、
<local_funding_pubkey/remote_funding_pubkey> OP_CHECKSIG OP_IFDUP OP_NOTIF OP_16 OP_CHECKSEQUENCEVERIFY OP_ENDIF
これに対して、Simple Taproot Channelのアンカー・アウトプットは、まず、16ブロック後に誰でも引き出せるスクリプト(anchor_script
)を、
OP_16 OP_CHECKSEQUENCEVERIFY
として、この単一のスクリプトをリーフとしたスクリプトツリーが構成され、そのルートハッシュanchor_script_root
を計算する。
そして、<local_funding_pubkey/remote_funding_pubkey> OP_CHECKSIG
の部分は、まず鍵がremotepubkey/local_delayedpubkey
に置き換えられ *1 、Taprootの内部鍵(anchor_internal_key
)として適用される。つまり、P2TRアンカー・アウトプットの公開鍵は、
anchor_output_key = anchor_internal_key + tagged_hash("TapTweak", anchor_internal_key || anchor_script_root)
となる。そして、P2TRアウトプットはOP_1 <anchor_output_key>
となる。
部分署名の作成
↑の形でファンディングトランザクションと最初のコミットメントトランザクションを両者それぞれ構成できたら、コミットメントトランザクションの部分署名をそれぞれ生成する。
open_channel/accept_channel
で受信したnext_local_nonce
を使って、MuSig2のNonceAggアルゴリズムを実行し、一意のnonceを生成する。- 集約nonceと自身が持つシークレットnonceを使って、MuSig2のSignアルゴリズムを実行し、部分署名を生成する。
funding_created/funding_signedの拡張
アリスはfunding_created
の、ボブはfunding_signed
の新しいTLVタイプpartial_signature_with_nonce
に生成した部分署名とpublic nonceを連結したデータをセットし、それぞれ送信する。
両者メッセージを受け取ると、部分署名とpublic nonceを使って、MuSig2のPartialSigVerifyアルゴリズムを実行し、受信した部分署名の有効性を検証する。
channel_readyの拡張
両者ともに有効な署名の検証が行えたら、互いにchannel_ready
メッセージを送信する。
この時、次にコミットメントトランザクションを更新する際の署名時に必要となる新しいpublic nonceを生成し、このメッセージに含める。
以上が、Simple Taproot Channelのチャネルセットアップの流れ。長くなったので、支払いの転送(HTLCの追加)についてはまた次回。
*1:MuSig2により両者のfunding_pubkeyは公開されなくなったので、代わりに、to_remote/to_localで使われているremotepubkeyとlocal_delayedpubkeyが使われるようになっている。