# 核心存款电路

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

### 存款 <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`根据公众输入证明是否有效。

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs-zh.tornadoeth.cash/tornado-cash-classic/dian-lu/he-xin-cun-kuan-dian-lu.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
