4.2 知识论证:验话之外还要验人 本节摘要:"某陈述为真"与"证明者知道它为什么为真"是两类断言,后者需要知识论证(proof of knowledge)与抽取器这套严格装置。本节给出抽取器的构造逻辑、论证与证明的信任差别,并用一段 Chaum-Pedersen 协议的可运行实现收尾。承接 4.1 的承诺容器,通往 4.3 的多项式承诺。 从"这话是真的"到"我知道为什么" 考虑同一句话的两种证明语境。语境一:某图存在哈密顿回路——陈述是关于对象的,证明者是否真的见过那条回路,验证者并不关心。语境二:某数字签名有效——签名有效这件事本身就蕴含签名人掌握私钥,验证者真正在意的是证明者脑袋里有没有那个秘密。
本节摘要:"某陈述为真"与"证明者知道它为什么为真"是两类断言,后者需要知识论证(proof of knowledge)与抽取器这套严格装置。本节给出抽取器的构造逻辑、论证与证明的信任差别,并用一段 Chaum-Pedersen 协议的可运行实现收尾。承接 4.1 的承诺容器,通往 4.3 的多项式承诺。
考虑同一句话的两种证明语境。语境一:某图存在哈密顿回路——陈述是关于对象的,证明者是否真的见过那条回路,验证者并不关心。语境二:某数字签名有效——签名有效这件事本身就蕴含签名人掌握私钥,验证者真正在意的是证明者脑袋里有没有那个秘密。第二类断言就是知识论证的对象:被证明的不是"存在 w 使陈述成立",而是"证明者掌握 w"。
"掌握"怎么定义?说出口就不算数了(谁知道你是不是背的答案),数学给的答案漂亮且实用:如果存在一台抽取器(extractor),只要与任何能通过验证的证明者互动,就能以压倒性概率把 w 完整榨出来,那么这个证明者就是"掌握"了 w。这个定义的锋利之处在于它不关心主观状态——你可以把 w 藏在芯片里、藏在零知识电路的中间值里,只要协议结束时抽取器能拿到 w,你就算"知道"。
抽取器听起来玄,构造常常朴素得惊人。1.3 节那段"同一承诺、两份应答、相减出秘密"的演算就是最典型的抽取器:让作弊者把同一个承诺用两次( rewind,倒带回放),两份应答线性相减,盲化量抵消,秘密现形。这类"特殊可靠性 + 回放"的构造是 sigma 协议族的标配,也是所有 Schnorr 型签名安全证明的地基——你未来在任何协议文档里看到 knowledge error(知识误差)这个词,它衡量的就是抽取器平均需要回放多少次才能得手。
读协议文档时会遇到 argument(论证)与 proof(证明)的措辞差别,这不是文风偏好而是安全等级声明:proof 的可靠性是信息论级的——作弊者哪怕算力无限也骗不过;argument 的可靠性是计算性的——作弊只在多项式算力内不可行。现代简洁证明系统几乎全是 argument:把验证成本压到对数级这个目标,代价就是放弃对无限算力对手的防线。工程上的对应动作很明确:安全声明里写 argument 的,核查它依赖的假设(离散对数、Knowledge of Exponent 等)与参数档位;写 proof 的,检查它是否为此付出了验证成本或证明尺寸的代价。
另一组对偶概念也在此收拢:4.2 的抽取器与 1.3 的模拟器是一枚硬币的两面——模拟器证明协议"没多给"(零知识性),抽取器证明协议"没少要"(知识性)。一份合格的 ZK 协议安全声明,必须同时供奉这两台机器:缺了模拟器,证明可能泄露;缺了抽取器,验证可能被骗。
看一个除 Schnorr 之外最常用的知识论证:Chaum-Pedersen 协议(也叫 DLEQ,离散对数相等证明)。场景:证明者声称知道同一个指数 x,使得 y1 = g^x 且 y2 = h^x——两个不同底上的公开值出自同一秘密。这在门限签名(验证份额出自同一私钥)、可验证加密、匿名凭据里天天见面。协议仍是三拍,验证等式从一条变两条:
# Chaum-Pedersen(DLEQ)玩具实现:证明两个公开值出自同一秘密指数 import hashlib, secrets p = 101 g, h = 2, 3 # 两个底(真实协议中 h 来自不同群生成) x = 41 y1, y2 = pow(g, x, p), pow(h, x, p) # 公开:两个"同源"值 def dleq_prove(): r = secrets.randbelow(p - 1) a1, a2 = pow(g, r, p), pow(h, r, p) # 承诺:两个底各封一次 c = int.from_bytes(hashlib.sha256(f"{a1},{a2}".encode()).digest(), "big") % (p - 1) s = (r + c * x) % (p - 1) # 应答(指数域的模是 p-1) return a1, a2, c, s def dleq_verify(a1, a2, c, s) -> bool: ok1 = pow(g, s, p) == a1 * pow(y1, c, p) % p # g^s ?= a1 * y1^c ok2 = pow(h, s, p) == a2 * pow(y2, c, p) % p # h^s ?= a2 * y2^c return ok1 and ok2 proof = dleq_prove() print("DLEQ 验证:", dleq_verify(*proof)) # True # 换个实验:若 x1 ≠ x2(两个值不同源),诚实执行协议必然验证失败 x2_bad = 37 y2_bad = pow(h, x2_bad, p) # 伪造另一个"公开值" y2 = y2_bad # 验证者拿到的是不同源对 a1, a2, c, s = dleq_prove() # 证明者仍用真 x 做 x1 的应答 print("不同源对无法通过:", dleq_verify(a1, a2, c, s)) # False(概率上几乎必败) # 抽取器视角:同一 a1, a2 拿到两份不同挑战的应答,可解出 x(回放抽取)
这段演算里藏着 DLEQ 的全部要义:两条验证等式把 g 与 h 两个底"并联"起来,作弊者除非恰好知道 x,否则无法让两条同时成立;而回放抽取器对两份应答做差,就能把 x 榨出来——与 Schnorr 的抽取完全同构。把 Schnorr、DLEQ 这类骨架收进工具箱,你会发现大量"新协议"都是它们的排列组合。
误读一:把知识论证当零知识证明的代名词。知识性回答"你有没有",零知识性回答"漏没漏",两个维度相互独立——一个协议可以只有知识性没有零知识性(如普通签名验证),也可以反之。误读二:以为"通过验证的次数多"等于"掌握知识"。回放抽取器的存在恰恰说明:通过多次验证反而给抽取器送弹药,承诺复用的自证其罪机制在知识论证层依然是铁律。
到目前为止,知识还被表示为指数、图、路径这类离散对象。下一节把"知识"整体翻译成多项式——这一步跨越之后,几百字节的证明才开始成为可能。
**问:抽取器实际部署在系统里吗?**不部署。抽取器是安全证明里的假想机器——它的存在性被数学证明,用来定义"知道";真实系统里没有任何组件去运行它。这层理解能避免一类经典误会:以为安全审计要去"找到抽取器",其实审计确认的是"该协议满足存在这种抽取器的定理条件"。
**问:知识论证与签名验证是什么关系?**签名验证就是一次非交互的知识论证:签名本身是"我掌握私钥"的证明,任何人可用公钥复核。差别在签名把这份论证固化成了可转交凭证(1.1 节的分野表),而交互式知识论证的说服力留在对话现场。读协议时看到"proof of possession of secret key",按知识论证的框架分析即可。
**问:能不能只有抽取器论证、不做零知识?**完全可以,而且大量生产协议正是如此——普通签名、身份识别协议都有知识性而毫无零知识性。两台机器各管一个方向:要不要零知识,取决于应用里"证明行为本身是否泄露敏感信息",是业务决定,不是密码学决定。