前回の記事では、Simple Taproot Channelの開設ステップを見てきたので↓
techmedia-think.hatenablog.com
今回は、支払い(HTLC)の転送の変更点についてみていく。
支払いの転送
メッセージの拡張
支払い(HTLC)の転送のメッセージフローも、従来の以下のフローと変わらない。ただ、メッセージがそれぞれ拡張される。
HTLCの追加
アリス ボブ (1) update_add_htlc ----------------------------------> (2) commitment_signed ----------------------------------> (3) revoke_and_ack <---------------------------------- (4) commitment_signed <---------------------------------- (5) revoke_and_ack ---------------------------------->
HTLCの解決
アリス ボブ (1) update_fullfill_htlc/update_fail_htlc <---------------------------------- (2) commitment_signed <---------------------------------- (3) revoke_and_ack ----------------------------------> (4) commitment_signed ----------------------------------> (5) revoke_and_ack <----------------------------------
前回の記事に書いたメッセージの拡張から分かるように、基本的には、MuSig2の部分署名を交換して、次の更新用に新しいpublic nonceを交換するという内容の拡張になってる。
commitment_signedの拡張
commitment_signed
メッセージは、新しい状態のコミットメント・トランザクションに対する署名データを含むが、元々のsignature
フィールドは64バイト分の0がセットされ、MuSig2の部分署名と集約nonceをセットするTLVが新しく追加される。
revoke_and_ackの拡張
新しいコミットメントに合意し、古いコミットメントを取り消すのに使われるrevoke_and_ack
メッセージには、次の状態の更新に使用するための新しいnonceを生成し、対応するpublic nonceをセットするTLVが追加されている。
channel_reestablishの拡張
↑のメッセージフローには記載していないけど、ノードを再接続した際に送信されるchannel_reestablish
メッセージにも、public nonceをセットするTLVが追加されている。ノードが再接続すると、新しいnonceを生成し、対応するpublic nonceをメッセージにセットして相手に送信、それが次にチャネルの状態を更新する際の署名作成時に使用されるnonceになる。
基本的に、前回の記事に掲載したchannel_ready
と、↑のrevoke_and_ack
およびchannel_reestablish
で、次に使用するnonceを事前共有する。
HTLCトランザクションの変更点
支払いを転送(HTLC)する際のトランザクションの変更点は以下のとおり。
Offered HTLC
BOLT#3で定義されているHTLCをコミットメント・トランザクションに追加する際の送金側のOffered HTLCアウトプットの現状のスクリプトは↓(アンカーアウトプット版)
# リモートノードによる失効 OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocation_pubkey))> OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL OP_NOTIF # ローカルノードがHTLCタイムアウトで回収 OP_DROP 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG OP_ELSE # リモートノードがプリイメージで回収 OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY OP_CHECKSIG OP_ENDIF 1 OP_CHECKSEQUENCEVERIFY OP_DROP OP_ENDIF
Simple Taproot Channelでは、失効用のrevocation_pubkey
はTaprootの内部鍵として使用され、HTLCのタイムアウトと転送成功の条件がそれぞれスクリプトのリーフになる。
<local_htlcpubkey> OP_CHECKSIGVERIFY <remote_htlcpubkey> OP_CHECKSIG
転送成功のスクリプト(htlc_success
)
OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY <remote_htlcpubkey> OP_CHECKSIG 1 OP_CHECKSEQUENCEVERIFY OP_DROP
これら2つをリーフとしたスクリプトツリーが構成され、そのルートハッシュhtlc_script_root
が計算される。
そしてP2TRのアウトプットの公開鍵は、
offered_htlc_key = revocation_pubkey + tagged_hash("TapTweak", revocation_pubkey || htlc_script_root)
となり、P2TRアウトプットは、OP_1 <offered_htlc_key>
。
Accepted HTLC
BOLT#3で定義されているHTLCをコミットメント・トランザクションに追加する際の受信側のReceived HTLCアウトプットの現状のスクリプトは↓(アンカーアウトプット版) (※ BOLT#3ではReceived HTLCだけど、Simple Taproot ChannelのPRでは、Accepted HTLCって名前に変わってる?)
# リモートノードによる失効 OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocation_pubkey))> OP_EQUAL OP_IF OP_CHECKSIG OP_ELSE <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL OP_IF # ローカルノードがプリイメージで回収で回収 OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY 2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG OP_ELSE # リモートノードがタイムアウトで回収 OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_CHECKSIG OP_ENDIF 1 OP_CHECKSEQUENCEVERIFY OP_DROP OP_ENDIF
↑も、Offered HTLCの場合と同様、失効用のrevocation_pubkey
がTaprootの内部鍵として使用され、HTLCのタイムアウトと転送成功の条件がそれぞれ以下のスクリプトリーフとなる。
<remote_htlcpubkey> OP_CHECKSIG 1 OP_CHECKSEQUENCEVERIFY OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
転送成功のスクリプト(htlc_success
)
OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY <local_htlcpubkey> OP_CHECKSIGVERIFY <remote_htlcpubkey> OP_CHECKSIG
これら2つをリーフとしたスクリプトツリーが構成され、そのルートハッシュhtlc_script_root
が計算される。
そしてP2TRのアウトプットの公開鍵は、
accepted_htlc_key = revocation_pubkey + tagged_hash("TapTweak", revocation_pubkey || htlc_script_root)
となり、P2TRアウトプットは、OP_1 <accepted_htlc_key>
。
HTLC-Success / HTLC-Timeout
上記のプリイメージによる回収およびタイムアウトによる回収を行うHTLC-Success / HTLC-Timeoutトランザクションは、コミットメントトランザクションの上記UTXO1つをインプットとして、1つのアウトプットを持つトランザクションになる。
両方ともTaprootのスクリプト・パスを使用したアンロックで、上記のスクリプトを満たす要素をwitnessとして提供する。
構造はそれぞれ
HTLC-Successトランザクション
version: 2 nLocktime: 0 インプット: OutPoint: コミットメント・トランザクションのHTLCのUTXO sequence: 1 witness: <remotehtlcsig> <localhtlcsig> <preimage> <htlc_success_script> <control_block> アウトプット: value: コミットメント・トランザクションのHTLCの金額 scriptPubkey: OP_1 htlc_success_key
HTLC-Timeoutトランザクション
version: 2 nLocktime: コミットメント・トランザクションのHTLCに設定されているcltv_expiry インプット: OutPoint: コミットメント・トランザクションのHTLCのUTXO sequence: 1 witness: <remotehtlcsig> <localhtlcsig> <htlc_timeout_script> <control_block> アウトプット: value: コミットメント・トランザクションのHTLCの金額 scriptPubkey: OP_1 htlc_timeout_key
htlc_success_key
およびhtlc_timeout_key
は、コミットメント・トランザクションと同じrevocation_pubkey
をTaprootの内部鍵として、以下の単一のスクリプトをリーフとしたスクリプトツリーから構成されるTaprootの公開鍵になる。
<local_delayedpubkey> OP_CHECKSIG <to_self_delay> OP_CHECKSEQUENCEVERIFY OP_DROP
2つのトランザクションはいずれも手数料がゼロで、かつSIGHASHフラグSIGHASH_SINGLE|SIGHASH_ANYONECANPAY
で署名されているので、このトランザクションそのままでスイープすることはない前提っぽい。
また、Offered HTLCとAccepted HTLCについて、従来のコミットメントトランザクションと違ってる条件が1つある。従来のトランザクションは、プリイメージによる回収 / タイムアウトによる回収のどちらの条件句でも1 OP_CHECKSEQUENCEVERIFY
の条件(アンカー・アウトプット用の条件)が付与されているけど、Simple Taproot Channel版では、Offered HTLCとAccepted HTLCそれぞれ、リモートノードの回収条件側にしかOP_CHECKSEQUENCEVERIFY
の条件はついておらず、ローカルノードの条件にはない(Offered HTLCの場合はhtlc_success
、Accepted HTLCの場合はhtlc_timeout
にのみ付いている)。
これは、結局↑のHTLC-Success / HTLC-Timeoutトランザクションについて、受け取るリモート側の署名が既にインプットのsequence=1
にコミットしているのが理由。
以上が、支払いの転送(HTLC)の変更点。