前回の記事では、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)の変更点。