V 神:使用 ZK-SNARKs 等技术改善 CEX 资产证明
原文作者:vitalik buterin,由 DeFi 之道翻译编辑。
特别感谢 Balaji Srinivasan,以及 Coinbase、Kraken 和 Binance 工作人员的讨论。
每当一家大型中心化交易所暴雷时,一个普遍的问题就是我们是否可以使用密码学技术来解决这个问题。交易所可以创建密码学证明,证明它们在链上持有的资金足以支付它们对用户的债务,而不是仅仅依赖政府许可、审计师和审查公司治理和运营交易所的个人背景等“法定”方法。
更雄心勃勃的是,交易所可以建立一个系统,在未经存款人同意的情况下,它根本无法提取存款人的资金。潜在地,我们可以探索“不作恶”(don't be evil)有抱负的好人 CEX 和“无法作恶”(can't be evil),但目前效率低下、泄露隐私的链上 DEX 之间的整个光谱。这篇文章将探讨使交易所向无需信任更近一步或两步的尝试的历史,这些技术的局限性,以及一些依赖 ZK-SNARKs 和其他先进技术的更新和更强大的想法。
余额表和默克尔树(Merkle trees):老派的偿付能力证明
交易所最早尝试以密码学方式证明他们没有欺骗用户的尝试可以追溯到很久以前。 2011 年,当时最大的比特币交易所 MtGox 通过发送一笔交易,将 424242 个比特币转移到预先宣布的地址,证明他们拥有这笔资金。2013年,人们开始讨论如何解决问题的另一面: 证明客户存款的总规模。如果你证明客户的存款等于 X(“负债证明”),并证明拥有 X 个币的私钥(“资产证明”),那么你就有了偿付能力证明:你已经证明交易所有偿还所有存款人的资金。
证明存款的最简单方法是简单地发布一个(用户名,余额)对列表。每个用户都可以检查他们的余额是否包含在列表中,任何人都可以检查完整列表以查看(i)每个余额都是非负的,以及(ii)总金额是索赔金额。当然,这破坏了隐私,所以我们可以稍微改变一下方案:发布一个 (hash(username, salt), balance) 对列表,并私下向每个用户发送他们的 salt
值。但即使这样也泄露了余额,它泄露了余额变化的模式。保护隐私的愿望将我们带到了下一个发明:默克尔树( Merkle tree)技术。
绿色:查理的节点。 蓝色:查理将收到的节点作为他证明的一部分。 黄色:根节点,公开给大家看。
默克尔树( Merkle tree)技术包括将客户余额表放入默克尔和树(Merkle sum tree)中。 在默克尔和树中,每个节点都是一个 (balance, hash) 对。 底层叶节点代表个人客户的余额和加盐的用户名哈希。 在每个上层节点中,余额是下面两个余额的总和,而哈希是下面两个节点的哈希。默克尔和证明与默克尔证明一样,是树的一个“分支”,由从叶到根的路径上的姐妹节点组成。
交易所将向每个用户发送其余额的默克尔和证明。 然后,用户将得到一个保证,他们的余额是正确包括在总额的一部分。可以在这里找到一个简单的示例代码实现。
# The function for computing a parent node given two child nodes
def combine_tree_nodes(L, R):
L_hash, L_balance = L
R_hash, R_balance = R
assert L_balance >= 0 and R_balance >= 0
new_node_hash = hash(
L_hash + L_balance.to_bytes(32, 'big') +
R_hash + R_balance.to_bytes(32, 'big')
)
return (new_node_hash, L_balance + R_balance)
# Builds a full Merkle tree. Stored in flattened form where
# node i is the parent of nodes 2i and 2i+1
def build_merkle_sum_tree(user_table: "List[(username, salt, balance)]"):
tree_size = get_next_power_of_2(len(user_table))
tree = (
[None] * tree_size +
[userdata_to_leaf(*user) for user in user_table] +
[EMPTY_LEAF for _ in range(tree_size - len(user_table))]
)
for i in range(tree_size - 1, 0, -1):
tree[i] = combine_tree_nodes(tree[i*2], tree[i*2+1])
return tree
# Root of a tree is stored at index 1 in the flattened form
def get_root(tree):
return tree[1]
# Gets a proof for a node at a particular index
def get_proof(tree, index):
branch_length = log2(len(tree)) - 1
# ^ = bitwise xor, x ^ 1 = sister node of x
index_in_tree = index + len(tree) // 2
return [tree[(index_in_tree // 2**i) ^ 1] for i in range(branch_length)]
# Verifies a proof (duh)
def verify_proof(username, salt, balance, index, user_table_size, root, proof):
leaf = userdata_to_leaf(username, salt, balance)
branch_length = log2(get_next_power_of_2(user_table_size)) - 1
for i in range(branch_length):
if index & (2**i):
leaf = combine_tree_nodes(proof[i], leaf)
else:
leaf = combine_tree_nodes(leaf, proof[i])
return leaf == root
这种设计中的隐私泄漏比完全公开的列表要低得多,并且可以通过每次发布根(root)时重新洗牌来进一步减少,但仍然存在一些隐私泄漏:Charlie 得知某人有 164 ETH 的余额 ,有两个用户的余额加起来达到 70 ETH,等等。控制许多帐户的攻击者仍然可能了解有关交易所用户的大量信息。
该方案的一个重要微妙之处在于可能出现负余额:如果一家交易所有 1390 ETH的客户余额,但只有 890 ETH 的储备金,其试图通过在树的某个位置的假账户下添加 -500 ETH余额来弥补差额,那该怎么办? 事实证明,这种可能性并没有破坏该方案,尽管这就是我们特别需要默克尔和(Merkle sum tree)树而不是常规默克尔树(Merkle tree)的原因。假设 Henry 是交易所控制的假账户,交易所在那里放了-500 ETH:
Greta 的证明验证将会失败:交易所必须给 Henry 的 -500 ETH 节点,她会认为该节点无效而拒绝。 Eve 和 Fred 的证明验证也会失败,因为 Henry 上面的中间节点总共有 -230 ETH,所以也是无效的!为了逃脱盗窃的惩罚,交易所必须希望整个树的右半部分都没有人检查他们的余额证明。
如果只以实现负债证明为目标,那么默克尔树技术基本上与负债证明方案一样好,但其隐私属性仍不理想。你可以用更聪明的方式使用默克尔树,比如把每个 satoshi 或wei 做成一个单独的子叶,但最终通过更现代的技术,甚至会有更好的方法来做到这一点。
使用 ZK-SNARKs 改善隐私和稳健性
ZK-SNARKs 是一项强大的技术。 ZK-SNARKs 之于密码学可能就像变压器之于 AI:一种通用技术,它是如此强大,以至于它将完全碾压一大堆应用特定技术,以解决几十年前开发的一大堆问题。因此,我们可以使用 ZK-SNARKs 来极大地简化和改进负债证明协议中的隐私。
我们能做的最简单的事情就是将所有用户的存款放入一棵默克尔树(或者,更简单的,一个 KZG 承诺),并使用一个 ZK-SNARK 来证明树中的所有余额都是非负的,并且加起来是某个声称的值。如果我们为隐私添加一层哈希,则提供给每个用户的默克尔分支(或 KZG 证明)将不会透露任何其他用户的余额。
使用 KZG 承诺是避免隐私泄露的一种方法,因为不需要提供“姐妹节点”作为证明,并且可以使用简单的 ZK-SNARK 来证明余额之和并且每个余额都是非负的。
我们可以用一个专用的 ZK-SNARK 来证明上述 KZG 中余额的和以及非负性。这里有一个简单的例子,我们引入一个辅助多项式 I(x),它“建立了每个余额的位”(为了举例,我们假设余额低于 2^15)并且每第 16 个位置跟踪一个带有偏移量的累计,只有当实际的总和与声明的总和相匹配时,它的总和才为零。 如果 z 是单位的 128 阶根,我们可以证明以下方程:
I(x)有效设置的第一个值为 0 0 0 0 0 0 0 0 0 0 1 2 5 10 20 -165 0 0 0 0 0 0 0 0 0 1 3 6 12 25 50 -300 …
请参阅我的 ZK-SNARKs 帖子,以进一步解释如何将此类等式转换为多项式检查,然后再转换为一个 ZK-SNARK。这不是最佳协议,但它确实表明如今这些类型的密码学证明并不是那么可怕!
只需几个额外的方程,像这样的约束系统就可以适应更复杂的设置。例如,在杠杆交易系统中,个人用户的负余额是可以接受的,但前提是他们有足够的其他资产,以一定的担保保证金覆盖资金。SNARK 可以用来证明这一更复杂的约束,让用户放心,交易所不会因为秘密地让其他用户不受规则约束而拿他们的资金冒险。
从长远来看,这种 ZK 负债证明或许不仅可用于交易所的客户存款,还可以用于更广泛的借贷。任何人借出一笔贷款都会将记录放入一个多项式或包含该贷款的树中,并且该结构的根将在链上发布。这将使任何寻求贷款的人都可以向贷方证明他们尚未获得太多其他贷款。最终,法律创新甚至可以使以这种方式承诺的贷款比没有承诺的贷款具有更高的优先级。这将我们引向与“去中心化社会:寻找 Web3 的灵魂”论文中讨论的一个想法完全相同的方向:通过某种形式的“灵魂绑定代币”,在链上产生负面声誉或负担的一般概念。
资产证明
资产证明的最简单版本,就是我们上面看到的协议:为了证明你持有 X 个币,你只需在事先约定的时间移动 X 个币,或者在数据字段包含“这些资金属于币安”字样的交易中移动 X 币。为了避免支付交易费用,你可以改为签署链下消息(比特币和以太坊都有链下签名消息的标准)。
这种简单的资产证明技术存在两个实际问题:
冷钱包处理
抵押品两用
出于安全原因,大多数交易所将大部分客户资金保存在“冷钱包”中:在离线计算机上,交易需要手动签署并转移到互联网上。麻烦是常见的:我过去用来存放个人资金的冷钱包设置需要一台永久离线的电脑生成一个包含签名交易的二维码,然后我用手机扫描。而对交易所来说,则要更为复杂,并且通常涉及多个设备之间的多方计算。鉴于这种设置,即使是制作一条额外的消息来证明对地址的控制也是一项昂贵的操作!
交易所可以采取多种途径:
保留一些公开长期使用地址。交易所会生成一些地址,对每个地址发布一次证明以证明所有权,然后重复使用这些地址。这是迄今为止最简单的选择,尽管它确实在如何保护安全和隐私方面增加了一些限制。
有很多地址,随机证明几个。交易所将有很多地址,甚至可能每个地址只使用一次,并在单笔交易后退出。在这种情况下,交易所可能有一个协议,有时会随机选择一些地址,并且必须“打开”以证明所有权。一些交易所已经与审计员一起做这样的事情,但原则上这种技术可以变成一个完全自动化的程序。
更复杂的 ZKP 选项。例如,交易所可以将其所有地址设置为 1-of-2 多重签名,其中每个地址的密钥都是不同的,另一个是存储在一些复杂但非常安全的地方,例如一个 12-of-16 多重签名钱包。为了保护隐私并避免泄露其整个地址集,交易所甚至可以在区块链上运行零知识证明,证明链上所有具有这种格式的地址的总余额。
另一个主要问题是防止抵押品的双重使用。在彼此之间来回运送抵押品以进行准备金证明,是交易所可以轻松做到的事情,并且可以让他们假装有偿付能力,而实际上却没有。理想情况下,偿付能力的证明应该是实时完成的,每个区块之后都有一个更新的证明。如果这是不切实际的,退而求其次的办法就是在不同的交易所之间按固定的时间表进行协调。例如。在每周二 UTC 时间 14:00 探明储备金情况。
最后一个问题是:你能在法币上进行资产证明吗?交易所不仅持有加密货币,它们还在银行系统内持有法定货币。在这方面,答案是肯定的,但这样的程序将不可避免地依赖于“法定”信任模型:银行本身可以证明余额,审计师可以证明资产负债表等。鉴于法币不可通过密码学验证,这是在该框架内所能做到的最好的方法,但仍然值得一做。
另一种方法是将一个运营交易所并处理资产支持稳定币的实体(例如 USDC)和另一个在加密货币和传统银行系统之间处理现金进出过程的实体(USDC本身)完全分离开来。因为 USDC 的“负债”只是链上的 ERC20 代币,负债证明是“免费的”,只需要资产证明。
Plasma 和 validiums:我们可以让 CEX 成为非托管的吗?
假设我们想走得更远:我们不想仅仅证明交易所有资金来偿还用户。 相反,我们希望防止交易所完全窃取用户的资金。
这方面的第一个主要尝试是 Plasma,这是一种在 2017 年和 2018 年在以太坊研究圈流行的扩容解决方案。Plasma 的工作原理是将余额分成一组单独的“币”,其中每个币都被分配一个索引,并存在于 Plasma 区块的默克尔树中的特定位置。要进行有效的代币转移,需要将交易放入树的正确位置(根被发布到链上)。
简化版 Plasma 示意图。代币存放在一个智能合约中,该合约在提现时强制执行 Plasma 协议的规则。
OmiseGo 试图基于该协议创建一个去中心化交易所,但从那时起他们就转向了其他想法——就此而言,Plasma Group 本身也是如此,它现在转变成了 optimistic EVM rollup 项目 Optimism。
不值得将 2018 年设想的 Plasma 的技术局限性(例如证明币碎片整理)看作是关于整个概念的某种道德故事,这是不值得的。自 2018 年 Plasma 讨论达到顶峰以来,ZK-SNARKs 在与扩容相关的用例中变得更加可行,正如我们上面所说,ZK-SNARKs 改变了一切。
Plasma 思想的更现代版本是 Starkware 提出的 validium:基本上与 ZK-rollup 相同,只是数据是链外保存的。这种结构可以用在很多用例中,可以想象任何中心化服务器需要运行一些代码并证明它正在正确执行代码的情况。在 validium 中,运营商无法窃取资金,但根据实施的细节,如果运营商消失,一些用户资金可能会卡住。
这一切都非常好: CEX 与 DEX 并不是二元对立的,事实证明,有各种各样的选择,包括各种形式的混合中心化,你可以获得一些好处(比如效率),但仍然有很多密码学护栏,来防止中心化运营商免于从事大多数形式的滥用行为。
但我们有必要从这个设计空间的右半部分入手:处理用户错误。截至目前,最重要的错误类型是:如果用户忘记了密码,丢失了设备,被黑客攻击,或者无法访问自己的帐户,那该怎么办?
交易所可以解决这个问题:首先是电子邮件恢复,如果连这都失败了,可以通过 KYC 进行更复杂的恢复。但为了能够解决这些问题,交易所需要真正控制这些币。为了有能力以正当的理由收回用户账户的资金,交易所需要有能力以不正当的理由窃取用户账户的资金。这是一个不可避免的权衡。
理想的长期解决方案是依靠自我保管,并借助诸如多重签名和社交恢复钱包等技术来帮助用户处理紧急情况。但在短期内,有两种明显的替代方案,它们的成本和收益明显不同:
总结: 交易所更好的未来
短期内,交易所分为两个明确的“类别”:托管交易所和非托管交易所。如今,后一类只是像 Uniswap 这样的 DEX,在未来我们可能还会看到受密码学“约束”的 CEX,其中用户资金保存在类似于 validium 智能合约的东西中。我们可能还会看到半托管交易所,我们信任它们的地方在于法定货币,而不是加密货币。
这两种类型的交易所将继续存在,而提高托管交易所安全性最简单的向后兼容方法,是增加储备金证明,这包括资产证明和负债证明的结合。为两者制定良好的协议存在技术挑战,但我们可以而且应该尽可能地在两者上取得进展,并尽可能开源软件和流程,以便所有交易所都能受益。
从长远来看,我希望我们越来越接近于所有交易所都是非托管的,至少在 crypto 方面是这样。钱包恢复将会存在,而且可能需要为处理小额金额的新用户提供高度中心化的恢复选项,以及由于法律原因需要这种安排的机构,但这可以在钱包层而不是在交易所内部完成。magic.link 与 Polymarket 等平台交互的方式就是这种方法的一个例子。在法定货币方面,传统银行系统和加密货币生态系统之间的流动可以通过 USDC 等资产支持的稳定币原生进/出流程来完成。但是,我们还需要一段时间才能完全到达那里。