协议规范

该文档详细阐述了一个在L1智能合约上管理支付通道的协议规范,涵盖了通道设置、用户支付、结算、代理争议处理和资金提现等整个生命周期。它利用默克尔树、Keccak256哈希函数、零知识证明或可信执行环境(TEE)进行验证,确保了链下支付的安全性和链上结算的准确性。

markdown

协议规范

预备知识

  • 哈希函数 (H):虽然最初假定为 Poseidon2 (SNARK友好型),但我们目前使用 Keccak256 进行原型设计。
  • 字段:我们使用 256位整数算术,并计划之后迁移到 254位字段(例如,BN254)。
  • 最大代理数:原型设计旨在处理每个通道约1000个代理(Merkle 树深度 = 10)。

智能合约

协议的核心是一个L1智能合约,用于管理通道生命周期。

  • 状态:合约维护 ChannelID 与其存储结构之间的查找映射。

    • 映射mapping(bytes32 => Channel) channels;
    • 结构体定义

      enum Status {
          Active,
          Settlement,
          Closed
      }
      
      struct Channel {
          Status status;
          bytes32 merkleRoot;          // Merkle 根
          uint256 maxFundedAmount;     // TotalMaxSpend 资金
          address owner;               // 通道控制器
          bytes32 encryptedValuesHash; // H(加密金额, 加密信息)
      }
      
  • 资金:持有用户存入的 TotalMaxSpend

  • 验证

    • 验证 $P_{\text{alloc}}$ 证明(通过代理进行链下检查,但合约持有承诺)。
    • 验证争议声明(链上)。
    • 验证最终结算证明。

协议生命周期图

sequenceDiagram
    participant User
    participant Agent
    participant Contract

    Note over User, Contract: 设置阶段
    User->>Contract: 1. 存入资金并提交 Merkle 根 (Mc)

    Note over User, Agent: 支付阶段
    User->>Agent: 2. 注册 (回执 + MerkleProof)
    User->>Agent: 3. 支付 (累计回执)

    Note over User, Contract: 结算阶段
    User->>Contract: 4. 发起结算 (EncryptedValues[], ReturnAmount)
    User->>Contract: 5. 证明 P_settle (有效求和)
    Contract->>Contract: 6. 进入结算模式 (争议窗口开始)

    Note over Agent, Contract: 争议阶段 (可选)
    opt 用户少付 / 被排除
        Agent->>Contract: 7. 争议 (回执 + MerkleProof + 密钥)
        Contract->>Contract: 8. 验证叶子 & 对比 EncryptedValues
        Contract->>Agent: 9. 直接支付代理 (如果有效)
    end

    Note over Agent, Contract: 提款阶段 (纪元后)
    Contract->>Contract: 10. 纪元最终确定 (H(通道ID, Merkle根, 加密值哈希) 累积)
    Agent->>Contract: 11. 声明 (所有纪元通道的元证明 P_claim)
    Contract->>Agent: 12. 批量转账

1. 通道设置

概述: 设置操作由用户在每个通道生命周期内执行一次。目前不支持通道调整,因为根据当前协议设计,调整会泄露旧预算和新预算之间的差异,这可能被用于推断新增代理的预算,并可能允许推断实际代理。

  1. 资金分配:用户选择代理并为每个代理分配特定预算 (MaxSpend)。
  2. 树构建:用户构建包含上述定义的叶子的 Merkle 树。
  3. 提交:用户提交 MerkleRoot ($Mc$),向合约存入 TotalMaxSpend 资金,并提交证明 `$P{\text{alloc}}$`,证明有足够的资金覆盖所有代理的预算。

格式良好证明 ($P_{\text{alloc}}$):

用户生成一个证明,以验证 TotalMaxSpend 覆盖了所有代理分配。此证明由代理在链下验证,以验证用户的偿付能力。

实现说明: 在 TEE 路径中,TeeUserSetup<H> 在安全区内验证 Merkle 树的构建并签署结果。计划后续用执行相同逻辑的 ZK 电路来替代。

叶子结构:

$$ Leaf = H(\text{SessionKey}, \text{Address}, \text{MaxSpend}) $$

数据布局:

  1. SessionKey (256 位) - 代理的密钥。
  2. Address (256 位) - 带有固定 96 个前导零位(有效 160 位)的以太坊地址。
  3. MaxSpend (256 位) - 最大花费限额,带有固定 192 个前导零位(有效 64 位)。

注意MaxSpend 字段设计为可容纳 64 位,但为简化原型暂时存储在 256 位插槽中。Address 字段同理(有效 160 位)。

可视化方案:

graph BT
    %% 叶子构建详情
    subgraph Construction [构建逻辑]
        direction BT
        SK["SessionKey<br/>(256位)"]
        Addr["地址<br/>(256位)"]
        MS["MaxSpend<br/>(64位)"]
        SK & Addr & MS -->|H| Leaf["叶子哈希"]
    end

    %% Merkle 树金字塔 (平衡)
    subgraph Tree [Merkle 树]
        direction BT
        L1[叶子 1] & L2[叶子 2] --> H1[节点哈希]
        L3[叶子 3] & L4[叶子 4] --> H2[节点哈希]
        H1 & H2 --> Root[Merkle 根]
    end

    %% 用于上下文的松散耦合
    Leaf -.-> L1

电路例程:

证明结构遵循标准的 Merkle Sum Tree 验证流程。例程 1 和 2 的输入是私有见证

  1. 例程 1:验证叶子到值
    • 目标:证明叶子构建并从私有见证数据中提取 MaxSpend
    • 私有输入SessionKeyPackedData$MS_i$
    • 逻辑
      • 计算 $LeafHash = H(\text{SessionKey}, \text{PackedData})$
      • 约束:验证 MaxSpend 的正确提取。
        • 检查 $\text{PackedData} \ll 160 == MS_i \ll 160$
        • 检查 $MS_i < 2^{64}$
      • 输出$(\text{LeafHash}, MS_i)$ 传递给例程 2。
  2. 例程 2:验证子到父 (Merkle Sum)
    • 目标:聚合哈希并将树向上求和(递归步骤)。
    • 私有输入
      • 左子:$(\text{LeftHash}, \text{LeftSum})$ -> 源自例程 1 或先前的例程 2。
      • 右子:$(\text{RightHash}, \text{RightSum})$ -> 源自例程 1 或先前的例程 2。
    • 逻辑
      • 计算 $ParentHash = H(\text{LeftHash}, \text{RightHash})$
      • 计算 $ParentSum = \text{LeftSum} + \text{RightSum}$
      • 输出$(\text{ParentHash}, \text{ParentSum})$ 传递到下一层。
  3. 例程 3:验证初始参数
    • 目标:确保计算的根匹配已提交的公共值。
    • 公共输入MerkleRoot ($M_c$),TotalMaxSpend
    • 私有输入 (已计算)$\text{FinalRoot}$$\text{FinalSum}$(最终例程 2 执行的结果)。
    • 逻辑
      • 断言 $\text{FinalRoot} == M_c$
      • 断言 $\text{FinalSum} == \text{TotalMaxSpend}$

2. 支付

概述: 支付是链下交互,用户向代理提供已签名的承诺。代理根据链上通道状态和首次交互时提供的 Merkle 证明验证这些承诺。

数据结构:

  • 支付回执struct { bytes32 channelId; uint64 amount; bytes signature; }
  • 签名内容(channelId, amount) 的签名哈希。
  • 签名者:必须是通道所有者的以太坊账户。
  • 注册信息struct { bytes32[] merkleProof; LeafDetails leaf; },其中 LeafDetails = struct { uint256 sessionKey; uint256 packedData; }
  • 仅在首次联系时提供。

协议逻辑:

  1. 首次联系 (注册):
    • 用户操作:发送 PaymentReceipt + RegistrationInfo
    • 代理验证
      1. 状态检查:断言 $\text{Channel}[\text{channelId}].\text{status} == \text{Active}$
      2. 所有权检查$\text{ecrecover}(\text{signature})$ 必须匹配 $\text{Channel}[\text{channelId}].\text{owner}$
        • 注意:地址不在签名中;它通过查找通道 ID 进行验证。
      3. Merkle 证明:验证 $\text{RegistrationInfo}.\text{merkleProof}$ 将所声明的叶子连接到 $\text{Channel}[\text{channelId}].\text{merkleRoot}$
      4. 叶子完整性:重建 $LeafHash = H(\text{SessionKey}, \text{PackedData})$ 并与证明叶子匹配。
        • 验证 $\text{PackedData}$ 中的 $\text{Address}$ 匹配代理自己的地址。
      5. 预算检查:从 $\text{PackedData}$(位 160-223)中提取 $\text{MaxSpend}$ 并断言 $amount \le \text{MaxSpend}$
      6. 存储:代理记录 merkleProofleaf$\text{LatestAmount} = amount$
  2. 后续支付:
    • 用户操作:发送更新的 PaymentReceipt,其中 $newAmount > oldAmount$
    • 代理验证
      1. 状态检查:断言 $\text{Channel}[\text{channelId}].\text{status} == \text{Active}$
      2. 签名检查:验证签名匹配 $\text{Channel}[\text{channelId}].\text{owner}$
      3. 预算检查:断言 $newAmount \le \text{MaxSpend}$(使用存储的 $\text{MaxSpend}$)。
    • 状态更新:代理更新 $\text{LatestAmount} = newAmount$
    • 效率:Merkle 证明/叶子详情会重新发送,因为 $\text{MaxSpend}$ 对于此纪元是不可变的。

设计决策:

  • 累积签名:用户签署累计总额($X + Y$)。代理只需保留金额最大的回执,并允许用户签署新的总额。
  • 效率:通道所有者地址不包含在签名消息中,因为它通过链上的 $\text{ChannelID}$ 查找隐式绑定。
  • 安全性:代理在每次支付时检查 $\text{Active}$ 状态,以防止接受来自处于 $\text{Settlement}$$\text{Closed}$ 状态的通道的资金(在这种情况下,用户可能已经开始提款)。

3. 结算

概述: 由用户触发,用于关闭通道并提取剩余资金。用户证明代理已花费的金额并收回差额。

旁注: 未来的迭代可能会考虑“部分关闭”以结算特定代理,同时保持其他代理活跃。

用户提交: 要进行结算,用户提交:

  1. 通道 ID
  2. 返回金额$Total - \sum \text{AgentSpent}$
  3. 加密代理数据:所有活跃代理的拆分加密值列表。
    • $\text{EncryptedAmount}: Amount \oplus H(\text{SessionKey}, 0)$
    • $\text{EncryptedInfo}: Address \oplus H(\text{SessionKey}, 1)$
  4. 证明 ($P_{\text{settle}}$)

实现说明: 在 TEE 路径中,TeeSettlement<H> 在安全区内验证结算会计。加密值作为 encryptedValuesHash = keccak256(abi.encode(encryptedAmounts, encryptedInfos)) 在链上提交。

链上承诺: 合约计算并存储 $$encryptedValuesHash = \text{keccak256}(\text{abi.encode}(\text{encryptedAmounts}, \text{encryptedInfos}))$$。此哈希随后在 finalizeSettlement() 期间包含在纪元哈希链条目中,将加密数据绑定到纪元哈希,并使 TEE/ZK 验证器能够在代理声明期间确认加密数据。

结算证明 ($P_{\text{settle}}$) - 电路例程:

  1. 例程 1:解密和验证 (拆分加密)

    • 目标:分别解密金额和地址以强制执行范围约束。
    • 公共输入$\text{EncryptedAmount}$$\text{EncryptedInfo}$
    • 私有输入$\text{SessionKey}$
    • 逻辑
      • $Amount = \text{EncryptedAmount} \oplus H(\text{SessionKey}, 0)$
      • $Address = \text{EncryptedInfo} \oplus H(\text{SessionKey}, 1)$
    • 约束$Amount < 2^{64}$
    • 安全性:如果用户提供伪造的 $\text{SessionKey}$,解密后的 $Amount$ 很可能是一个随机的 256 位值,这会以极高的概率违反此约束($Prob \approx 1 - 2^{-192}$)。
    • 输出输出: $Amount$
  2. 例程 2:求和

    • 目标:累积解密后的金额。
    • 输入$\text{Accumulator}$(内部),$Amount$(来自例程 1)。
    • 逻辑$\text{Accumulator} += Amount$
  3. 例程 3:最终余额

    • 目标:验证总账。
    • 公共输入$\text{TotalMaxSpend}$$\text{ReturnAmount}$
    • 私有输入$\text{Accumulator}$(最终)。
    • 逻辑:断言 $\text{TotalMaxSpend} == \text{ReturnAmount} + \text{Accumulator}$

安全分析 (假密钥攻击): 恶意用户可能尝试使用伪造的 $\text{SessionKey}$ 将有效条目解密为更低的金额(从而减少 $\text{Accumulator}$ 并增加他们的 $\text{ReturnAmount}$)。

  • 保护:通过单独加密 $Amount$ 并强制 $Amount < 2^{64}$,任何伪造密钥都会生成一个随机的 256 位“金额”,这会以极高的概率未能通过检查。
  • 替代方案 (Merkle 包含证明)
    • 我们可以转而证明 $\text{SessionKey}$ 存在于原始 MerkleTree 中。
    • 成本比较:这种方法需要 $L \times \log(N)$ 次哈希(其中 $L$ 是可结算的代理数量,$N$ 是代理总数),这通常比拆分加密检查(其复杂度随 $L$ 线性增长)更昂贵。

4. 代理争议

实现说明: 争议流程完全在链上。不涉及 TEE 或 ZK 证明; 合约直接验证 Merkle 证明、签名和解密金额。

概述: 如果用户尝试以不正确的金额关闭通道给代理(或完全排除代理),则会发生争议。

  • 隐私:此过程将泄露代理与用户的关联(SessionKey 被泄露)。
  • 前提条件:通道必须处于 $\text{Settlement}$ 状态。

代理提交: 代理通过以下参数调用合约上的 dispute()

  1. 通道 ID
  2. 支付回执$\{\text{channelId}, \text{amount}, \text{signature}\}$
  3. Merkle 证明:证明其叶子包含在 $\text{Channel}[\text{channelId}].\text{merkleRoot}$ 中。
  4. 叶子原像$\text{SessionKey}$$\text{Address}$$\text{MaxSpend}$

合约逻辑 (链上验证):

  1. 状态检查:断言 $\text{Channel}[\text{channelId}].\text{status} == \text{Settlement}$
  2. 签名检查:断言 $\text{ecrecover}(\text{signature}) == \text{Channel}[\text{channelId}].\text{owner}$
    • 确认用户向代理承诺了 $amount$
  3. Merkle 验证
    • 重建 $Leaf = H(\text{SessionKey}, \text{Address}, \text{MaxSpend})$
    • 验证 $\text{MerkleProof}$ 针对存储的 $\text{MerkleRoot}$ 验证 $Leaf$
  4. 所有权检查:断言 $\text{Address}$ 匹配 $\text{msg.sender}$(代理)。
  5. 结算验证
    • 遍历用户提交的 $\text{EncryptedValues}[]$(在结算触发期间存储/承诺)。
    • 尝试解密:对于每个条目,尝试 $DecryptedAddress = \text{EncryptedInfo} \oplus H(\text{SessionKey}, 1)$
    • 查找匹配:检查 $\text{DecryptedAddress}$ 是否匹配代理的地址。
    • 检查金额
      • 如果未找到匹配:声明缺失。争议有效
      • 如果找到匹配:检查 $\text{DecryptedAmount}$(来自 $\text{EncryptedAmount} \oplus H(\text{SessionKey}, 0)$)。
        • 如果 $\text{DecryptedAmount} < \text{claimedAmount}$:用户少付。争议有效
        • 否则:争议无效。

结果: 如果争议有效

  • 合约直接从通道资金中向代理支付 $\text{claimedAmount}$
  • 剩余余额(如果有)允许进行其他争议或用户最终提款。

5. 代理提款

概述: 代理从已成功完成 $\text{Settlement}$ 阶段(且无争议)的通道中提取资金。

  • 系统:声明按纪元汇总。
  • 纪元结构
    • mapping(uint256 => bytes32) epochHashes:每个纪元的滚动哈希链摘要(在最终确定时累积)。
    • mapping(address => mapping(uint256 => bool)) epochClaimed:跟踪代理是否已针对给定纪元声明过。

纪元哈希链:

当通道最终确定(通过 finalizeSettlement())时,其数据会累积到当前纪元的滚动哈希中:

$$ entryHash = H(\text{channelId}, \text{merkleRoot}, \text{encryptedValuesHash}) $$ $$ epochHash = H(\text{epochHash_prev}, \text{entryHash}) $$

其中 $$encryptedValuesHash = \text{keccak256}(\text{abi.encode}(\text{encryptedAmounts}, \text{encryptedInfos}))$$ 是在 initiateSettlement() 期间计算的加密结算数据的承诺。将 encryptedValuesHash 包含在纪元哈希条目中,将加密数据绑定到链上纪元哈希,防止代理在声明期间提供伪造的加密数据。

流程:

  1. 等待:代理等待通道退出争议窗口并最终确定。
  2. 纪元最终确定:通过滚动哈希链将通道累积到当前纪元的 epochHash 中。
  3. 声明:代理提交元证明$P_{\text{claim}}$)以一次性提取纪元中多个通道的资金。

实现说明: 在 TEE 路径中,TeeAgentClaim 在安全区内验证纪元哈希 重建和每项声明检查,然后签署结果。 TeeVerifier 合约检查 TEE 签名,而未来的 ZK 验证器 将验证 SNARKs。

声明证明 ($P_{\text{claim}}$) - 验证例程:

该证明汇总了跨通道列表的有效声明。验证器(TEE 或 ZK 电路)接收所有纪元条目以重建纪元哈希,以及代理拥有资金的通道的声明数据。

私有输入(每个纪元条目):

  • $\text{channelId}$$\text{merkleRoot}$:通道结算状态。
  • $\text{encryptedAmounts}[]$$\text{encryptedInfos}[]$:此通道的完整加密结算数据。
  • claim(可选):仅存在于代理有资金的通道:
    • $\text{claimAmount}$:代理声明的金额。
    • balanceProof:代理叶子的 Merkle 包含证明(包含 $\text{sessionKey}$$\text{agentId}$$\text{allowance}$)。
    • $\text{paymentSignature}$:用户对 $(\text{channelId}, \text{cumulativeAmount})$ 的 EIP-191 签名。

公共输入:

  • $\text{AgentAddress}$:声明代理(左填充至 32 字节)。
  • $\text{TotalClaimAmount}$:所有单个声明金额的总和。
  • $\text{EpochHash}$:链上纪元哈希摘要。

验证逻辑:

  1. 纪元哈希重建

    • 对于每个条目,计算 $$encryptedValuesHash = \text{keccak256}(\text{abi.encode}(\text{encryptedAmounts}, \text{encryptedInfos}))$$
    • $(\text{channelId}, \text{merkleRoot}, \text{encryptedValuesHash})$ 馈入哈希链。
    • 断言重建的摘要与公共 $\text{EpochHash}$ 匹配。
    • 这确保了所提供的加密数据与链上发布的数据相符。
  2. 通道排序

    • 断言 $\text{channelIds}$ 严格递增(防止重复声明)。
  3. 每项声明验证(对于每个带有声明的条目):

    • 代理身份:断言 $\text{balanceProof.leaf.agentId} == \text{AgentAddress}$
    • Merkle 包含:验证 $\text{balanceProof}$ 与条目的 $\text{merkleRoot}$
    • 超额声明检查:断言 $\text{claimAmount} \le \text{balanceProof.leaf.allowance}$
    • 加密金额验证
      • 从余额证明中获取 $\text{leafIndex}$(代理在树中的位置)。
      • 解密:$amount = \text{encryptedAmounts}[\text{leafIndex}] \oplus H(\text{sessionKey}, 0)$
      • 断言解密金额适合 u64(前 24 字节为零)。
      • 断言 $\text{decryptedAmount} \ge \text{claimAmount}$
    • 累积:$\text{totalClaimed} += \text{claimAmount}$
  4. 总余额:断言 $\text{totalClaimed} == \text{TotalClaimAmount}$

链上验证:

  1. 断言纪元已最终确定(在过去):$epochNumber < \text{currentEpoch}$
  2. 断言 $\text{epochHashes}[\text{epochNumber}] \ne 0$(纪元有已结算的通道)。
  3. 断言 $\text{epochClaimed}[\text{agentAddress}][\text{epochNumber}] == \text{false}$
  4. 验证 $P_{\text{claim}}$(公共输入:$\text{agentAddress}$$\text{totalClaimAmount}$$\text{epochHash}$)。
  5. $\text{totalClaimAmount}$ 转账给 $\text{agentAddress}$
  6. 设置 $\text{epochClaimed}[\text{agentAddress}][\text{epochNumber}] = \text{true}$

旁注: 增量提款(未来扩展)。 通过跟踪每个代理的状态,允许无需等待纪元提款是可行的。

权衡:增加了状态增长/存储成本,但改善了代理的流动性。

实现参考

此表将正式协议概念映射到它们在 Rust 和 Solidity 中的实现。

规范概念 Rust Solidity
Merkle 根 ($M_c$) BalanceTree<H>.root() channels[id].merkleRoot
叶子 = H(会话密钥, 地址, 最大花费) BalanceEntry + Hasher::hash() setupChannel() input
$P_{\text{alloc}}$ (设置证明) TeeUserSetup<H> IVerifier.verify() via setup flow
$P_{\text{settle}}$ (结算证明) TeeSettlement<H> initiateSettlement()
$P_{\text{claim}}$ (代理提款证明) TeeAgentClaim claim()
加密金额 BlindingScheme::blind() IBlindingScheme.unblind()
通道结构体 Channel<P> in user crate Channel struct in PrivateX402.sol
支付回执 PaymentReceipt in common EIP-191 ecrecover
纪元哈希链 HashChain<H> epochHashes mapping
会话密钥 session_key: [u8; 32] Blinding factors

TEEZK 路径:当前的 MVP 使用 TEE (Trusted Execution Environment) 作为信任后端。 TEE 签署证明输出(选择器 + ECDSA 签名)而不是生成 ZK 证明。 TeeVerifier 验证来自注册 TEE 密钥的 ECDSA 签名,而未来的 ZK 验证器 将验证 SNARKs。

  • 原文链接: github.com/ConorNethermi...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ConorNethermind
ConorNethermind
江湖只有他的大名,没有他的介绍。