# Lightning Network YP 本ネットワークにおいては手数料をネットワークのネイティブトークンではなく、Lightning NetworkによるBTCでの支払いを通して行う。 本項ではオペレーターへのLightning Networkを通した手数料支払について解説する。 ## 基本モデル ユーザーはまずトランザクションのデータをオペレーターに対して提示し、見積もられる計算量やストレージ使用量に合わせたInvoiceをユーザーに提示する。 ユーザーはDiffと一緒にこのInvoiceを受け取り、Lightning Networkで支払う。オペレーターは支払いを確認した後トランザクションを処理する。 ```mermaid sequenceDiagram actor U as User participant O as Operator participant T as Totoro Network autonumber U --> O : Send Tx Data O --> U : Send Invoice and diff U --> O : Pay Network fee via LN O --> T : Execute Tx ``` ただし、この方式ではユーザーが支払ったあと、オペレーターがトランザクションを処理することについて保証されていない。 ユーザーが支払った場合、きちんとトランザクションが処理されることについて保証される方式について以下で解説する。 ## トラストレスに手数料支払をおこなうための条件 上記のようにトラストレスに支払いが可能であるための条件を考える。 Lightning Networkでの支払いにおいて、Txの処理の有無に応じた払い戻しはできないため、もしオペレーターが処理を行わなかった場合についてはLightning Networkではなく本ネットワークもしくはL1チェーン上での補償を行う必要がある。 そのため、オペレーターは補償を行うチェーンに対してなんらかの資産をステークし、オペレーターに不正があった場合、そのチェーンを通して補償させる必要がある。 また、その場合ユーザー側の支払いの遅延などによってオペレーターが不利益を被らないようにする必要がある。そのうえ、Lightning Networkの支払いについては正常に行われている必要がある。 以上の点を踏まえて以下の6点をトラストレスな手数料支払に必要な条件と定義する。 1. InvoiceのPreimageは支払いが完了するまで判明してはいけない 2. InvoiceがそのTxに対する正しいInvoiceであることはユーザーが支払うまでに検証可能でないといけない 3. ただしいメッセージ交換が行われている場合、オペレーターリーダーが交代するまでにすべての通信が必ず完了し、その後のメッセージはすべて無効である 4. オペレーターは支払いを受けたのであれば必ずTxが発行可能な状況でなければならない 5. Txを実行しなかった場合、確実に補償を請求できる 6. Txを実行した場合、確実に補償を請求できない ## 支払い方式 今回提案する手法においてはオペレーターはInvoiceと同時にユーザーのTxHashとOneTimeAddressを含んだpreimageの署名をユーザーに提出することを要求する。 (この際に利用する鍵はリーダー交代時にL1に提出する) これを確認した後、ユーザーは手数料をLightning Networkで支払う。 また、この際利用されるInvoiceは現在リーダーであるオペレーターがリーダーを交代するまでに期限切れしないといけない。 #### シーケンス図 - sigPubkey = Public key for signature - sigPrivkey = Private key for signatures ```mermaid sequenceDiagram actor U as User participant O as Operator participant T as Totoro Network participant F as L1 Chain autonumber O --> F : Submit sigPubkey U --> O : Throw Tx data O --> O : Calc gas fee O --> U : Return receipt, invoice, siblings O --> U : Return OPSIG = Sig(Hash(Prefix + Hash(preImage) + OneTimeAddress, L1Address), sigPrivkey) U --> U : Validate Invoice U --> O : Pay Fee through LN O --> T : Execute Transaction with invoice hash ``` ### OperatorがTxを処理しない場合 この場合、ユーザーはL1に対して補償をClaimすることができる。 その際、ユーザーはLNの支払いによって獲得したInvoiceのPreimageを提出する必要がある。 Preimageとinvoiceおよびオペレーターが提出した署名、そして使用する予定であったOneTimeAddressを提出する。 #### シーケンス図 - sigPubkey = Public key for signature - sigPrivkey = Private key for signatures ```mermaid sequenceDiagram actor U as User participant O as Operator participant T as Totoro Network participant F as L1 Chain autonumber O --> F : Stake Token(WBTC or ETH) O --> F : Submit sigPubkey U --> O : Throw Tx data O --> O : Calc gas fee O --> U : Return receipt, invoice O --> U : Return OPSIG = Sig(Prefix + Hash(Hash(invoice) + OneTimeAddress), sigPrivkey) U --> U : Validate Invoice U --> O : Pay Fee through LN O --> O : Hold Tx O --> O : Leader Changed U --> F : Submit OneTimeAddress, Invoice, OPSIG, preimage F --> F : Check if Operator is lazy F --> O : Slash Lazy OP's stake O --> U : Transfer staking token ``` ## Claimの具体的な方式 補償のClaimは二段階の方式から成り立っている。 ### 1. オペレーター署名の確認 このフェーズではまず指定されたオペレーターが提出した公開鍵(前述のsigPubkey)を参照し、すでにリーダーから交代されていることを確認する。 そののち、提出された署名が正しくInvoiceのハッシュとOneTimeAddressのハッシュであることおよびPreImageがInvoiceに対して正しいことを確認する。 ### 2. OneTimeAddressのNon Inclusion Proof 対象のオペレーターがリーダーの間に提出された、Diffが出現したOneTimeAddressのSMTすべてに対して、ユーザーのOneTimeAddressが含まれていないことをNon Inclusion Proofによって証明する。 もしSMTに含まれていた場合は該当トランザクションは処理されているため、Claimは通らなくなる。 ```mermaid sequenceDiagram actor U as User participant F as L1 Chain participant O as Operator autonumber O --> F : Submit sigPubkey O --> F : Submit SMTs of OneTimeAddress O --> O : Change Leader Operator U --> F : Submit (LeaderID, OPSIG, Invoice, Preimage, OneTimeAddress) F --> F : Check if leader is already changed F --> F : Validate PreImage F --> F : Validate OPSIG with LeaderPubkeys[LeaderID] F --> F : Check if OneTimeAddress is not included in SMTs F --> U : Transfer Staking Token ``` ## セキュリティおよび各種攻撃手法への対応 まず、手数料支払を行うための6つの条件を満たしているかについて検討する。 1. InvoiceのPreimageは支払いが完了するまで判明してはいけない => Operetorが漏らさない限り、支払い完了時までPreImageは伝えられず、この点は満たしている 2. InvoiceがそのTxに対する正しいInvoiceであることはユーザーが支払うまでに検証可能でないといけない => OPSigにはOneTimeAddressが含まれているため、該当Txに対する正しいInvoiceであるかどうかは署名およびInvoiceの内容を確認することで検証できる。 3. ただしいメッセージ交換が行われている場合、オペレーターリーダーが交代するまでにすべての通信が必ず完了し、その後のメッセージはすべて無効である => オペレーターとユーザーの間のメッセージの交換はLightning Networkでの送金で最後であり、Invoiceの有効期限(すなわちリーダー交代前)までに完了する 4. オペレーターは支払いを受けたのであれば必ずTxが発行可能な状況でなければならない => 最初にTxのデータを受け取るため、支払いさえ行われれば常にTxを発行できる状態になっている 5. Txを実行しなかった場合、確実に補償を請求できる => Lightning Network支払いで得たPreImageとオペレーターの署名があり、なおかつOneTimeAddressがSMTに乗っていない(=Txが実行されていない)場合は確実にClaimできる 6. Txを実行した場合、確実に補償を請求できない => SMTに当該OneTimeAddressが乗っているため検証が通らない。 ### その他の攻撃手法と対策 #### オペレーター側からの攻撃 - **Feeを過大に請求される** => Invoiceを確認して支払わない選択をすれば良い (ただし、正しいFeeのInvoiceがかならず請求されるわけではない) #### ユーザー側からの攻撃 - **LNの支払いをしない** => Preimageを保有していないのでProofが通らない - **Operatorが交代してから支払い、Preimageを入手しようとする** => Invoiceの有効期限が切れているのでしはらいがFailする - **リーダー交代ギリギリにユーザーが支払う** => Invoiceの有効期限に交代まで猶予のある時間を設定すれば良い - **安いtxで見積もりし安いinvoiceゲットし、別の手数料が高いtxを投げてくる** => 最初に提出したTxDataから変更ができないようにすれば良い。 - **ステーク量 >= spamにかかる手数料**が成り立つのであれば常にDoSの可能性がある - IPもバレてしまっている - 普通のブロックチェーンと違い、ブロック生成者だとバレてるのが辛い - アプリ層だけのDoSではなく、ネットワーク層でのDoSもあり得そう => 解決策 ## この方式におけるメリット - ZKEVM内と紐付かないでgasが支払われ、手数料に関してZK側で処理をおこなわないため、シンプルな実装になりConstraints数の増加も発生しない。 - User <=> Operator間の通信が増加しない - ClaimはL1チェーン上で完結するため、ZKネットワークの実装が複雑にならない ## 注意点 - リーダーの交代はあるtimestampをもってかならず交代されなければならない - OneTimeAddressは必ず使い捨てであり、それはなんらかの都合でRevertされた場合でも使用されたアドレスはその後使われない必要がある(もし使いまわしてしまうとOperatorがTxを処理しない => その後別のTxを同じOneTimeAddressで処理する状態になった場合、前者をClaimすることが不可能になる) - オペレーターの交代直前にTPSが大きく落ちるため対策が必要。 - ブロックリミットでtxがこれ以上入らないのにtxを受け取ってしまうとスラッシュされる(そもそもL2にブロックリミットはあるだろうか?) ## テスト設計 ### オペレーター側のテスト ```javascript= // リーダー選出時に公開鍵を登録する describe("Initialize Leader", () => { // 公開鍵を生成してL1に登録する it("Generate and Register Public key", () => {}); }); // LNノードがP2PでTXデータを受け取る (HTTP等、他の通信経路でもほぼ同様) describe("Receive TX request", () => { // TXリクエストを検証 it("Validate TX request") // TX feeを計算 it("Calculate TX fee") // 算出したFeeをもとにInvoiceを作成する describe("Create Invoice", () => { // AmountはTxDataから算出されたfeeと同額である必要がある it("Check amount", () => {}); // 有効期限はLeaderが交代するより十分前である必要がある it("Check expiry", () => {}); // 正しくPreimageを保存する it("Create and save preimage", () => {}); }); // InvoiceおよびOneTimeAddressをもとにOPSIGを作成 describe("Create OPSIG", () => { // 署名するメッセージが正しく生成されているか it("Create message", () => {}); // 事前に登録した公開鍵で複合できるか確かめる it("OPSIG can be verified by Operator's pubkey", () => {}) }); // LNノードにP2Pで、あるいは別の通信経路でreceipt, invoice, OPSIGを返す it("Send receipt, invoice, OPSIG") }); // Paymentの受け取りの検知とTxの実行を行う describe("Receive LN Payment", () => { // LN払いのFeeを正しく受け取って実行する it("Receive LN fee and execute", () => {}); }); ``` ### ユーザー側のテスト ```javascript= describe("Send TX request", () => { // TX requestを作成する it("Create TX request") // もしLNノードのP2Pで提出する場合、オペレーターにピアとして接続する it("Add Operator as peer") // TX requestを提出する it("Send TX request") }) describe("Receive receipt, invoice, and OPSIG", () => { it("Check if receipt, invoice and OPSIG have been received", () => {}); }); // OPが発行したReceiptメッセージを確認する describe("Validate receipt", () => { // Receiptの中身を確認する it("Validate and store receipt message") }) // OPが発行したInvoiceがまともかどうか判断する describe("Validate Invoice", () => { // Invoiceの形式チェック it("Deny if invoice is invalid", () => {}); // Invoiceの期限が切れていたら拒絶 it("Deny if invoice is expired", () => {}); // Feeが高すぎる場合は拒絶 (事前の試算との差 or ユーザーが都度選択) it("Deny if payment amount is too large", () => {}); }); // OPが発行したOPSIGがまともかどうか判定する describe("Validate OPSIG", () => { it("Accept correct OPSIG", () => {}); // 正しく複合できていなければ拒絶 it("Deny if prefix is incorrect", () => {}); // 正しいMessageでなければ拒絶 it("Deny if message signed is incorrect", () => {}); }); // 支払いを行ってPreimageを保存する describe("LN Payment", () => { // Feeを支払う it("Pay TX fee over LN", () => {}); // 支払いが失敗したらTX失敗 (incl. ルーティングフィー高すぎ、ルートなし、ステート変化) it("Handle payment failure", () => {}); // 支払い成功・失敗のどちらも返ってこない場合は期限まで待機 it("Handle stuck payment", () => {}); // Preimageを保存する it("Save preimage with invoice, OPSIG", () => {}); }); // L2のTXが実行されたか確認する describe("L2 TX succeeded", () => { // 成功したか確認する it("Check if L2 TX succeeded", () => {}); }) ``` ### Claimコントラクトのテスト ```javascript= // Claimの前に分散DBからsiblingsを獲得して検証する describe("Get SMT siblings from DB", () => { // DBから得られたSMTのSiblingが正しいことを検証する it("Get and validate SMT siblings", () => {}); }); // Claimする describe("Claim staking token", () => { // Userのアドレスの残高が増えている it("Check Balance of user L1 address", () => {}); // Operatorの残高が減っている it("Check balance of Operator address", () => {}); // 予めCommitしていたL1 Claimアドレス以外が請求したらFailする it("Fail if sender is invalid", () => {}) // まだLeaderが交代していなければFail it("Fail if Leader operator is not changed", () => {}); it("Fail if preimage is invalid", () => {}); it("Fail if OPSIG is invalid", () => {}); // onetimeaddressは対象OPのSMTのなかに存在してはいけない it("Fail if onetimeaddress has been included in Diff SMTs", () => {}); }); ``` ###### tags: `Yellow paper` `Fee Payment` `Lightning Network`