# 核心存款电路

核心存款电路是大多数用户交互的，证明用户已经创建了代表存入某些相应资产面额的承诺，他们尚未提取该资产，并且他们知道在生成初始承诺时提供的秘密。

### 存款 <a href="#making-a-deposit" id="making-a-deposit"></a>

向 Tornado.cash 存款是一个非常简单的操作，实际上不涉及任何 ZK 证明。至少现在还不需要。要存款，您需要调用[Tornado 合约](https://github.com/tornadocashdev/tornado-core/blob/master/contracts/Tornado.sol)`deposit`实例的方法，提供 Pedersen 承诺以及您要存入的资产面额。此承诺被插入到专门的 Merkle 树中，其中 Merkle 树的结构与与 BN128 椭圆曲线顺序中的素数相关联的椭圆曲线对齐，并且使用 MiMC 哈希计算树的标签。

#### 承诺计划 <a href="#commitment-scheme" id="commitment-scheme"></a>

在密码学中，当你做出“承诺”时，你所做的就是获取一个秘密值（通常很大且随机），并通过一些加密函数（例如哈希函数）运行它，然后披露结果。稍后，当您需要履行承诺时，您要证明您知道原始秘密值。

#### 佩德森哈希 <a href="#pedersen-hash" id="pedersen-hash"></a>

Pedersen 哈希是一种非常专业的哈希函数，特别适合用于利用零知识证明电路的应用程序。其他哈希函数（如 SHA-256）被设计为具有一些特性，例如即使输入稍有不同，也会产生非常不同的输出（雪崩效应），而 Pedersen 哈希则优先考虑在零知识电路中极其高效地计算哈希的能力。

使用 Pedersen 对消息进行哈希处理会将消息的位压缩到一条椭圆曲线上的一个点，该曲线称为 Baby Jubjub。Baby Jubjub 的阶数与 BN128 椭圆曲线相同，该曲线由以太坊网络上的预编译操作支持，这些操作是在 EIP-196 中添加的。这意味着使用 Baby Jubjub 曲线的操作（例如 Pedersen 哈希处理）非常节省 gas。

当您计算消息的 Pedersen 哈希值时，沿其椭圆曲线的结果点非常容易验证，但无法反转回原始消息。

#### 龙卷风承诺 <a href="#tornado-commitment" id="tornado-commitment"></a>

要生成 Tornado.cash 存款承诺，您首先需要生成两个较大的随机整数，每个整数长度为 31 个字节。第一个值是一个无效符，您稍后将披露该值以提取存款，第二个值是一个秘密，用于保护您的存款和取款之间的机密关系。

您的存款票据的原像就是这两个值的串联 ( `nullifier`+ `secret`)，从而产生一条长度为 62 字节的消息。此消息经过 Pedersen 哈希处理，从而产生一个输出，该输出表示编码为 32 字节大端整数的 Baby Jubjub 椭圆曲线的元素。

如果你想以代码形式查看这一点，你可以参考[tornado-cli 存款功能](https://github.com/tornadocashdev/tornado-cli/blob/master/cli.js#L53-L112)。

#### MiMC Merkle 树 <a href="#mimc-merkle-tree" id="mimc-merkle-tree"></a>

Tornado[合约](https://github.com/tornadocashdev/tornado-core/blob/master/contracts/Tornado.sol)是一种特殊的 Merkle 树，它使用 MiMC 哈希标记其节点。

对于不熟悉 Merkle 树的人来说，它们是二叉树，其中每个非叶节点都标有其子节点标签的哈希值，而叶节点则标有其数据的哈希值。通常，Merkle 树使用单向加密哈希函数（如 SHA-2），但在这种情况下，我们使用 MiMC，它具有一些有用的属性。

MiMC 的一个有用特性是它非常适合在素数域上进行操作，这对我们很重要，因为零知识证明从根本上来说基于素数域，而 Pedersen 哈希是素数域内的点，该域由 Baby Jubjub 椭圆曲线定义 - 而该曲线又在以太坊原生支持的 BN128 曲线的阶内。由于零知识证明在操作上成本高昂，并且以太坊交易中的每个操作都有相应的 gas 成本，因此我们设计的特定类型的操作需要尽可能节省 gas。

MiMC 的其他特别有用的特性是它不可并行化，难以计算但易于验证。这些特性通过使计算在 Merkle 树中具有冲突路径的伪造“承诺”变得不可行，从而增加了合约的安全性。

#### 零节点 <a href="#zero-nodes" id="zero-nodes"></a>

在 Tornado Merkle 树初始化期间，会预先分配一条跨越树高的唯一路径，从标签为 的“零叶”节点开始`keccak256("tornado") % FIELD_SIZE`。然后，每个后续朝向根的非叶节点都会被标记，就好像树的整个底部都由同一个叶节点填充一样。

这些“零节点”的目的是确保 Merkle 树内的所有路径都是无效的，直到它们以有效承诺终止。

#### 插入承诺 <a href="#inserting-a-commitment" id="inserting-a-commitment"></a>

当你将承诺插入到 Tornado 合约的 merkle 树中时，你将用一个新叶子替换“零叶子”，该新叶子的标签是你的 Pedersen 承诺的 MiMC 哈希，然后遍历树，并根据新叶子在下面引入的标签更新，用新标签更新每个后续父节点。

承诺在树中从左到右插入，每两个承诺插入填充一棵“子树”。每次插入都会增加树的“索引”，以确定下一个承诺是插入到其 merkle 路径条目的左侧还是右侧。

一旦您的存款更新了树，最顶层节点的标签将成为树的新“根”，并添加到包含最后 100 个根的标签的滚动历史记录中，以供以后用于处理提款交易。

Tornado.cash 存款合约部署了 20 个“级别”，每个级别将潜在叶子的数量增加 2 的幂。这意味着合约的默克尔树最多支持 2^20 个叶子，允许在需要更换合约之前向合约中存入最多 1,048,576 笔存款。

层级数量看似较少的原因是，每笔存款都必须对树进行与层级数量相同的更新。层级越多的树，每笔存款需要的 gas 就越多，提取票据时需要的证明大小也就越大。

### 提款 <a href="#making-a-withdrawal" id="making-a-withdrawal"></a>

存款后，您现在拥有一组真实声明，您可以据此生成证明。一般而言，零知识证明锚定在证明者和验证者都知道的某些值上，这些值与只有证明者知道的一组值之间存在关系。电路验证者可以确认证明者使用了已知的值，并且他们计算的证明满足电路施加的约束。

#### 提款证明的输入 <a href="#inputs-to-a-withdrawal-proof" id="inputs-to-a-withdrawal-proof"></a>

对于 Tornado.cash 存款，证明者（提交提款交易的人）和验证者（存款合约的提款方法）都知道最近的 merkle 根。证明者还提供一组其他公共输入，用于生成证明。

**提款证明的公共输入总量如下：**

1. 最近的 Merkle 根
2. 来自其存款承诺的无效化组件的 Pedersen 哈希
3. 提款接收者的地址
4. 他们选择的中继者的地址（或他们自己的地址）
5. 他们向中继者支付的费用（或零）
6. 他们向中继者支付的退款（或零）

**提款证明的额外私人输入包括：**

1. 存款承诺中的无效部分
2. 存款承诺的秘密部分
3. Merkle 树的根节点和叶节点之间的路径中存在的节点标签集合
4. &#x20;一个值数组`0/1`，指示每个指定路径元素是位于其父节点的左侧还是右侧

#### 已证实的主张 <a href="#proven-claims" id="proven-claims"></a>

当我们构建承诺并将其插入 Merkle 树时，我们很容易忽略我们创造的巧妙的新知识。您可能倾向于认为，要进行提款，我们只需证明我们知道 Pedersen 承诺的组成部分，而 Merkle 树只是存储这些承诺哈希的一种有效方式。

这种构造的特殊之处在于，它不仅使我们能够证明我们知道已存承诺的组成部分，而且使我们能够简单地证明我们知道**树中承诺的路径**，以及**如何**从承诺原像开始到达那里。

如果我们只是证明我们知道已存哈希的原像，那么我们就有可能泄露哪个承诺是我们的。相反，我们不会泄露承诺原像，而是简单地证明我们知道树中承诺的原像。在电路协议的提款端，哪个承诺是我们的完全无法区分。

#### 计算见证 <a href="#computing-the-witness" id="computing-the-witness"></a>

**无效哈希校验**

为了计算提款证明的见证，我们的电路首先获取私人存款承诺输入 ( `nullifier`+ `secret`)，并将它们通过电路组件运行，该组件同时计算完整承诺消息的 Pedersen 哈希和无效符的 Pedersen 哈希。然后，电路将生成的无效符哈希与您作​​为公共输入提供的哈希进行比较，并断言它们相等。

**这证明您公开提供的无效哈希实际上是您原始承诺的组成部分。**

**Merkle 树检查**

接下来，电路将其计算的承诺哈希、您公开指定的 merkle 根以及您私下指定的路径元素和左/右选择器作为检查您的 merkle 树路径声明的组件的输入。

Merkle Tree Checker 从路径底部开始，将您的承诺哈希和您提议路径的第一个元素输入到 Muxer 中。Muxer 接受第三个输入，即您提供的左/右方向的一个元素。Muxer 组件使用这些方向来通知 MiMC 哈希组件其输入的顺序。如果提供的方向为 0，则提供的路径元素在左侧，而您的承诺哈希在右侧。如果方向为 1，则顺序相反。

MiMC 哈希器输出生成的哈希值，Merkle Tree 检查器继续进行下一级。它会重复上一个过程，只不过这一次它使用的是最后一级的哈希值，而不是您的承诺哈希值。它会继续遍历建议路径的每一级，直到得到最终的哈希值输出。

Merkle 树检查器将其计算出的哈希值与您提供的公共 Merkle 根输入进行比较，并断言它们相等。

**这证明你的承诺存在于指定的 merkle 根下的某条路径内。**

**额外提款参数检查**

在完成之前，电路会获取剩余的四个公共输入，并将它们平方为一个公共输出。虽然这不是绝对必要的，但它会在您的证明中创建一组约束，以确保在处理您的提款交易之前，您的交易参数不会被篡改。如果任何这些参数发生变化，您的证明将不再有效。

#### 计算证明 <a href="#computing-the-proof" id="computing-the-proof"></a>

现在我们有了证明的见证人，我们将这些见证的状态值输入到与提款电路相对应的 R1CS 中，并运行证明器。证明器产生两个证明工件。第一个是证明本身（根据我们使用的 SNARK 协议），第二个是与该证明相对应的一组公共输入和输出。

#### 完成提款交易 <a href="#completing-a-withdrawal-transaction" id="completing-a-withdrawal-transaction"></a>

现在已生成提款证明，您可以将该证明及其公共输入提供给`withdraw`存款合约的方法。此方法验证：

1. 指定的中继费不超过提取资产的面额价值
2. 提供的无效哈希之前未被使用过
3. 提供的 merkle 根是已知的，使用 100 个根的历史记录
4. 提供的证明有效

作为存款合约的依赖项部署的工件之一是 Solidity 合约，该合约使用取款电路的证明密钥作为输入生成。此验证器合约是一个经过优化的证明验证器，具有单个公共视图函数，它接受证明和六个公共输入的数组作为`uint256`值。

此函数返回`TRUE`根据公众输入证明是否有效。

如果满足上述先决条件，则将提供的无效符哈希插入到已使用无效符的集合中，然后根据指定的费用参数在接收者和中继者之间分配押金的价值。
