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 提款时,他们向 验证者 合约提交一个零知识证明,证明:
burnAddress 进行了 zERC20 转账;并且burnAddress 的 secret。验证者铸造相同数量的 zERC20 代币给 B。为了防止双重提款,合约会追踪每个目的地的累计提款金额,并且只允许铸造已接收与已提款之间的差额。
最初的 zk-Wormhole 提案针对的是 ETH。由于 ETH 转账到常规地址和燃烧地址在协议层面无法区分,它提供了非常强大的隐私。
相比之下,zERC20 是一种 ERC-20 代币。转账到未使用的地址有时可以与普通转账区分开来。然而,随着 账户抽象 (AA) 的普及,这一差距预计会缩小。
即便如此,将 zk-Wormholes 应用于 ERC-20 仍具有以下几个优势:
简而言之,zERC20 的功能类似于 Tornado Cash,但不同之处在于无需特殊的存款功能或用户界面——仅使用标准的 ERC-20 转账操作即可进行私密交易。
poseidon(a, b): Fr × Fr -> Fr 表示 2 输入 1 输出的 Poseidon 哈希。merkle_proof.get_root(i, leaf_hash) 表示使用该证明计算第 i 个 leaf_hash 的 Merkle 根。提款 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),并将其与已证明的 transferRoot 和 index 一起作为根转换 Nova 的公共输入。如果证明通过验证,它会存储 newTransferRoot 和 newIndex。这些稍后将用作提款证明的公共输入。
我们使用 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:用于生成燃烧地址的 secretvalue:转账到燃烧地址的金额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 的累计提款金额。
提款流程如下:
recipient = trim246(keccak256(recipient_chain_id, recipient_address, tweak)) 的组成部分作为参数,并验证 recipient_chain_id == block.chainid。proof.recipient 与步骤 1 中的 recipient 匹配。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,从而防止双重提款。在提款时将全局转账树视为本地转账树,从而实现跨链燃烧和铸造。
演示: https://zerc20-demo.vercel.app/
代码: https://github.com/kbizikav/zERC20
IVC 使用 PSE 的 Sonobe。
(在配备 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 |
erc721-extension-for-zk-snarks(隐身地址)
- 原文链接: ethereum-magicians.org/t...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!