本文深入探讨了Pinocchio协议的原理及其在Lambdaworks库中的实现,Pinocchio是一种SNARK协议,用于验证计算的正确性,同时保护隐私。文章详细介绍了将代码转换为QAP的过程,并解释了协议的工作原理以及实现安全所需的各种检查,此外还探讨了如何通过最少的额外工作将SNARK转换为零知识SNARK,文章还提供了相应的代码片段。
想象一下,你想进行一个复杂的计算,但你的计算机无法完成,或者你需要从一个你不信任的计算机那里获取结果。如何在不自己重做或理解复杂细节的情况下,确保它被正确完成?2013 年推出的 Pinocchio 使用 SNARK 提供了一个解决方案。这项技术使证明者能够简洁地证明其计算的正确性,并且能够验证它们,而无需透露细节。虽然 Pinocchio 本身已经进化,不再以其原始形式使用,但理解它有助于我们理解驱动当今区块链技术的 SNARK,包括 ZK Rollups,从而增强可扩展性和隐私。
所以,Pinocchio 是一个 SNARK 协议,但什么是 SNARK?SNARK 代表 Succinct, Non-Interactive Argument of Knowledge (简洁、非交互式知识论证)。简洁,因为我们将拥有易于验证的小型证明。非交互式,因为生成的证明可以用来 убедить 任意数量的验证者,而无需与证明者进行直接交互。知识论证,因为我们以非常高的概率知道证明者没有作弊。本质上,SNARK 协议为我们提供了一种将复杂计算“压缩”成小型、易于验证的证明的方法。
能够证明计算的有效性而无需提供其代码听起来很酷,但在现实世界中有哪些应用?它用在哪里?
一个典型的例子是 ZK Rollups。区块链是可验证的计算机;它们通过让每个节点重新执行每个交易并达成共识来实现这种可验证性。问题在于,最弱的设备成为了瓶颈。与 web2 中发生的情况相反,添加更多硬件并不能使它们更快:系统变得更加健壮和可靠,但最弱的设备仍然限制着它。使用 SNARK,我们可以用证明的验证来替换重新执行,这要快得多(提高了吞吐量)。此外,我们可以创建包含整个交易块的证明,从而实现有效的扩展。总而言之,我们可以将执行移至链下 rollup,并在链上验证其 доказательства,从而使系统能够扩展。
要能够使用任何 SNARK 协议,我们必须做的第一件事是找到一种有效且系统的方式来表示计算代码。这就是算术电路所做的:算术电路是一种计算模型,用于以结构化的方式表示算术运算。它提供了一种系统的方式来描述和计算复杂的数学函数。要了解有关算术电路的更多信息,你可以查看我们的文章 如何将代码转换为算术电路。
现在,如果证明者想要证明在给定特定输入的情况下,特定代码返回某些输出,她可以简单地将相应的算术电路发送给验证者,而无需任何其他协议。问题在于,这样的测试根本不简洁,实际上几乎就像完全发送输入和代码一样。这就是为什么,为了获得简洁的证明,我们将不得不将算术电路转换为我们所说的 R1CS,然后将获得的 R1CS 转换为 QAP。
下面我们将广泛解释 R1CS 和 QAP 是什么。请注意,伴随此解释可能会对理解有所帮助,相应的实现可以在 来自 Lambdaworks 库的 Pinocchio 中找到。
R1CS 代表 Rank-1 Constraint System (秩为 1 的约束系统)。它允许我们使用 矩阵 方程以结构化的方式表达电路变量之间的关系。更具体地说,给定一个具有有效解 $c$ 的算术电路,我们的目标是创建一个形式为 $A c \odot B c = C c$ 的方程组,其中 $A$、$B$ 和 $C$ 是矩阵:
为了充分理解什么是 R1CS 以及如何构建它们,我们建议阅读 这篇文章。不过,我们在此列出将算术电路转换为 R1CS 的步骤。
(如果你不知道 $\mathbb{F}_p^{n \times m}$ 是什么意思,请不要担心,你可以将其视为 $\mathbb{R}^{n \times m}$,因此 $A$ 只是一个数字矩阵)。
所以现在我们知道程序可以表示为算术电路,并进一步转换为 R1CS。但是,由于需要大量的操作,特别是对于复杂的计算,直接评估 R1CS 进行验证仍然不简洁。二次算术程序 (QAP) 通过提供更有效的表示来解决此问题。
QAP 将 R1CS 的约束编码为 多项式 集。这允许将多个约束批量处理成单个多项式方程。但是,为什么使用多项式会使证明简洁?这完全归功于被称为 Schwartz-Zippel 引理 的数学结果。要详细了解为什么这个引理使证明简洁以及我们如何将 R1CS 转换为 QAP,我们建议阅读 ZK RareSkills 书的这一章。我们的目标是能够测试 R1CS 解的有效性,检查某个多项式是否具有给定的属性。我们在此处留下带有符号的步骤,我们将在协议中使用的符号:
对于每个 $k \in {1, \dots, m}$,将 $(1, \dots, n)$ 与 $(a{1k}, \dots, a{nk})$ (A 的第 $k$ 列) 进行内插。我们将生成的多项式称为 $v_k(x)$。
类似地,$w_k(x)$ 和 $y_k(x)$ 分别内插 $B$ 和 $C$ 的列。
现在我们准备好理解协议。它从一次性设置开始,在该设置中生成两个密钥,用于证明和验证这些计算。执行计算的证明者使用她的密钥来创建一个证明,该证明的大小很小且恒定,与计算的复杂性无关。然后通过数学检查有效地验证此证明,以确保计算已正确完成。该系统不仅支持公共验证,允许任何拥有验证密钥的人检查证明,而且还可以扩展以提供保护隐私的零知识证明。
理解 Pinocchio 协议需要熟悉关键的数学概念,主要是椭圆曲线、有限域和群论。这些构成了 Pinocchio(以及一般的 SNARK 协议)中密码运算和安全证明的基础。有关椭圆曲线的详细探索,请参阅我们的 文章,我们在其中讨论了它们。有关诸如群和域之类的基本结构的入门知识,请参阅我们的 开发人员数学生存工具包。这些资源提供了理解 Pinocchio 复杂设计所需的背景知识。
在以下部分中,我们将介绍该协议,其中包含来自我们使用 Lambdaworks 库制作的 实现 的一些代码片段。
从 $\mathbb{F}_p$ 中选择八个私有随机元素 $s$、$\alpha_v$、$\alpha_w$、$\alpha_y$、$\beta$、$r_v$、$r_w$、$\gamma$,并设置 $r_y = r_v \cdot r_w$。这组元素被称为有毒废物,一旦生成密钥就应丢弃并完全遗忘。
pub struct ToxicWaste {
rv: FE,
rw: FE,
s: FE,
// .... (其他元素)
impl ToxicWaste {
pub fn sample() -> Self {
Self {
s: sample_fr_elem(),
alpha_v: sample_fr_elem(),
// ... (其他元素)
}
}
pub fn ry(&self) -> FE {
&self.rv * &self.rw
}
}
在设置中生成两个公钥:评估密钥 (evaluation key),发送给证明者;验证密钥 (verification key),发送给验证者。
为了在 rust 中实现这一点,我们首先需要创建一个包含每个元素的 VerificationKey 结构,然后生成它。
pub struct VerificationKey {
pub g2: G2Point,
pub g2_alpha_v: G2Point,
pub g2_alpha_w: G2Point,
// ...
}
pub fn generate_verification_key(
qap: QuadraticArithmeticProgram,
toxic_waste: &ToxicWaste,
) -> VerificationKey {
let g1: G1Point = Curve::generator();
let g2: G2Point = TwistedCurve::generator();
// 声明其余需要的变量
// ...
VerificationKey {
g2: g2.clone(),
g2_alpha_v: g2.operate_with_self(toxic_waste.alpha_v.representative()),
// ...
}
}
pub struct EvaluationKey {
pub g1_vk: Vec<G1Point>,
pub g1_wk: Vec<G1Point>,
pub g2_wk: Vec<G2Point>,
// ...
}
pub fn generate_evaluation_key(
qap: &QuadraticArithmeticProgram,
toxic_waste: &ToxicWaste,
) -> EvaluationKey {
let g1: G1Point = Curve::generator();
let g2: G2Point = TwistedCurve::generator();
let (v_mid, w_mid, y_mid) = (qap.v_mid(), qap.w_mid(), qap.y_mid());
// 声明其余需要的变量
// ...
EvaluationKey {
g1_vk: vs_mid.iter()
.map(|vk| g.operate_with_self((rv * vk.evaluate(&s))
.representative()))
.collect(),
,
// ...
}
}
拥有 EvaluationKey 和 VeifiationKey 之后,我们可以实现设置:
pub fn setup(
qap: &QuadraticArithmeticProgram,
toxic_waste: ToxicWaste,
) -> (EvaluationKey, VerificationKey) {
(generate_evaluation_key(&qap, &toxic_waste),
generate_verification_key(qap.clone(), &toxic_waste))
}
证明者的步骤如下:
使用输入值评估电路并获得 $c_{N+1}, \dots, c_m$ 中间值。
计算多项式 $p(x) = (\sum_{k=1}^m c_k vk(x)) (\sum{k=1}^m c_k wk(x)) - \sum{k=1}^m c_k y_k(x)$。
计算多项式 $h(x) = \frac{p(x)}{t(x)}$。
生成证明 $\pi = (V, W_1, W_2, Y, V', W', Y', Z, H)$,计算其元素:
发送公共值 $(c_1, \dots, c_N)$ 和证明 $\pi$。
pub fn generate_proof(
evaluation_key: &EvaluationKey,
qap: &QuadraticArithmeticProgram,
qap_c_coefficients: &[FE],
) -> Proof {
// 我们将调用 {c_{N+1}, ... , c_m} cmid。
let cmid = &qap_c_coefficients[qap.number_of_inputs..qap_c_coefficients.len() - qap.number_of_outputs];
// 我们将 cmid 的每个 FieldElement 转换为 UnsignedInteger,以便我们可以将它们乘以 g1。
let c_mid = cmid
.iter()
.map(|elem| elem.representative())
.collect::<Vec<_>>();
let h_polynomial = qap.h_polynomial(qap_c_coefficients);
let h_coefficients = h_polynomial.coefficients
.iter()
.map(|elem| elem.representative())
.collect::<Vec<_>>();
let h_degree = h_polynomial.degree();
Proof {
v: msm(&c_mid, &evaluation_key.g2_vk_s).unwrap(),
w1: msm(&c_mid, &evaluation_key.g2w_wk).unwrap(),
w2: msm(&c_mid, &evaluation_key.g2w_wk).unwrap(),
//...
}
}
为了防止恶意证明者欺骗验证者,他必须确保两件事:首先,满足 QAP 多项式的请求条件(编号 4);其次,证明的元素已从 QAP 正确生成。为了实现这一点,验证者将进行三个检查。第一个检查将确保 QAP 的有效性,其他两个检查将确保证明元素的正确构造。
我们将用 $e$ 表示其第一个参数来自 $G_1$,第二个参数来自 $G_2$ 的配对。
检查 1:QAP 的正确性
为了确保提供的证明对应于 QAP 的有效解,从而对应于正确的计算结果,验证者需要 убедиться $p(s) = h(s) t(s)$。为了实现这一点,他可以简单地检查 $e(V{io} + V, W{io} + W_2) = e(r_y t(s) \cdot g1, H) e(Y{io} + Y, g_2)$,为了简化符号,我们称为
pub fn check_divisibility(
verification_key: &VerificationKey,
proof: &Proof,
c_io: &[FE],
) -> bool {
// 我们将使用 hiding_v、hiding_w 和 hiding_y 作为配对的参数。
// 我们将 c_io 转换为 UnsignedInteger。
let c_io = c_io
.iter()
.map(|elem| elem.representative())
.collect::<Vec<_>>();
let v_io = verification_key.g1_vk[0]
.operate_with(&msm(&c_io, &verification_key.g1_vk[1..]).unwrap());
// 与 w_io 和 y_io 相同。
//...
Pairing::compute(
&v_io.operate_with(proof.v),
&w_io.operate_with(proof.w)
).unwrap()
== Pairing::compute( ... , ...).unwrap()
* Pairing::compute( ... , ...).unwrap()
}
$V$、$W$ 和 $Y$ 的正确构造:
检查 2: 验证者检查证明者是否使用 QAP 的多项式来构造 $V$、$W$ 和 $Y$,并且他没有提供随意的值,这些值只是通过了先前的检查。
因此,在此检查中,目标是验证 $V$ 是否是 $g_1$ 乘以 $v_k(s){k \in 1, \dots, m}$ 的一些线性组合,并且类似地,对于 $W$ 和 $Y$:
pub fn check_appropriate_spans(
verification_key: &VerificationKey,
proof: &Proof
) -> bool {
let b1 = Pairing::compute(&proof.v_prime, &verification_key.g2)
== Pairing::compute(&proof.v, &verification_key.g2_alpha_v);
let b2 = Pairing::compute( ... , ... )
== Pairing::compute(... , ... );
let b3 = // ...
b1 && b2 && b3
}
为什么这会奏效?
如果此检查通过,则验证者可以确保,例如,$V' = \alpha_v V$。查看评估密钥,他看到证明者不知道 $\alpha_v$ 的原始值。因此,证明者可以构造 $V$ 和 $V'$ 使得它们满足此等式的唯一方法是使用 $v_k(s){k \in 1, \dots, m}$ 的线性组合。类似地,他可以 убедиться $W$ 和 $Y$ 如此构造。
检查 3: 先前的检查不足以确保正确构造证明元素。我们还需要验证证明者在先前检查的每个线性组合 $V$、$W$ 和 $Y$ 中使用了相同的系数集 $c_1, \dots, c_m$。
$e(Z, \gamma \cdot g_2) = e(V + W + Y, \beta \gamma \cdot g_2)$
pub fn check_same_linear_combinations(
verification_key: &VerificationKey,
proof: &Proof
) -> bool {
Pairing::compute(&proof.z, &verification_key.g2_gamma)
== Pairing::compute(
&proof.v
.operate_with(&proof.w)
.operate_with(&proof.y),
&verification_key.g2_beta_gamma
)
}
放在一起
pub fn verify(verification_key:&VerificationKey,
proof: &Proof,
c_inputs_outputs: &[FE]
) -> bool {
let b1 = check_divisibility(verification_key, proof, c_inputs_outputs);
let b2 = check_appropriate_spans(verification_key, proof);
let b3 = check_same_linear_combinations(verification_key, proof);
b1 && b2 && b3
}
零知识是什么意思?我们希望验证者不可能从证明中获得任何信息,因为它看起来与随机数据无法区分。
为了使其具有零知识性,证明者必须采样一些随机值 $\delta_v, \delta_w, \delta_y$ 并对多项式进行以下更改:
$v_{mid}(x) + \delta_v t(x), v(x) + \delta_v t(x), w(x) + \delta_w t(x)$ 和 $y(x) + \delta_y t(x)$。
你可以在 Why and How zk-SNARK Works 的第 4.13 章 中详细了解协议的 zk 适配。
在本文中,我们介绍了 Pinocchio 协议背后的主要思想以及我们使用 Lambdaworks 库的实现。我们首先看到了将代码转换为 QAP 的步骤。然后,我们介绍了实际协议,解释了它的工作原理以及为什么我们需要每个不同的检查来实现安全性。最后,我们观察到,虽然其主要目标是实现可验证的计算,但它可以通过最少的额外工作来合并零知识属性。
- 原文链接: blog.lambdaclass.com/pin...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!