
系列: Zcash from First Principles 。文章 3 · 哈希与承诺 读者对象: 新手。我们会基于文章 1(有限域)和文章 2(椭圆曲线),但核心直觉本身也可以独立理解。 你将收获: 清楚理解哈希函数、“隐藏”和“绑定”真正意味着什么,以及 Zcash 如何构建支撑每一笔私密支付的 note commitments。
在文章 0中,我们描述了一个“神奇的密封信封”:一种你可以钉在公共公告板上的东西,它能证明一个信封确实存在,同时隐藏里面的内容,而且你之后永远无法把它调包。我们当时承诺会解释,这样的东西为什么可能存在。本文就是这个解释。我们需要两种材料:哈希函数和承诺。
想象一下,你预测了一场选举的结果,并且想在事后证明,你早就预测对了。你不能直接公开你的预测(那会影响他人,或者招来别人指责你后来改过)。但你也不能把它完全保密(那样你之后就无法证明任何事)。
你真正需要的是一种方法,能够现在就在公开场合锁定一个值,并满足以下条件:
这种“现在锁定、以后公开、不能撒谎”的装置,叫作承诺,它在 Zcash 中无处不在。一个 note 的数值和所有者,会在 note 创建的那一刻被锁进一个承诺里。要构建承诺,我们首先需要它的核心工具:哈希函数。
哈希函数可以接收任意数据输入,无论是一个字母还是整座图书馆,然后把它压缩成一个短小、固定长度的字符串,称为摘要或哈希值。你可以把它理解为:数据的指纹。

一个好的密码学指纹具有四个性质。先把它们当作直觉来理解,而不是方程:
| 性质 | 通俗含义 | 为什么重要 |
|---|---|---|
| 确定性 | 相同输入总会得到相同指纹 | 你可以随时重新核对一个指纹 |
| 正向计算很快 | 计算指纹非常迅速 | 便于在各处实际使用 |
| 单向性(抗原像) | 给定一个指纹,你找不到生成它的输入 | 隐藏原始数据 |
| 抗碰撞性 | 你找不到两个不同输入拥有相同指纹 | 没人能伪造匹配结果 |
还有一种额外的行为,会让这种指纹看起来几乎像魔法一样:
只要对输入做最微小的改动,指纹就会彻底改变,看起来和原来的毫无相似之处。下面是两个真实的 SHA-256 指纹,对应的消息只差一个字符:
H("Pay Bob 5 ZEC") = 6e2dc1a954c70cc865f18ea8cb70b7b56eeaf6ca42b380824a55d65dc342f34b
H("Pay Bob 6 ZEC") = 76abc346d8d3053f76a9ae18b617af71f02729a73ec6a51732d2d94934e4217f
在 64 个十六进制字符中,有 59 个不同。 仅仅一个字符变化,输出的就是一个完全无关的新指纹。这就是为什么你无法把输入“微调”到某个目标指纹:这里没有任何“更接近了 / 更远了”的信号可供追踪。
这里有一个很诱人但其实有缺陷的想法:如果要对一个秘密值 v 做承诺,那就直接公布它的指纹 H(v)。
这样确实能很好地绑定你(你之后不能再声称另一个不同的 v,因为那需要找到碰撞)。但它无法隐藏。 如果可能值的集合很小,攻击者只需要把每个候选值都算出指纹再进行比较。比如你承诺的是“yes”还是“no”?对方把这两个值都哈希一遍,立刻就知道你选了哪个。刚才还是朋友的确定性,现在却在泄露秘密。
修复方法只有一个词:随机性。
承诺,就是把你的值和一个全新的随机数混合后得到的指纹:
commitment = H(v, r),其中r是一个秘密的随机“致盲”值。
这样一来,同一个 v 每次都会产生看起来不同的承诺,因为 r 每次都不同。我们想要的两个性质,这时终于都成立了:

要在之后打开(公开)这个承诺,你只需公布 v 和 r;任何人都可以重新计算 H(v, r) 并检查它是否匹配。你已经被锁定了。这就是文章 0 里那个神奇的密封信封,真正落地后的样子。
请永远记住这两个结论: 绑定性来自哈希函数的抗碰撞性;隐藏性来自随机致盲因子
r。
有两种常见做法,而 Zcash 两种都在使用。
| 基于哈希的承诺 | Pedersen commitment(来自文章 2) | |
|---|---|---|
| 公式 | H(v, r) | v.G + r.H(曲线上的点) |
| 隐藏性来自 | 随机数 r | 随机数 r |
| 绑定性来自 | 抗碰撞性 | 椭圆曲线陷门(ECDLP) |
| 特殊能力 | 简单且快速 | 这些承诺可以相加(同态) |
最后这一行,就是为什么 Pedersen commitments 在 Zcash 中如此重要。因为 commit(v_1) + commit(v_2) 仍然是一个有效的 commit(v_1 + v_2),协议之后就可以通过把承诺相加,来证明流入的钱等于流出的钱,同时又不泄露任何一个具体金额。这个事实我们会先存起来,留到文章 6 再用。
这里有一个大多数入门介绍都会忽略的洞见,而它恰恰是值得强调的“数学遇上工程”的关键点。
SHA-256 作为日常计算中的指纹工具非常优秀。但 Zcash 不只是要计算哈希;它还必须在零知识证明内部,证明某个哈希是被正确计算出来的(文章 5 会解释原因)。问题在于:零知识证明所使用的语言是有限域算术(文章 1),而 SHA-256 的内部则由大量位运算构成(移位、AND、XOR)。要把这些位运算全部表达成域上的算术,代价极其高昂,会让证明变得又大又慢。
因此,Zcash 的密码学家设计了那些内部结构本来就是域算术的哈希函数,这样它们证明起来就便宜得多:

正是这一条工程压力——“它必须易于证明”——促使 Zcash 发明并采用了专门的哈希函数,而不是在所有地方都直接使用 SHA-256。
Zcash 在不同设计中使用过不同的哈希,每一种都是按用途挑选的:
| 设计 | 使用的哈希 | 用在何处 |
|---|---|---|
| Sprout(最早期) | SHA-256 | Note commitments 和树 |
| Sapling | Pedersen hashes,以及 BLAKE2 | Pedersen 用于 note commitments 和 Merkle 树;BLAKE2 用于密钥派生和 nullifiers |
| Orchard(当前) | Sinsemilla,以及 Poseidon | Sinsemilla 用于 note commitments 和 Merkle 树;Poseidon 用于 nullifier,全部都为算术电路而设计 |
需要重点记住的名字是 Pedersen 和 Sinsemilla(基于曲线点构造的承诺式哈希,因此继承了“可相加”的超能力,而且证明成本低)以及 Poseidon(一种专为零知识电路打造的域算术哈希)。当文章 0 说一个 note 的内容被封进了一个承诺里时,真正执行这项封装工作的,正是这些机制。
因此,文章 0 中留下的那个开放问题——“一个密封信封怎么能既隐藏其内容,又无法被伪造?”——现在已经闭环了:隐藏性来自随机致盲因子,绑定性来自抗碰撞性或曲线陷门。
为了讲清楚,我们做了一些简化。真实的承诺方案会精确定义 v 和 r 如何编码、使用哪些生成元;“隐藏性”和“绑定性”也各自还有不同版本(完美型与计算型),并且有精确的安全定义;另外我们也没有展开 Pedersen、Sinsemilla 或 Poseidon 的内部细节。但这些都不改变核心直觉:承诺就是“指纹 + 随机性”,它能在当下隐藏内容,并且永久绑定结果。等到协议层面的文章真正需要这些细节时,我们会再次明确指出并讲回来。
H(v) 只能绑定,但不能隐藏。加入随机致盲因子后,即 H(v, r),问题就解决了:隐藏性来自 r,绑定性来自抗碰撞性。| 术语 | 通俗含义 |
|---|---|
| Hash function | 把任意数据压缩成一个固定长度的短指纹(摘要) |
| Digest | 哈希函数输出的指纹 |
| Preimage resistance | 无法从摘要反推出原始输入(单向) |
| Collision resistance | 无法找到两个输入拥有相同摘要 |
| Avalanche effect | 输入发生极小变化,摘要就会彻底改变 |
| Commitment | 现在锁定一个值,以后公开,而且不能对它撒谎 |
Blinding factor (r) | 让承诺具备隐藏性的全新随机数 |
| ZK-friendly hash | 由域算术构成、因此证明成本很低的哈希 |
为什么不直接加密这个值,而要对它做承诺? 加密解决的是保密,之后可以解密。承诺解决的是绑定:保证你之后不能改口。两者解决的是不同问题。
如果承诺隐藏了数值,别人怎么检查规则有没有被遵守? 这正是零知识证明(文章 5)的作用:它能证明隐藏的数值满足规则,同时又不泄露该数值。
既然 Zcash 在某些地方避免使用 SHA-256,是不是说明 SHA-256 坏掉了? 不是。SHA-256 没问题,Zcash 现在也仍然在使用它。只是它在电路内部被证明时成本很高,这正是 ZK-friendly 哈希存在的原因。
随机数 r 是从哪里来的,谁来保存它?
它会在 note 创建时全新生成,并由该 note 的所有者知晓。它也是让每个 note 都独一无二且保持私密的重要组成部分。
你把自己的选举预测写成 H(v, r) 做出承诺并公开发布。一个朋友坚持认为你应该只发布 H(v),这样更简单。如果结果只有两种可能,用一句话说明为什么这是个糟糕主意。(答案见下方。)
如果只有两种结果,你的朋友完全可以自己计算 H("win") 和 H("lose"),再与你公开的摘要对比,从而立刻知道你的预测。裸哈希只能绑定,不能隐藏;真正阻止这种猜测比对攻击的,是随机数 r。
文章 4 · Merkle 树: 现在我们已经有了数以百万计不断堆积的 commitments。文章 4 会展示 Zcash 如何把它们组织成一棵树,用一个很小的根指纹代表整个历史,以及你如何在不暴露具体是哪一个 note 的前提下,证明你的 note 位于这棵树中。这才是文章 0 中那个“公共公告板”的真实形态。
属于 Zcash from First Principles 系列的一部分,面向 ZecHub。采用 CC BY-SA 4.0 许可。