本文假设您对椭圆曲线运算及哈希函数等有着基础的了解

简洁的 Schnorr 协议

Alice 拥有一个秘密数字,a,我们可以把这个数字想象成「私钥」,然后把它「映射」到椭圆曲线群上的一个点 a*G,简写为 aG。这个点我们把它当做「公钥」。

  • sk = a ( secret key = a )
  • PK = aG

a secret key  that corresponds to a public key .

请注意「映射」这个词,给任意一个有限域上的整数 r,我们就可以在循环群中找到一个对应的点  rG,或者用一个标量乘法来表示 r*G。但是反过来计算是很「困难」的,这是一个「密码学难题」—— 被称为离散对数难题。

取模之后 , 就很难知道原来的指数是多少了。 事实上,如果模取得相当大,从运算结果倒推指数运算就不可行了;现代密码学很大程度上就是基于这个问题的“困难”

也就是说,如果任意给一个椭圆曲线循环群上的点 R,那么到底是有限域中的哪一个整数对应 R,这个计算是很难的,如果有限域足够大,比如说 256bit 这么大,我们姑且可以认为这个反向计算是不可能做到的

Schnorr 协议充分利用了有限域和循环群之间单向映射,实现了最简单的零知识证明安全协议:Alice 向 Bob 证明她拥有 PK 对应的私钥 sk

  1. 第一步:为了保证零知识,Alice 需要先产生一个随机数 r,这个随机数的用途是用来保护私钥 无法被 Bob 抽取出来。这个随机数也需要映射到椭圆曲线群上即 rG。 ( 映射之后 , Bob 就不可能通过 rG 推算出 r )
  2. 第二步:Bob 要提供一个随机数进行挑战,我们把它称为  c
  3. 第三步:Alice 根据挑战数 c 计算  z = r + c * a (即sk),把 z 发给 Bob,Bob 在自己这边通过下式进行检验:
#![allow(unused)]
fn main() {
z*G ?= R + c*PK 
    ?= rG + c*(aG)
}

大家可以看到 Bob 在第三步「同态地」检验 z 的计算过程。如果这个式子成立,那么就能证明 Alice 确实有私钥 a。可是,这是为什么呢?

z 的计算和验证过程很有趣,有几个关键技巧:

  1. 首先 Bob 必须给出一个「随机」挑战数 ,然后 Bob 在椭圆曲线上同态地检查 z 。如果我们把挑战数    看成是一个未知数,那么 r+a*c=z 可以看成是一个一元一次方程,其中 r 与 a 是方程系数。请注意在 c 未知的前提下,如果 r + a*x = r' + a'*x 要成立,那么根据 Schwatz-Zippel 定理,极大概率上 r=r'a=a' 都成立。也就是说, Alice 在 c 未知的前提下,想找到另一对不同的 r',a' 来计算 z 骗过 Bob 是几乎不可能的。这个随机挑战数 c 实现了r 和 a 的限制。虽然 Bob 随机选了一个数,但是由于 Alice 事先不知道,所以 Alice 不得不使用私钥 a 来计算 z。这里的关键: c 必须是个随机数。
  2. Bob 验证是在椭圆曲线群上完成。Bob 不知道 r ,但是他知道 r  映射到曲线上的点 R ;Bob 也不知道 a,但是他知道 a 映射到曲线群上的点 PK,即 a*G。通过同态映射与Schwatz-Zippel 定理,Bob 可以校验 z 的计算过程是否正确,从而知道 Alice 确实是通过 r 和 a 计算得出的 z,但是又不暴露 r 与 a 的值。
  3. 还有,在协议第一步中产生的随机数 r 保证了 a 的保密性。因为任何一个秘密当和一个符合「一致性分布」的随机数相加之后的和仍然符合「一致性分布」。

看懂了这个图就看懂了 !!!!!

是 Sigma 零知识证明的一个特例

Schnorr 的非交互式版本

Schnorr 协议的非交互式版本可以避免 Prover 与 Verifier 的交互,但这要求 Prover 使用哈希函数,这样他就无法预测哈希函数的输出,非交互式版本的验证器实现非常简单,因为它不需要随机数生成器

(Making the protocol non-interactive)

首先定义: 即私钥 ; 是 Public key 即公钥 ;

  1. Prover 生成一个随机数 并创建一个承诺 , Prover 对 进行哈希处理以获得挑战值 ,
  2. Prover 创建对挑战的响应 , 然后将元组 (comm, s) 发送给验证者。

Verifier 自己计算 , 然后验证 :

如果 Verifier 自己验证这个等式相等, 则 Prover 就通过 这种方式隐藏了私钥 , 同时又能让对方确信自己真的有这个私钥 .

  1. The prover generates a random number r and creates a commitment com = gʳ. The prover hashes  gcom and y to get challenge cc = Hash(g, y, t).
  2. The prover creates a response to the challenge as s = r + c*x. The prover sends tuple (t, s) to the verifier.

The verifier now generates the same challenge c as Hash(g, y, t) and again checks if  equals yᶜ.tPython code demonstrating this protocol.

Schnorr 的问题

对不同的消息, 如果不幸选了相同的随机数 私钥就会泄露

如果 Alice 在两次交互过程中使用了同一个 K,那么 Bob 可以通过发送两个不同的 c 和 c' 来得到 s 和 s',然后通过下面的公式算出私钥 a

s  = (c +a*e)/k , 
s' = (c'+a*e)/k , 两式相减, 求出 k 

k = (c - c')/(s - s')
a = (k * s - c)/e

ECDSA

Bitcoin 和 ETH 都支持 ECDSA signature.

why need ECDSA?

除了显而易见的“我需要对一份文件/合同进行签名”,还有一个非常流行的应用场景:让我们以一个不想自己的数据被用户修改或者破坏的应用程序为例,比如一个只允许你载入官方地图和不可修改的模块的游戏,或者一部只允许你安装官方应用程序的手机或其它设备。

在这些案例当中,相关文件(应用程序、游戏地图、数据等)会用 ECDSA 进行签名,公钥会随应用程序/游戏/设备一起捆绑并用来验证签名来确保数据没有被修改,而私钥在本地一个私密的地方进行保存。由于你可以用公钥对签名进行验证,但是不能用它创建或者伪造新的签名,你可以无所顾忌地将公钥随应用程序/游戏/设备一起分发。

这与AES相比,区别是显而易见的。AES加密系统允许你对数据进行加密,但是你需要用密钥来解密,这就要求你将密钥与应用程序一起捆绑,破坏了对数据进行保护防止数据被用户修改的目的。

一个很好的例子就是PS3的控制台,它被大量的破解,所有的文件可以解密,所有的密钥可以从解密的文件当中抽取,但是为了能够在最新的固件上面运行程序,你还需要破解一个ECDSA的数字签名。

当你想要对一个文件进行签名的时候,你会用这个私钥 / 随机数 / 文件的哈希组成一个魔法数学方程,这将给出你的签名。签名本身将被分成两部分,称为 RS

  • 选择随机数 , 计算承诺 :
  • 挑战 : 取 的横坐标为 (先 mod , 再 mod )
  • 响应 :

为了验证签名的正确性,你只需要公钥(用私钥在曲线上面产生的点)并将公钥和签名的一部分 S 一起代入另外一个方程,如果这个签名是由私钥正确签名过的数字签名,那么它将给出签名的另外一部分 R

简单来说,一个数字签名包含两个数字,RS,然后你使用一个私钥来产生 RS ,如果将公钥和 S 代入被选定的魔法数学方程给出 , 且 的话,这个签名就是有效的。仅仅知道公钥是无法知道私钥或者创建出数字签名。

Algorithm

初始化: 椭圆曲线生成元为 ,标量域为 ,基域为

基域 理解为椭圆曲线点的横纵坐标的取值范围 标量域 即做倍点运算的标量的取值范围, 比如 里的 , 其不会超过椭圆曲线的阶

密钥生成: 私钥 和公钥

签名: 输入任意消息 , 计算

  • 选择随机数 , 计算承诺 :
  • 挑战 : 取 的横坐标为 (先 mod , 再 mod )
  • 响应 : ( k 增加了 ECDSA 的难度)

则签名为

的乘法逆元

我们是如何对一个文件或者一个信息进行签名的呢?

  1. 你需要知道签名本身是 40 字节,由各20字节的两个值来进行表示,第一个值叫作 ,第二个叫作
  2. 值对 放到一起就是你的 ECDSA 签名

验证 :

验证它,也非常的简单,你只需要 [公钥] 和导出这个公钥的曲线参数就可以了。你用以下方程来计算

Verifier :

  1. 输入消息 , 计算
  2. 校验
  3. 计算

的横坐标为 , 校验等式 : 如果相等, 则接受 , 否则拒绝

公式推导过程如下:

这里知道 还是可以推算私钥, 所以 EIP-32 要求 :

EdDSA

以太坊 BN256 曲线已经支持了 EdDSA

EdDSA 正是为了解决 Schnorr 签名私钥泄露的问题 : 他不是选择随机数, 而是计算随机数

初始化 : 椭圆曲线生成元为 , 阶为 密钥生成 : 私钥为 , 公钥为

签名: 消息为 ,计算随机数 **,计算承诺

计算挑战

计算响应

签名为(R,s)

验证: 重新计算挑战 ,然后校验

与 ECDSA 最大的区别在于 是算出来的, 没有使用随机数 这样产生的签名结果是确定性的,即对同一消息, 签名结果相同, 不会额外泄露信息

一般说来随机数是安全措施中重要的一种方法,但是随机数的产生也是安全隐患,著名的索尼公司产品 PS3 密钥泄露事件,就是随机数产生的问题导致的 (写死在了代码里, 晕)。

zk-SNARK

在聊 zk-SNARKs 之前, 首先来看 NARK(Non-interactive ARgument of Knowledge) :

  • C : 电路 Circuit
  • : 公开声明 public statement
  • : witness
  • 预处理(Preprocessing) 也称为 Setup, 它以电路的描述作为输入,然后输出这些公开参数,我们称之为 :
  • 表示公开的参数,供证明者使用。
  • 表示公开的参数,供验证者使用。

证明者和验证者各自会输入 :

  • prover takes the (public statement) & (public (circuit)params) & the Witness
  • Verifier takes & (public statement)

然后,证明者试图向验证者证明: It knows some such that

NARK Definition : A pre-processing NARK is a triple , where :

  • generate the Circuit’s as public params for P & V.
  • : proof
  • : or

所有算法和对手都可以访问 随机预言机 (random oracle)

zk-SNARKs 条件是苛刻的, 因为要让 Verifier 在如此短的时间内完成某些验证, 我们需要一些新的方法来去处理计算, 比如多项式承诺 (polynomial commitment)

(To be continued …)

Reference :

Vitalik ZK_Snark zk-learning Lectures 安比 zk-snarks https://vitalik.ca/general/2021/01/26/snarks.html Zero Knowledge Proofs with Sigma Protocols