Develop with pleasure!

福岡でCloudとかBlockchainとか。

Simple Taproot Channel(Part 1)

lnd v0.17.0のリリースで、実験的にTaproot Channelのサポートが追加される↓

https://github.com/lightningnetwork/lnd/blob/master/docs/release-notes/release-notes-0.17.0.md#protocol-features

従来の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_channelaccept_channelで交換する互いのfunding_pubkeyを1つの公開鍵として集約する必要がある。

また、最初のコミットメントトランザクションに対する署名は、従来のチャネルでは両者が自分の秘密鍵を使って単独の署名を作成し、funding_createdfunding_signedでそれぞれ交換していたけど、Simple Taproot Channelでは、MuSig2を使ってそれぞれ集約公開鍵に対して有効な部分署名を交換するようになる。

open_channel/accept_channelの拡張

MuSig2を利用した部分署名の作成のためには、事前にpublic nonceにコミットし、交換する必要がある。そのため、両者はそれぞれMuSig2のNonceGenアルゴリズムを使ってpublic nonceを生成し、open_channelaccept_channelメッセージのnext_local_nonceにそれぞれ生成したpublic nonceをセットする(新しいTLVタイプでメッセージが拡張されている)。これらのpublic nonceは2つの圧縮形式の公開鍵を連結した66バイトのデータになる。

ファンディングトランザクションの作成

open_channel/accept_channelを交換した後は、以下の手順で2-of-2のマルチシグに相当する集約公開鍵を生成する。

  1. 両者のfunding_pubkeyをMuSig2のKeySortアルゴリズムでソート(辞書順にソート。ソートは既存のマルチシグスクリプトの鍵の順序付けでも使用されてる)
  2. ソートした公開鍵を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>となる。

部分署名の作成

↑の形でファンディングトランザクションと最初のコミットメントトランザクションを両者それぞれ構成できたら、コミットメントトランザクションの部分署名をそれぞれ生成する。

  1. open_channel/accept_channelで受信したnext_local_nonceを使って、MuSig2のNonceAggアルゴリズムを実行し、一意のnonceを生成する。
  2. 集約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が使われるようになっている。