零知识证明这个词第一次砸到我面前,是在一次需求评审上:业务方要验证用户“符合某个条件”,但用户死活不愿意把原始数据交出来。当时会议室里有人提了加密,有人提了脱敏,最后落到一个更根本的问题上——能不能只让对方相信“结论成立”,而不暴露“结论为什么成立”。这就是零知识证明要解决的事。它不是一个具体的加密算法,而是一类协议的设计思想:证明者向验证者证明自己知道某个秘密,或者某个命题为真,但整个过程中除了“命题为真”这个结论,验证者拿不到任何额外信息。它适合谁?适合那些手里有敏感数据、又必须向外界自证清白的人;也适合做隐私身份、链下计算验证、数据协作的开发者。下面我按自己实际做项目的顺序,从原理拆到代码,再聊选型和踩过的坑。
1. 先把“零知识”拆成三件事:证明、验证、不泄露
1.1 一个环形洞穴的思想实验
我习惯用环形洞穴来解释零知识证明,因为它把“交互”和“随机挑战”这两个关键点都装进去了。想象一个环形的洞穴,入口只有一个,洞穴深处有一扇门,门需要一句咒语才能打开。证明者说自己知道咒语,但不想把咒语告诉验证者。怎么办?让证明者先走进洞穴,随机选择从左通道或右通道走到门边;验证者在洞口外,看不到证明者选了哪条路,然后随机喊“从左出来”或“从右出来”。如果证明者真知道咒语,他总能打开门,从被喊的那一侧出来;如果他不知道,每轮只有一半概率蒙对。重复二十轮,蒙对的概率就是百万分之一。
这个实验里,验证者得到的全部信息是“每一轮证明者都从指定侧出来了”,他并不知道证明者一开始走的是哪边,也不知道咒语是什么。零知识证明的“零知识”就体现在这里:验证者被说服了,但没有拿到任何可用于复现或泄露秘密的信息。生活里类似的场景很多,比如你证明自己会开车,不需要把驾驶技巧的全部细节讲出来,上车跑一圈就行;但零知识证明更严格,它要求连“你具体怎么开”的过程都不留痕迹。
1.2 完备性、可靠性、零知识性到底在说什么
任何零知识证明协议,至少要满足三个性质,我在评审方案时也是按这三条逐项问。第一是完备性:如果命题是真的,证明者老老实实按协议走,验证者必须接受。这条最基础,相当于“真话一定能说通”。第二是可靠性:如果命题是假的,证明者不管怎么作弊,验证者接受的概率必须低到可以忽略。注意,这里的“低”不是“零”,而是可忽略,比如 2 的负 128 次方。第三是零知识性:验证者从交互记录里学不到除命题为真之外的任何东西。形式上,存在一个模拟器,它不需要知道秘密,也能生成与真实交互不可区分的记录。
很多方案还会加一条知识可靠性:证明者不仅能让验证者相信“存在某个解”,而且证明者自己确实“知道”那个解。这条在简洁非交互证明里经常被强调,因为有些协议只能证明“命题为真”,不能证明“证明者掌握 witness”。我在做身份凭证类需求时特别看重这条,否则可能出现“证明是别人代做的,但凭证被转手”的问题。
1.3 它和普通加密、签名的边界在哪里
刚接触的人容易把零知识证明和加密混在一起。加密解决的是“内容别被看见”,签名解决的是“内容没被篡改、来源可确认”,零知识证明解决的是“我让你相信一个结论,但不告诉你支撑结论的数据”。举个例子:加密像把信锁进保险箱,签名像在信封上盖火漆印,零知识证明像你站在门口告诉保安“我有进入权限”,保安信了,但你的门禁卡号、权限等级、有效期他都没看到。
这个边界很重要,因为零知识证明不是万能药。它不能替代加密,因为证明过程本身可能泄露元信息;它也不能替代访问控制,因为验证者信的是数学关系,不是你的身份系统。实际项目里,零知识证明通常和承诺、签名、哈希一起用:先用承诺把数据锁住,再用证明说明承诺里的数据满足某个条件,最后用签名绑定证明者的身份。单独上一个零知识证明库,往往解决不了完整的业务问题。
2. 从交互式协议到非交互证明:核心机制怎么跑起来
2.1 承诺、挑战、响应:Sigma协议的三拍节奏
最经典的一类零知识证明叫 Sigma 协议,结构非常固定,就三拍:承诺、挑战、响应。以“证明自己知道离散对数”为例,公开参数是一个循环群、生成元和公开值 y,证明者知道 x,使得 y 等于生成元的 x 次方。第一拍,证明者随机选一个 r,计算承诺 t,把 t 发给验证者;第二拍,验证者随机选一个挑战 c,发回去;第三拍,证明者计算响应 s,把 s 发回去。验证者最后检查一个等式,等式成立就接受。
这三拍的精髓在于“顺序不能乱”。证明者先承诺,相当于把答案锁进盒子再把盒子交出去;验证者后给挑战,相当于随机抽题;证明者最后开盒,相当于按题作答。如果证明者先知道题再锁盒,他就可以针对题目编一个能过的答案,协议就废了。我见过有人为了省一次网络往返,把承诺和响应合并发送,结果整个协议的安全性直接归零,这个后面会细说。
2.2 为什么必须“先承诺后挑战”
先承诺后挑战,本质上是让证明者没有机会根据挑战来调整承诺。假设顺序反过来:验证者先给挑战 c,证明者再给承诺 t 和响应 s。证明者完全可以随便选一个 s,然后反推出一个 t,让等式成立,而他根本不需要知道秘密。这在数学上叫“模拟”,但模拟出来的记录和真实记录不可区分,验证者就无法分辨证明者是不是在作弊。
我在代码 review 时有个习惯:看到零知识证明的实现,先看随机数生成和挑战生成的位置。如果挑战是在承诺之前生成的,或者承诺和响应用了同一个随机数,基本可以直接打回。这个顺序不是形式主义,它是整个安全性证明的支点。你可以把它理解成考试:先发卷子再答题,和先知道答案再出卷子,难度完全不是一回事。
2.3 哈希挑战变换:把对话压成一条证明
交互式协议有个麻烦:验证者必须在线,而且每轮都要真随机。实际系统里,我们更希望证明者生成一条证明,贴到公告板上,任何人随时可验证。这就需要把“验证者的随机挑战”替换成“哈希函数的输出”。做法是把公开参数、承诺、命题上下文拼成一个字节串,做一次哈希,把哈希结果当作挑战。这样挑战看起来是随机的,但实际上由承诺和上下文唯一决定,证明者没法预先操纵。
这个变换叫哈希挑战变换,也有人叫非交互化变换。它把多轮交互压成一条证明,验证者只需要重算哈希、验一个等式。但这里有个坑:哈希输入必须把所有相关上下文都绑进去,包括命题、公开值、协议版本、甚至链 ID 或域名。少绑一个字段,就可能出现跨场景重放。比如同一个证明在 A 业务里有效,被搬到 B 业务里也能过,因为挑战没绑定业务标识。我在实际项目里会要求把“证明用途”写进哈希输入,这相当于给证明加了一个不可伪造的标签。
2.4 模拟器视角:零知识性不是“感觉安全”
零知识性的严格定义,是存在一个多项式时间的模拟器,它不需要知道 witness,就能生成一个与真实交互不可区分的记录。这句话听起来绕,但它的实际意义是:验证者即使把交互记录拿回去反复分析,也找不到任何关于 witness 的信息,因为模拟器在没有 witness 的情况下也能造出一样的记录。换句话说,记录本身不携带 witness 信息。
这个视角帮我排除过很多“伪零知识”方案。有些方案只是把敏感字段哈希了一下,就宣称零知识,但验证者可以通过穷举或关联分析还原出原始值,这就不满足零知识性。判断方法很简单:问一句“如果我是验证者,我能不能用这条证明去推断 witness 的任何一位?”如果答案是能,那它就不是零知识证明,只是某种承诺或脱敏。真正的零知识证明,安全性不依赖于验证者“不去分析”,而依赖于数学上的不可区分。
3. 技术路线选型:不是所有场景都适合上zk-SNARK
3.1 四类主流方案的能力边界
市面上的零知识证明方案很多,名字也花哨,但按能力边界大致可以分成四类。我在选型时不看宣传语,只看四个指标:证明大小、验证成本、是否需要可信设置、开发难度。下面这张表是我内部评审时常用的对照,具体参数会随实现变化,但量级关系基本稳定。
| 方案类别 | 证明大小 | 验证成本 | 可信设置 | 开发难度 | 典型适用场景 |
|---|---|---|---|---|---|
| 基于配对与多项式承诺的简洁证明 | 几百字节,常数级 | 很低,毫秒级 | 通常需要 | 高,需要电路设计 | 链上验证、凭证系统 |
| 透明设置的哈希型证明 | 几十 KB 到几百 KB | 中等,随规模增长 | 不需要 | 中高 | 抗信任假设、长期可验证 |
| 内积论证类方案 | 几百字节到几 KB | 对数或线性 | 不需要 | 中 | 范围证明、聚合证明 |
| 多方计算生成证明 | 较小 | 低 | 依赖参与方 | 高 | 特定隐私计算场景 |
这张表里没有“最好”的方案,只有“最合适”的方案。链上验证对证明大小和验证成本极度敏感,哪怕证明生成慢一点,也愿意选常数大小方案;离线审计场景更看重长期可验证性,不愿意引入一次性可信设置,那就倾向透明设置;移动端或嵌入式设备则要反过来,证明生成不能太重,宁可证明大一点。
3.2 可信设置与非可信设置的取舍
可信设置是很多人心里的疙瘩。简单说,这类方案需要一次性生成公共参数,生成过程中会产生一些“有毒废料”,如果这些废料没被销毁,持有者理论上可以伪造证明。实际项目里通常用多方计算仪式来分散风险,只要有一个参与方诚实地销毁了自己的份额,参数就是安全的。但“信任”这件事很难向普通用户解释,所以我在面向公众的场景里会非常谨慎地使用“需要可信设置”这个说法。
透明设置方案不需要这种一次性仪式,它只依赖哈希函数,公共参数可以由任何人用公开随机数生成。代价是证明更大、验证更贵。我的经验是:如果验证方是链上合约,且证明调用频繁,可信设置带来的性能优势很难被替代;如果验证方是审计机构或监管方,且系统生命周期很长,透明设置的“无信任假设”更省心。选型时不要只算技术账,还要算沟通账。
3.3 算术化:把业务逻辑翻译成电路
零知识证明本身只认数学关系,不认业务语言。要把“用户余额大于零”“身份证有效期在范围内”“模型输出等于某值”变成证明,中间必须做算术化。最常见的形式是 R1CS,每个约束长这样:A 向量点乘 witness,乘以 B 向量点乘 witness,等于 C 向量点乘 witness。加减乘都能表示,比较和范围检查则要拆成比特。
举个例子,证明 x 在 0 到 100 之间。你不能直接写一个“x < 100”的约束,因为电路里没有原生的比较指令。常见做法是把 x 拆成若干比特,约束每个比特只能是 0 或 1,即 b 乘以 b 减 1 等于 0;再用二进制权重把比特加回 x;最后约束 x 小于 100。这个过程中,比特位数、边界条件、符号处理都容易出错。我见过一个范围证明漏了最高位约束,导致用户可以声称一个负数在范围内,链上验证还通过了,因为电路根本没检查符号。
3.4 选型清单:按证明规模、验证成本、开发周期排序
我自己的选型顺序是这样的:先问证明在哪验证。链上验证优先看证明大小和验证 gas;服务端验证可以放宽到几十 KB;移动端验证则要避免复杂配对运算。再问能不能接受可信设置。面向公众、生命周期长的系统,优先透明设置;内部系统、性能敏感的场景,可以接受多方仪式。最后问团队熟悉什么。电路开发不是普通业务开发,学习曲线很陡,选一个团队能维护的方案,比选一个纸面参数最漂亮的方案更重要。
还有一条经验:不要一上来就上通用电路方案。很多需求用 Sigma 协议、范围证明、哈希树成员证明就能解决,代码量小、审计成本低、出错面窄。通用电路方案适合逻辑复杂、约束多的场景,但开发周期可能是前者的五到十倍。先做威胁模型,再看能不能用简单协议覆盖,覆盖不了再上电路。
4. 手写一个可验证的零知识证明Demo
4.1 环境与参数准备
下面这段代码只依赖 Python 标准库,目的是把协议跑通,不是生产实现。教学参数我故意选得很小:模数 23,子群阶 11,生成元 4。小参数方便手算和打印,但绝对不安全,生产环境必须用大素数群或椭圆曲线群,并且使用经过审计的库。我之所以坚持手写一遍,是因为很多坑只有自己实现过才会真正记住,比如随机数复用、挑战空间、哈希绑定。
先准备参数和基础函数。secrets模块用于生成密码学安全的随机数,不要用random。pow的三参数形式做模幂,Python 自带的大整数运算足够演示。
import hashlib import secrets # 教学参数,禁止用于生产 P = 23 # 模数 Q = 11 # 子群阶 G = 4 # 生成元 def keygen(): x = secrets.randbelow(Q - 1) + 1 y = pow(G, x, P) return x, y这段代码里,私钥 x 落在 1 到 Q-1 之间,公钥 y 是生成元的 x 次方。这里有一个细节:x 不能取 0,否则公钥恒为 1,证明就失去意义。实际系统里还要检查公钥是否在正确的子群内,防止小群攻击。教学代码省略了这些检查,但你在生产实现里必须补上。
4.2 交互式离散对数证明
交互式版本分三步。证明者先选随机 r,算承诺 t;验证者给挑战 c;证明者算响应 s,验证者验等式。代码很短,但每一步都有讲究。
def commit(): r = secrets.randbelow(Q) t = pow(G, r, P) return r, t def respond(r, x, c): return (r + c * x) % Q def verify(y, t, c, s): left = pow(G, s, P) right = (t * pow(y, c, P)) % P return left == right验证等式的原理是:左边是生成元的 s 次方,右边是承诺乘以公钥的 c 次方。把 s 展开,右边等于生成元的 r 加 c 乘 x 次方,正好等于左边。这个等式成立,说明证明者知道 x,因为只有知道 x 的人才能算出正确的 s。但验证者看到的只有 t、c、s,他不知道 r,也不知道 x,所以没有泄露秘密。
这里最容易犯的错是 r 复用。如果同一个 r 用在两个不同的挑战上,攻击者拿两组响应一减,就能解出 x。我在 code review 里看到过有人把 r 存在全局变量里“省性能”,这是典型的省小钱赔大钱。每次证明都必须重新生成 r,而且要用密码学安全的随机源。
4.3 非交互版本与挑战绑定
把交互式协议变成非交互,核心是把验证者的随机挑战换成哈希输出。哈希输入要包含公开参数、公钥、承诺和命题上下文。我通常还会加一个域分隔字符串,防止不同协议之间的哈希碰撞。
def challenge(y, t, statement): data = f"zk-demo-v1:{P}:{G}:{y}:{t}:{statement}".encode() return int(hashlib.sha256(data).hexdigest(), 16) % Q def nizk_prove(x, y, statement): r = secrets.randbelow(Q) t = pow(G, r, P) c = challenge(y, t, statement) s = (r + c * x) % Q return t, s def nizk_verify(y, statement, t, s): c = challenge(y, t, statement) return pow(G, s, P) == (t * pow(y, c, P)) % P这段代码里,statement是证明用途,比如“证明我是某个凭证的持有者”。把它绑进哈希,意味着同一个证明换一个用途就失效,攻击者不能把 A 场景的证明搬到 B 场景。这个细节在实际系统里非常关键,我见过因为没绑上下文导致证明被跨系统重放的案例,修复成本很高。
还有一个坑:挑战取模后的空间。教学参数 Q 只有 11,挑战空间太小,伪造成功率是十一分之一,多试几次就能蒙混过关。生产环境挑战至少 128 位,通常 256 位。不要因为哈希输出很长就直接用,取模之后的空间才是实际安全边界。
4.4 哈希树成员证明:隐藏“我是谁”
很多场景不需要通用电路,只需要证明“我在这棵树的某个叶子里”,但不想暴露具体是哪个叶子。哈希树成员证明就能做这件事。先把每个叶子做哈希,再两两合并成父节点,一直到根。证明者给出叶子、路径上的兄弟节点和方向,验证者从叶子一路算到根,看是否等于公开的根。
def h(data: bytes) -> bytes: return hashlib.sha256(data).digest() def build_tree(leaves): level = [h(x) for x in leaves] tree = [level] while len(level) > 1: nxt = [] for i in range(0, len(level), 2): left = level[i] right = level[i + 1] if i + 1 < len(level) else left nxt.append(h(left + right)) tree.append(nxt) level = nxt return tree def merkle_path(tree, index): path = [] for level in tree[:-1]: sibling = index ^ 1 if sibling < len(level): direction = "left" if sibling < index else "right" path.append((direction, level[sibling])) else: path.append(("self", level[index])) index //= 2 return path def verify_path(root, leaf, path): cur = h(leaf) for direction, node in path: if direction == "left": cur = h(node + cur) elif direction == "right": cur = h(cur + node) else: cur = h(cur + cur) return cur == root这段代码能证明成员关系,但它本身不是零知识的,因为路径会泄露叶子在树中的位置,叶子内容也可能被猜到。要变成零知识,通常要把叶子换成承诺,或者用零知识电路重新包装成员证明。我在实际项目里会先问一句:验证者到底需要知道“某个人在名单里”,还是需要知道“名单里某个人满足条件”?前者用承诺加路径就够,后者往往要上电路。
4.5 跑测试与性能观察
测试时我关注三件事:正确性、伪造成功率、耗时。正确性用固定向量跑通;伪造成功率用随机响应去撞验证等式;耗时用time.perf_counter粗略观察。教学参数下,一次证明和验证都在微秒到毫秒级,看不出真实性能。生产环境里,瓶颈通常在模幂运算和电路规模,而不是哈希。
| 观察项 | 教学参数表现 | 生产环境关注点 |
|---|---|---|
| 证明生成耗时 | 微秒级 | 电路规模、模幂次数、并行度 |
| 验证耗时 | 微秒级 | 配对运算次数、哈希次数 |
| 证明大小 | 几十字节 | 常数大小还是随规模增长 |
| 伪造成功率 | 约 1/Q | 挑战空间至少 128 位 |
我建议你在本地至少跑三组:正常证明、篡改承诺、篡改响应。正常证明必须过;篡改承诺或响应必须挂。如果篡改后还能过,说明验证等式写错了,或者挑战没绑进承诺。这个自测习惯能提前发现大部分低级错误。
5. 踩坑实录:那些让证明“看起来能过”的陷阱
5.1 随机数复用是头号杀手
随机数复用是零知识证明里最致命的错误,没有之一。还是用离散对数证明举例:同一个 r 生成承诺 t,面对两个不同挑战 c1 和 c2,得到两个响应 s1 和 s2。攻击者把两个响应相减,得到 s1 减 s2 等于 c1 减 c2 乘以 x,直接解出私钥 x。整个过程不需要破解哈希,也不需要攻破群,只要拿到两条用同一随机数的证明就行。
我在项目里见过两种复用:一种是开发者图省事,把 r 缓存在对象里,多次证明复用;另一种是随机数生成器 seeded 不当,不同进程生成相同序列。修复方法很直接:每次证明都调用密码学安全的随机源,不要缓存,不要用时间戳做种子。还有一条:如果协议需要多轮,每轮的随机数必须独立,不能从上一轮推导。
5.2 挑战空间太小等于没挑战
挑战空间决定了伪造成功率。如果挑战只有 8 位,伪造者随机猜一个响应,有 256 分之一概率通过;重复几百次就能蒙中。有人觉得“我可以多验几轮”,但多轮验证会带来交互成本和状态管理问题,不如直接把挑战空间做大。生产环境里,挑战至少 128 位,通常 256 位,取模后的空间也要足够大。
这里还有一个隐蔽问题:哈希输出取模后的分布。如果模数不是 2 的幂,取模会带来微小偏置,严格来说需要用拒绝采样或大整数映射。教学代码为了简洁直接取模,生产实现要用标准做法。安全参数不是拍脑袋定的,128 位是底线,256 位更稳妥。
5.3 电路约束漏写:证明对了,业务错了
通用电路方案最怕约束漏写。验证者看到证明通过了,以为业务逻辑也对了,其实电路只约束了一部分。比如证明“我知道一个数 x,使得公开值等于生成元的 x 次方”,但电路没约束 x 的范围,攻击者可以拿 x 加上子群阶来构造另一个 witness,证明照样过,但业务语义变了。再比如范围证明漏了符号位,负数也能通过“大于零”的检查。
我现在的做法是:电路写完后,先做一轮“反例测试”。故意构造边界值、负数、超大数、零点,看证明能不能过。能过就说明约束有漏洞。然后再做一轮“约束覆盖检查”,把业务规则逐条映射到约束,确保每条规则至少有一个约束对应。电路审计比普通代码审计更贵,因为错误往往不是崩溃,而是“静默通过”。
5.4 常见问题速查表
下面这张表是我自己整理的排查顺序,遇到证明异常时从前往后查,基本能覆盖八成问题。
| 现象 | 可能原因 | 排查方法 | 修复建议 |
|---|---|---|---|
| 真证明通不过 | 挑战没绑全上下文 | 打印哈希输入逐字段比对 | 把命题、公钥、承诺、版本号全绑进去 |
| 假证明也能过 | 验证等式写错或挑战空间太小 | 用随机响应撞验证 | 检查等式,挑战至少 128 位 |
| 同一证明可跨场景重放 | 哈希没绑业务标识 | 换一个 statement 重验 | 加入域分隔和用途字段 |
| 证明生成偶尔失败 | 随机数越界或模运算错误 | 检查 r 的范围和取模 | 用密码学安全随机源,范围对齐子群阶 |
| 验证耗时波动大 | 群运算未预计算 | 压测不同输入规模 | 预计算生成元幂表,或换更高效曲线 |
这张表不是万能的,但它能帮你快速定位“是协议问题还是实现问题”。我的经验是:先怀疑实现,再怀疑协议。大部分线上问题不是数学被攻破,而是随机数、绑定、范围这些工程细节出了错。
6. 应用场景与影响范围:它到底能落到哪里
6.1 身份与凭证:只回答“是/否”
零知识证明在身份场景里最自然的用法,是把“出示全部信息”变成“回答一个布尔问题”。比如验证者只想知道“这个人是否年满十八岁”,证明者不需要给出出生日期,只需要给出一个证明,说明自己持有的凭证里的出生日期满足某个范围。再比如会员资格、权限等级、白名单成员,都可以做成“只回答是/否”的凭证。
这类系统的关键不是证明本身,而是凭证的签发和撤销。凭证需要被签名,证明里要绑定签发者公钥和撤销状态,否则攻击者可以拿过期凭证生成证明。我在设计这类方案时,会把“凭证有效期”和“撤销根”作为公开输入绑进电路,验证者每次验证时检查最新状态。这样证明仍然零知识,但业务上不会出现过期凭证蒙混过关。
6.2 扩容与批量验证:把计算搬到链下
另一大类场景是扩容。思路很直接:把大量计算搬到链下,生成一个证明,链上只验证这个证明。验证成本从“随交易数量线性增长”变成“常数级”,这就是很多人说的批量验证。某类公链项目会用这种方式把成百上千笔状态转换压成一个证明,链上合约只验一次。
但这里有个现实问题:证明生成很贵。电路越大,生成时间越长,内存占用越高。我见过一个团队把几百兆的电路直接丢到普通服务器上跑,结果内存爆了。实际项目里要做电路拆分、并行生成、证明聚合,还要考虑证明生成失败时的回退策略。扩容不是“上个零知识证明就完了”,它是一整套工程体系。
6.3 数据协作与模型推理
数据协作场景里,零知识证明可以用来证明“我的数据满足某个统计性质”,而不交出原始数据。比如证明一组数据的平均值在某个区间,或者证明某个模型在私有数据集上的输出等于公开结果。这类应用听起来很美好,但边界要划清楚:零知识证明不加密数据本身,它只保证证明过程不泄露 witness。如果数据在传输或存储环节没加密,证明再漂亮也没用。
模型推理场景还要注意“证明的是什么”。你可以证明“输出等于模型对输入的计算结果”,但很难证明“模型是某个公开模型”,因为模型权重本身可能也是私有的。实际做法通常是把模型哈希或承诺作为公开输入,证明者证明自己用的模型承诺匹配,再证明计算结果正确。这套组合拳的复杂度不低,适合对信任要求极高的场景,不适合普通业务。
6.4 边界:延迟、证明大小与开发成本
零知识证明不是免费的。证明生成延迟可能从几百毫秒到几分钟不等,证明大小从几百字节到几百 KB,电路开发周期可能是普通业务的数倍。我在评估一个需求是否值得上零知识证明时,会问三个问题:验证方是否真的不需要原始数据?信任缺失是否严重到必须用数学证明?业务能否接受证明生成的延迟和成本?
如果三个答案都是“是”,再考虑技术选型。如果只是“不想让日志里出现明文”,那加密和脱敏更合适;如果只是“想让对方相信我们没作弊”,那审计和签名可能更便宜。零知识证明的价值在于“在不泄露秘密的前提下建立信任”,它的成本也在这里。想清楚这一点,就不会为了用而用。
我个人在实际项目里的体会是:先做威胁模型,再选协议,最后才写电路。威胁模型里把“证明者可能作弊”“验证者可能重放”“凭证可能过期”这些场景列清楚,比急着调库更重要。很多坑不是数学问题,而是边界没想明白。