zERC20:基于零知识燃烧证明的跨链隐私型 ERC-20 - ERC标准

zERC20 是一种基于零知识证明的隐私保护型 ERC-20 代币,它通过 zk-Wormhole 的燃烧证明机制,允许用户使用标准 Web3 钱包进行私密转账,并原生支持跨链转账。该方案利用 Nova IVC 构建链下 Merkle 树以降低链上 Gas 成本,并详细介绍了其转账流程、技术细节(如燃烧地址生成、批量提款)以及跨链实现架构。

审核支持:INTMAX 团队

标题链接 摘要

以太坊上的隐私变得越来越重要。然而,许多现有的隐私解决方案需要额外的设置,或者依赖于专用钱包和自定义用户界面。

zERC20 是一种隐私保护的 ERC-20 代币,它集成了 zk-Wormhole 的燃烧证明机制。它与标准 Web3 钱包无缝协作,通过熟悉的 ERC-20 界面实现私密转账。

通过使用 Nova IVC 构建链下转账 Merkle 树并支持批量提款,zERC20 实现了轻量级 ZK 证明和低的链上 gas 成本。此外,zERC20 原生支持跨链私密转账。

标题链接 转账流程

zERC20 基于 EIP-7503: Zero-Knowledge Wormholes / Private Proof-of-Burn 中提出的概念。

https://ethereum-magicians.org/t/eip-7503-zero-knowledge-wormholes-private-proof-of-burn-ppob/15456

当用户 A 将代币发送给用户 B 时,A 生成一个随机 secret 并计算:

burnAddress = trim160(poseidon(recipient, secret))

然后 A 将 zERC20 代币发送到这个 burnAddress。由于碰撞概率极小,这些转账的代币可以被视为燃烧掉。

如果 B 生成这个 burnAddress 并与 A 共享,B 可以隐藏其真实地址。

当 B 提款时,他们向 验证者 合约提交一个零知识证明,证明:

  1. 已向相应的 burnAddress 进行了 zERC20 转账;并且
  2. 他们知道用于形成该 burnAddresssecret

验证者铸造相同数量的 zERC20 代币给 B。为了防止双重提款,合约会追踪每个目的地的累计提款金额,并且只允许铸造已接收已提款之间的差额。

标题链接 为何将 zk-Wormholes (EIP-7503) 应用于 ERC-20?

最初的 zk-Wormhole 提案针对的是 ETH。由于 ETH 转账到常规地址和燃烧地址在协议层面无法区分,它提供了非常强大的隐私。

相比之下,zERC20 是一种 ERC-20 代币。转账到未使用的地址有时可以与普通转账区分开来。然而,随着 账户抽象 (AA) 的普及,这一差距预计会缩小。

即便如此,将 zk-Wormholes 应用于 ERC-20 仍具有以下几个优势:

  • 钱包兼容性:通过标准的 ERC-20 流程,从普通钱包进行私密转账。
  • 易于 dApp 集成:现有 DEX、借贷协议和桥接器自然支持。
  • 跨链支持:利用 zk-Wormhole 设计,在多个链之间私密地转移资金。

简而言之,zERC20 的功能类似于 Tornado Cash,但不同之处在于无需特殊的存款功能或用户界面——仅使用标准的 ERC-20 转账操作即可进行私密交易。

zk-wormhole\ zk-wormhole1316×664 76.6 KB

标题链接 技术细节

标题链接 符号说明

  • Frbn254 的标量域。
  • poseidon(a, b): Fr × Fr -> Fr 表示 2 输入 1 输出的 Poseidon 哈希。
  • Merkle 证明 是 Poseidon 哈希二叉 Merkle 树中的兄弟节点列表。merkle_proof.get_root(i, leaf_hash) 表示使用该证明计算第 i 个 leaf_hash 的 Merkle 根。
  • trimN 截断低 N 位。

标题链接 通过链下 Merkle 树构建减少链上 Gas

提款 ZKP 必须证明向给定燃烧地址的转账。在 Poseidon 哈希树上的 Merkle 证明对此是高效的。然而,链上更新 Merkle 树(如 Tornado Cash 中)成本很高(约 90 万 gas),如果像 zERC20 一样在每次转账时更新,这是不可接受的。

因此,在链上我们使用Gas 效率高的承诺(一个哈希链)并使用 Poseidon 重建链下 Merkle 树。这同时降低了链上 gas 和提款时的 ZKP 成本。

标题链接 根转换步电路

公共输入:

[prev_index, prev_hash_chain, prev_transfer_root]

见证:

  • to ∈ Fr:嵌入 Fr 中的 160 位以太坊地址。
  • value ∈ Fr:假定适合 248 位内。
  • merkle_proof:转账树中 prev_index 处元素的 Merkle 证明。

约束条件

1. new_hash_chain ← trim246(sha256(prev_hash_chain || to || value))
2. prev_transfer_root == merkle_proof.get_root(prev_index, poseidon(0, 0))
3. new_transfer_root ← merkle_proof.get_root(prev_index, poseidon(to, value))
4. new_index ← prev_index + 1

公共输出:

[new_index, new_hash_chain, new_transfer_root]

我们使用 Nova IVC 迭代地应用此步电路,生成一个从

[initial_index, initial_hash_chain, initial_transfer_root]

[current_index, current_hash_chain, current_transfer_root] 的转换证明。

我们称之为根转换 Nova

验证者合约从 zERC20 查询 (index, hashChain),并将其与已证明的 transferRootindex 一起作为根转换 Nova 的公共输入。如果证明通过验证,它会存储 newTransferRootnewIndex。这些稍后将用作提款证明的公共输入。

transfer\ transfer1937×1071 176 KB

标题链接 燃烧地址生成与批量提款

我们使用 Poseidon 生成燃烧地址:

burnAddress = poseidon(recipient, secret)

这里的 recipient 是一个通用接收者,计算方式如下:

recipient = trim246(keccak256(recipient_chain_id, recipient_address, tweak))
  • recipient_chain_id:接收者的链 ID(64 位)
  • recipient_address:接收者的 160 位地址
  • tweak:一个任意的 32 字节值,用于划分 recipient_address 的“命名空间”(下文解释)

我们可以通过以下电路将多个提款聚合到一个证明中。除了可扩展性之外,只发布金额(而不是每个单独的金额)可以提高隐私性。

标题链接 提款步电路

公共输入:

[transfer_root, prev_index_with_offset, prev_total_value]

见证:

  • recipient:绑定到燃烧地址的接收者
  • secret:用于生成燃烧地址的 secret
  • value:转账到燃烧地址的金额
  • index:该转账的索引
  • merkle_proof:转账树中 index 的 Merkle 证明

约束条件

1. burn_address ← poseidon(recipient, secret)
2. Assert transfer_root == merkle_proof.get_root(index, poseidon(burn_address, value))
3. new_index_with_offset ← index + 1
4. Assert prev_index_with_offset < new_index_with_offset
5. new_total_value ← prev_total_value + value

公共输出:

[new_index_with_offset, new_total_value]

我们通过 Nova 迭代地应用此步电路,获得一个从

[transfer_root, recipient, initial_index_with_offset = 0, initial_total_value = 0]

[transfer_root, recipient, current_index_with_offset, current_total_value]提款 Nova

通过要求严格递增的 index,我们防止在同一提款 Nova 证明中出现双重提款。验证者合约记录 totalWithdrawn[recipient],即每个 recipient 的累计提款金额。

提款流程如下:

  1. 接收 recipient = trim246(keccak256(recipient_chain_id, recipient_address, tweak)) 的组成部分作为参数,并验证 recipient_chain_id == block.chainid
  2. 验证提款 Nova,并确保 proof.recipient 与步骤 1 中的 recipient 匹配。
  3. 计算 delta = proof.current_total_value − totalWithdrawn[recipient],并且仅当 delta > 0 时,提款(铸造)精确的 delta

标题链接 关于燃烧地址和提款的说明

标题链接 通过 tweak 轮换接收者

一个提款 Nova 必须处理相同 recipient所有提款。如果收据很多,Nova 步骤的数量会变得很大。通过更改 tweak,即使是相同的 (recipient_chain_id, recipient_address) 对,我们也可以更改 recipient,从而重置 Nova 步数。

如果你为每个收据都更改 tweak,每次提款都可以作为单次提款完成,从而启用轻量级的 Groth16 证明。当 Nova 的决策者证明(在 MacBook 上可能需要约 30 秒的较重操作)不合时宜时,这很有吸引力。权衡是隐私性降低,因为单次提款会透露提款金额。

标题链接 燃烧地址的工作量证明

如果能找到这样的 (recipient, secret)(recipient', secret') 对,使得

trim160(poseidon(recipient, secret)) == trim160(poseidon(recipient', secret'))

那么发送到该燃烧地址的资产可能会被提款两次。由于生日悖论,160 位哈希的碰撞成本约为 80 位,这是不足的。

因此,我们要求一个工作量证明条件:poseidon(recipient, secret) 的第 161 位到 160 + n 位必须为零。这使得每次尝试增加 n 位工作量,有效提升了 n 位的安全性。

标题链接 跨链转账

每个链上的验证者合约通过跨链消息协议(例如 LayerZero)将其本地转账树根传输到一个 Hub 合约。Hub 合约从这些根构建一个全局 Merkle 树,并将生成的全局转账根中继回每个链。

为防止重复,Hub 合约必须强制每个链的转账树只被纳入全局树一次。有了这个保证,全局树中的索引就可以用作提款 Nova 证明中的 index,从而防止双重提款。在提款时将全局转账树视为本地转账树,从而实现跨链燃烧和铸造。

global_tree\ global_tree2591×998 164 KB

标题链接 演示与实现

:link:演示: https://zerc20-demo.vercel.app/

:link:代码: https://github.com/kbizikav/zERC20

IVC 使用 PSE 的 Sonobe

标题链接 Gas 成本与 ZKP 基准测试

(在配备 M4 芯片的 MacBook Pro 上测量 ZKP)

操作 Gas / 时间
zERC20 转账 47,834 gas (标准 ERC-20 transfer:~35,016 gas)
批量提款合约执行 913,922 gas
单次提款合约执行 302,033 gas
单次提款证明生成 ~105 ms
根转换 IVC 步骤 ~242 ms / 步骤
根转换决策者生成 ~32.0 s
批量提款 IVC 步骤 ~142 ms / 步骤
批量提款决策者生成 ~30.1 s

标题链接 相关工作

Tornado cash

EIP-7503: zk-Wormholes

EIP-1724: zkERC20

erc721-extension-for-zk-snarks(隐身地址)

我之前关于根转换 Nova 的工作

confidential-wrapped-ethereum

removing-pairing-bulletproofs

ERC8065: ZWToken

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

0 条评论

请先 登录 后评论
以太坊中文
以太坊中文
以太坊中文, 用中文传播以太坊的最新进展