Develop with pleasure!

福岡でCloudとかBlockchainとか。

Simple Taproot Channel(Part 2)

前回の記事では、Simple Taproot Channelの開設ステップを見てきたので↓

techmedia-think.hatenablog.com

今回は、支払い(HTLC)の転送の変更点についてみていく。

https://github.com/lightning/bolts/blob/e25132d8de0164224578964fcd3f7328ddfc3281/bolt-simple-taproot.md#channel-operation

支払いの転送

メッセージの拡張

支払い(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のタイムアウトと転送成功の条件がそれぞれスクリプトのリーフになる。

タイムアウトスクリプトhtlc_timeout

<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のタイムアウトと転送成功の条件がそれぞれ以下のスクリプトリーフとなる。

タイムアウトスクリプトhtlc_timeout

<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)の変更点。

続く。