介绍
随着 zkevms 的商品化,出现了一些有趣的机会,可以在保持 EVM 兼容性的同时提供隐私智能合约基础设施。开发人员可以编写 Solidity 代码,并使用 Solidity 编译器或一些后处理工具进行编译。从而创建隐私智能合约。
私有全局状态和隐私之间存在重要的权衡,这源于这样一种观念:为了能够证明,你需要知道你在证明什么。因此,你不可能拥有一个包含你不知道的全局公开状态的私有智能合约。由此可见,你不可能拥有一个包含全局私有状态的私有智能合约。例如, Uniswap就无法实现,因为证明者需要知道两个池子的余额才能证明交换已正确完成。更多内容: 为什么你无法使用零知识证明 (ZKP) 构建私有Uniswap - #24 by bowaggoner
所以,在IO出现之前,我们所熟知和喜爱的一些事物都无法以隐私的方式实现,这就是IO如此重要的原因。它让我们能够基于完全相同的信任假设,构建一个完全隐私的以太坊。
无论如何,这篇文章是关于如何向 reth pstore 和 pload 添加两个操作码,将其编译成 zkevm 并拥有具有私有用户状态但不具有私有全局状态的私有智能合约。
如何
某些合约调用是私有的。我们利用现有的 zkevm 代码来证明这些调用已正确执行,但不会泄露任何有关合约实际执行内容的信息。除了满足某些要求外,例如,需要从另一个合约中批准使用一定数量的代币,但不能透露代币持有者的身份。我们实现了两个新的OP码 pstore 和 pload,它们与 sload 和 sstore 类似,只是值是私有的。
工具箱
我们将 zkevm 作为我们的工具链。我们不会对 zkevm 本身进行任何更改。我们将其视为一个黑盒。相反,我们将对 reth 进行更改。我们为 reth 添加两棵新树:私有存储树 (PST) 和私有无效树 (PNT)。
PST 和 PNT 中的每个叶子节点都会在每次更新时发布。因此,任何人都可以证明自己是任何叶子节点的成员。但这些叶子节点包含的值只有创建它们的用户知道。
上传
pload 是我们添加的 evm 操作码。它类似于 sload。当 sload 在 zkevm 中执行时,zkzkevm 会生成一个 Merkle 证明,证明某个值位于树中的某个位置。
类似地,对于 pload,我们对树中该叶子的成员资格进行了证明,但我们也证明了该叶子尚未被无效。
假设我们要上传一个值 x。那么我们基本上要做两个 Merkle 证明
- 在 PST 中证明 x
- 证明 x.nullifier 不在 PNT 中
只有知道该叶子秘密值的用户才能计算其无效器,并且只有这样的用户才能证明它没有被无效。
注意:sload 隐含了非包含证明,因为它使用的是有序默克尔树。我们不能对 pload 和 pstore 使用有序默克尔树,因此我们需要某种编码来确保指定的叶子节点未被创建。这种编码可以是 hash (合约,槽位,值,无效符)
注意:我认为,如果你加载一个没有任何内容的地址,得到的也是 0x0,这也需要考虑。可能需要想办法在 zkzkevm 中处理这个问题,以便相同的 devex 存在。但很难证明存储槽未被填满。
存储
pstore 的作用与 sstore 相同,但工作原理略有不同。
在 zkevm 中,每次执行 sstore 操作时,实际上都会执行两个 Merkle 证明。第一个证明证明叶子节点的当前值为 x,第二个 Merkle 证明计算 Merkle 根,并将 x 的值替换为 y。因此,你可以将第一个证明视为获取树中所有叶子节点的汇总证明,而第二个证明则视为仅将单个叶子节点 (x) 替换为 y。
所以 sstore
- 证明值 x 在树中
- 用 y 替换它
pstore 可以做同样的事情,但略有不同
- 它通过获取 x.nullifier 并将其添加到 nullifier 树来删除 x。
- 它通过将 y 添加到 PST 来用 x 替换 x。
Solidity
Solidity 编译为 evm 操作码。
假设我们有以下智能合约
def transfer(sender, reciver, amount) private:bal[sender]= bal[sender] - amountbal[reciver] = bal[reciver] + amount# this is not adding to the users balance directly. Instead it is kind of input out put thing where the user needs to get the received funds to add to their total balance. This nuance is encapsulated in a receiver address abstraction for now. But needs more work to figure out what is needed on zkzkevm side.return(1)Solidity 编译器(某些后处理器的编译器)会发现这一点,并将字节码中的所有 sloads/sstore 替换为 ploads/pstores。它只会对带有 private 修饰符或标签的函数执行此操作。
具有一些私有分支和一些公共分支的呼叫链
可以将其视为 aztec connect 的更可编程版本。假设我们有一个私人钱包,也可以让该钱包调用Uniswap。这可以实现,但我们必须小心处理 message.sender、tx.origin、nonce、gas_price、gas_limit 和其他元数据泄漏。我们可以通过几种方法来实现这一点。
- 为每个调用创建代理合约,然后在另一个 tx 中或在当前 tx 调用堆栈的末尾重新平衡。
- 使用全局代理合约
注意:tx.origin 可能需要在 reth 变更中进行清理。不过它可能只是一个打包工具,所以我觉得还好。
权衡
这一切似乎有点复杂,只是为了实现 aztec 连接。但其强大之处在于能够重用我们目前拥有的大部分基础设施,从而支持更强大的应用程序。
卡特尔合同
我们讨论了一下私有全局状态,以及为什么无法实现Uniswap 之类的功能。但是假设我想为我和我的朋友们创建一个智能合约。我希望将源代码公开,但允许我和我的朋友们执行。所以这也是可能的,我们只需要放宽智能合约的数据可用性保证,这样智能合约代码就不需要公开了。
卡特尔内部在数据可用性保障方面存在一些细微差别。我想我们可以实施某种强制日志记录,将所有数据更新加密并发布,以便只有卡特尔成员才能看到。
结论
这个想法似乎会在两个方面立即发挥作用
- 创建一个私有Rollup ,其中有一个巨型服务器,用于生成所有用户向该服务器提供的数据证明,但其他所有人的数据不予提供
- 私人Rollup,用户可以提供一些证明,以便他们的存储访问权限对怪物服务器隐藏。
看起来很有用,而且很容易实现。不需要 zk 知识,以 zk 作为商品。
待办事项
我们需要更多地思考
- 如果我们要默认将 EOA 设为私有,似乎可以通过一些无效器技巧来实现,比如让它们签署“nullifier”,然后随机字符串成为它们的无效器,或者像 nullifier_0 = hash (sign(“nullifier”) , 0 ) nullifier_1 = hash(sign(“nullifier”,1)) 等等。但要做到这一点,我们必须编译所有 erc20 合约以使用 erc20 的 pstore 和 pload。这似乎可能会破坏其他功能。但手动 EOA 隐私似乎不包括状态变化,因为大多数人关心的是 erc20 而不是ETH。
- 在移动设备上对一些宽松的事物进行 zkevm 证明是否可行?一些 ploads
- 如果您要存储的值是动态生成的,那么最好有一个无效器,怪物服务器可以使用它来为您存储该叶子,而不是由于竞争条件而制作完整的叶子。
- 如何提供我的地址,以便我可以私下接收资金,而无需将所有收据连接在一起
- 我该如何使用日志或其他机制来了解我是否收到了款项?可能很简单,比如返回一个包含某种加密“标记”的日志。




