Skip to content

Commit 6d1e15b

Browse files
authored
Merge branch 'main' into main
2 parents 74514d6 + 019cfcf commit 6d1e15b

File tree

7 files changed

+225
-1
lines changed

7 files changed

+225
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Both Math and Crypto support wasm with target `wasm32-unknown-unknown`. To see a
4444
- [Merkle Tree CLI](https://github.com/lambdaclass/lambdaworks/tree/main/examples/merkle-tree-cli)
4545
- [Proving Miden](https://github.com/lambdaclass/lambdaworks/tree/main/examples/prove-miden)
4646
- [Shamir's secret sharing](https://github.com/lambdaclass/lambdaworks/tree/main/examples/shamir_secret_sharing)
47+
- [BabySNARK](https://github.com/lambdaclass/lambdaworks/tree/main/examples/baby-snark)
4748

4849
## Exercises and Challenges
4950
- [lambdaworks exercises and challenges](https://github.com/lambdaclass/lambdaworks_exercises/tree/main)

examples/baby-snark/README.md

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,147 @@ Below is a simple example demonstrating the usage of BabySnark:
3636
```rust
3737
let verified = verify(&vk, &proof, &public);
3838
assert!(verified);
39-
```
39+
```
40+
41+
# Working principle
42+
43+
## Programs as relationships between polynomials
44+
45+
BabySNARK is based on this [NIZK](https://eprint.iacr.org/2014/718) proposed in 2014. It works with square span programs, which are similar to, yet simpler than, quadratic arithmetic programs (used in [Pinocchio](https://eprint.iacr.org/2013/279)). The representation of the circuit is done with a matrix $U$ (belonging to $F^{m \times n}$) and a vector $z = (1 , u , w)^t$ (containing the instance $u$ and witness $w$),
46+
$(U.z) \circ (U.z) = 1$
47+
48+
We can express any boolean circuit using these types of constraints. Let us rewrite the equations in a different form that will be convenient for later purposes:
49+
$\left(\sum_j u_{ij} z_j \right)^2 = 1$
50+
which should be valid for every $i = 0, 1, 2, ...$. We can encode these equations using polynomials. Suppose that $m = 2^k$ for some $k$ and that we are working with a nice field $F$ containing a subgroup $D_i$ of size $2^k$. We can take $\omega$ as an $m$-th primitive root of unity ($\omega$ generates the whole subgroup) and find the polynomials $U_j (x)$ which satisfy
51+
$U_j (\omega^i ) = u_{ij}$
52+
By doing this, we are encoding our equations as relations over polynomials. Thus, we can replace the problem equivalently,
53+
$\left(\sum_j U_{j} (x) z_j \right)^2 - 1 = p(x)$
54+
If we evaluate the polynomials at $\omega^i$, then we get $U_j (\omega^i ) = u_{ij}$, and $p(\omega^i )$ evaluates to $0$ at every $\omega^i$. A theorem says that if $\omega^i$ is a root/zero of a polynomial $p(x)$, then $x - \omega^i$ divides $p(x)$. In other words, there is some $q (x)$ such that $p(x) = (x - \omega^i )q(x)$.
55+
56+
If the polynomial has multiple zeros, then it must be divisible by each $x - \omega^i$. Let us define $Z(x)$ as the vanishing polynomial over $D_i$
57+
$Z(x) = \prod_j (x -\omega^j ) = x^m - 1$
58+
where we used in the last equality that $\omega$ is a primitive $m$-th root of unity (this trick is also used in [STARKs](https://blog.lambdaclass.com/diving-deep-fri/)). Therefore, if all the constraints hold, we have a polynomial $q(x)$ which fulfills this equality
59+
$p(x) = Z(x) q(x)$
60+
61+
One way to show that the computation described by the system of equations is valid is by providing $p(x)$ and $q(x)$ and letting the verifier check the equality by himself. The problem is that we have to pass all the coefficients of both polynomials (which are as long as the computation) and let him compute the right-hand side and assert whether it equals the polynomial on the left-hand side. Besides, we also leak information on the witness! How can we turn this into something succinct and not leak information?
62+
63+
## Polynomial commitment schemes
64+
65+
A polynomial commitment scheme is given by four algorithms: setup, commit, open, and evaluate. The commitment allows us to bind ourselves to a given polynomial using short data and later be able to prove things about that polynomial. The commitment scheme must satisfy the following two properties:
66+
1. Hiding: the commitment does not reveal anything about the committed polynomial.
67+
2. Binding: given the commitment to $p(x)$, $\mathrm{cm} (p)$, it is infeasible to find another $q(x)$, such that $\mathrm{cm} (p) = \mathrm{cm} (q)$
68+
69+
One way to build a PCS is by using a pairing-friendly elliptic curve, such as BN254 or BLS12-381. We will work here with type-3 pairings, which are functions $e: G_1 \times G_2 \rightarrow G_t$ with the following properties:
70+
1. Bilinearity: $e(a g_1 , b g_2) = e(g_1 , g_2 )^{ab}$.
71+
2. Non-degeneracy: If $e(P,Q) = 1$, then either $P = \mathcal{O}$ or $Q = \mathcal{O}$.
72+
73+
[KZG commitment scheme](https://blog.lambdaclass.com/mina-to-ethereum-bridge/) works in this setting, which is the tool we will use. Why are pairings useful? Because they provide us with a way of multiplying things hidden inside an elliptic curve group.
74+
75+
We pick a random $s$ (which is unknown to both the prover and verifier), and we generate the following points in the elliptic curve
76+
$\\{ P_0 , P_1 , ..., P_n \\} = \\{ g_1 , s g_1 , ..., s^n g_n \\}$
77+
These points contain the powers of $s$ hidden inside a group of the elliptic curve. Given any $P_k$, recovering $s$ is computationally intractable due to the hardness of the discrete log problem over elliptic curves.
78+
79+
We commit to the polynomial by computing
80+
$p(s) g_1 = \sum a_k (s^k g_1 ) = \sum a_k P_k$
81+
where $g_1$ is a generator of the group/subgroup of prime order $r$ of the elliptic curve. We could also commit using elements in $G_2$, where we have $g_2$ as a subgroup generator.
82+
83+
Using pairings, we could prove the relationship between the polynomial $p(x)$ and the quotient $q(x)$ by computing two pairings and checking their equality:
84+
$e( p(s) g_1 , g_2) = e(g_1 , g_2 )^{p(s)}$
85+
$e(q(s) g_1 , s^m g_2 - g_2) = e(g_1 , g_2 )^{ q(s)(s^m - 1)}$
86+
Since $s$ is chosen at random, if $p(s) = q(s) Z(s)$, then with overwhelming probability, we have that $p(x) = q(x) Z(x)$.
87+
88+
With this construction, we do not need to supply the verifier with the coefficients of the polynomials, only their commitments. This solves part of the problem but not everything.
89+
90+
## Intuition for the protocol
91+
92+
The program/circuit that we want to prove is defined by the matrix $U$. When we define a particular instance/public input $u$ to the circuit, if $u$ is valid, we should be able to find some $w$ that solves the system of equations. To make the proof succinct, we should send much less information than the full witness (besides, if we want zero-knowledge, the witness should be kept secret).
93+
94+
We have the polynomial $p(x)$ of the problem, the vanishing polynomial $Z(x)$, and the quotient $q(x)$. In the end we want to prove that
95+
$p(x) = Z(x)q(x)$
96+
if the computation is valid. $Z(x)$ is known to both the prover and the verifier, and we could even commit to $Z(x)$ as part of the public information. We can reduce this check to just one point $x = s$ and verify this using pairings. However, this check alone would be insufficient since the prover could provide any polynomial $p(x)$. If we recall how we build $p(x)$,
97+
$\left(\sum_j U_{j} (x) z_j \right)^2 - 1 = p(x)$
98+
Some terms in the summation can be computed by the verifier (since these are public). However, the verifier does not know the witness's terms, and we do not want to give him access to that data in total. The solution would be for the prover to give the summation, including only the values of the witness,
99+
$$V_w (x) = \sum_{j \in w} w_j U_j(x)$$
100+
Moreover, we can provide a commitment to $V_w (x)$ using the commitment scheme we had before, $V_w (s) g_1$ and $V_w (s) g_2$ (we will show why we need both soon). The verifier can then compute
101+
$$V_u (x) = \sum_{k \in u} u_j U_j(x)$$
102+
and get $V_u (s) g_1$ and $V_u (s) g_2$. The verifier can compute the pairing involving $e( p(s) g_1 , g_2)$ in an equivalent way,
103+
$$e ( V_u (s) g_1 + V_w(s) g_1 , V_u (s) g_2 + V_w(s) g_2 ) e ( g_1 , g_2 )^{ - 1 } = e( p(s) g_1 , g_2)$$
104+
This looks odd, but if we take all the scalars to the exponent, we have $(V_u (s) + V_w (s))(V_u (s) + V_w (s)) - 1$, and the verifier can get the polynomial of the circuit. So, we get the first check,
105+
$$e ( V_u (s) g_1 + V_w(s) g_1 , V_u (s) g_2 + V_w(s) g_2 ) e ( g_1 , g_2 )^{ - 1 } = e( q(s) g_1 , Z(s)g_2)$$
106+
107+
We have one problem, though. How do we know that the prover used the same $V_w (x)$ in both commitments? Luckily, we can solve this with another pairing check,
108+
$e( V_w (s) g_1 , g_2 ) = e( g_1 , V_w(s) g_2 )$
109+
110+
We got another check. Finally, how do we know that the verifier computed $V_w (x)$ correctly and did not do some random linear combination that will cancel out with the public input and yield something nice?
111+
112+
We could force the prover to provide the same linear combination, but with the points all shifted by some constant $\beta$, unknown to the parties. We define
113+
$B_w (x) = \sum \beta w_j U_j (x) = \beta V_w (x)$
114+
We can do one final check for this relationship using pairings,
115+
$e( B_w (s) g_1 , \gamma g_2 ) = e( \gamma \beta g_1 , V_w (s) g_2 )$
116+
where $\gamma$ is also unknown to the parties. This makes it impossible for the prover to build fake polynomials for $V_w (x)$. We can see that if this condition did not exist, we could create any $V_w (x) = C Z(x) - V_u (x) + 1$, which would pass all the other checks for any $C$ of our choice. In fact,
117+
$V_w (x) + V_u (x) = C Z(x) + 1$
118+
But $p(x) = (V_w (x) + V_u (x))^2 - 1$, so
119+
$p(x) = C^2 Z(x)Z(x) + C Z(x) = Z(x) (C^2 Z(x) + C)$
120+
and we find that $q(x) = (C^2 Z(x) + C)$, even though we do not know the witness.
121+
122+
The proof $\pi$ will consist of:
123+
1. The commitment to $V_w (x)$ using $g_1$.
124+
2. The commitment to $V_w (x)$ using $g_2$.
125+
3. The commitment to the quotient polynomial $q(x)$ using $g_1$.
126+
4. The commitment to $B_w (x)$ using $g_1$
127+
128+
The verification involves six pairings (the pairing $e(g_1 , g_2)^{ - 1}$ can be precomputed since it is a constant), to check the three conditions we mentioned.
129+
130+
To compute the commitments, we need parameters $s , \beta , \gamma$ to be unknown to both parties (hence, they are toxic waste). We need to generate a reference string, which will be circuit dependent (that is because we need to provide $\beta U_j(s) g_1$). With all this, we can jump into the implementation.
131+
132+
# Implementation
133+
134+
## Setup
135+
136+
Prover and verifier agree on a pairing-friendly elliptic curve and generators of the groups $G_1$ and $G_2$, denoted by $g_1$ and $g_2$, respectively. In our case, we choose BLS12-381. The proving key consists of the following:
137+
1. $\\{s^k g_1 \\}$ for $k = 0, 1, 2 , ... m$
138+
2. $\\{U_j (s) g_1 \\}$ for $j = l , l + 1 , ... m$ ($l$ being the number of public inputs).
139+
3. $\\{U_j (s) g_2 \\}$ for $j = l , l + 1 , ... m$
140+
4. $\\{\beta U_j (s) g_1 \\}$ for $j = l , l + 1 , ... m$
141+
142+
The verifying key consists of the following:
143+
1. $\\{U_j (s) g_1 \\}$ for $j = 0 , 1 , ... l - 1$
144+
2. $\\{U_j (s) g_2 \\}$ for $j = 0 , 1 , ... l - 1$
145+
3. $[Z^\prime ] = (s^m - 1)g_2$ (commitment to the vanishing polynomial)
146+
4. $e(g_1 , g_2)^{ - 1}$
147+
5. $\beta \gamma g_1$
148+
6. $\gamma g_2$
149+
150+
## Prove
151+
152+
The steps for the prover are as follows:
153+
1. Compute $[V_w ] = V_w (s) g_1$, $[V_w^\prime ] = V_w (s) g_2$, and $[B_w ] = B_w (s) g_1$ using the proving key.
154+
2. Compute the polynomial quotient polynomial $q(x)$ from the zerofier $Z(x)$, the vector of witness and instance, and the polynomials describing the circuit $U_j (x)$.
155+
3. Compute $[q ] = q(s) g_1$ using the proving key.
156+
4. Produce the proof $\pi = ( [q] , [V_w ] , [V_w^\prime ] , [B_w ])$
157+
158+
## Verify
159+
160+
The verifier has the following steps:
161+
1. Parse the proof $\pi$ as $[q] , [V_w ] , [V_w^\prime ] , [B_w ]$.
162+
2. Check $e( [V_w ] , g_2 ) = e( g_1 , [V_w^\prime ])$
163+
3. Check $e( [B_w] , \gamma g_2) = e( \beta \gamma g_1 , [V_w^\prime ])$
164+
4. Compute $[V_u ] = V_u (s) g_1$, and $[V_u^\prime ] = V_u (s) g_2$ using the verifying key.
165+
5. Check $e([V_u ] + [V_w ] , [V_u^\prime ] + [V_w^\prime ])e(g_1 , g_2)^{ - 1} = e( [q] , [Z^\prime])$
166+
167+
If all checks pass, the proof is valid.
168+
169+
## Optimizations
170+
171+
1. Interpolation is done using the Fast Fourier Transform (FFT). This is possible because BLS12-381's scalar field has $2^{32}$ as one of its factors.
172+
2. The quotient is calculated in evaluation form, using the FFT. We need to evaluate the polynomials at $\mu \omega^k$, where $\mu$ is the offset (we want to evaluate on cosets because if we evaluate directly over $D_i$, we get $0/0$).
173+
3. The evaluation of the vanishing polynomial is straightforward: $Z(\mu \omega^k ) = (\mu \omega^k )^m - 1 = \mu^m - 1$, because $\omega$ has order $m$.
174+
4. Compute multiscalar multiplications using Pippenger's algorithm.
175+
176+
## Turning the SNARK into a zk-SNARK.
177+
178+
The protocol above is not zero-knowledge since $V_w (x)$ can be distinguished from a random-looking $V (x)$. To make it zero-knowledge, the prover has to sample a random value $\delta$ and make the following changes to the polynomials:
179+
1. The polynomial $p(x) = \left(\sum_k z_j U_j(x) + \delta Z(x) \right)^2 - 1$. Note that adding $Z(x)$ does not change the main condition, which is that the constraints are satisfied if and only if $p(x)$ is divisible by $Z(x)$.
180+
2. Compute $[V_w ] = (V_w (s) + \delta Z(s)) g_1$, $[V_w^\prime ] = (V_w (s) + \delta Z(s)) g_2$, and $[B_w ] = (B_w (s) + \beta \delta Z(s)) g_1$.
181+
182+
The verifier's steps are unchanged.

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/field_extension.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,14 @@ mod tests {
381381
assert_eq!(a + &b, a_extension + b);
382382
}
383383

384+
#[test]
385+
fn double_base_field_with_degree_2_extension() {
386+
let a = FieldElement::<BLS12381PrimeField>::from(3);
387+
let b = FieldElement::<Degree2ExtensionField>::from(2);
388+
assert_eq!(a.double(), a.clone() + a);
389+
assert_eq!(b.double(), b.clone() + b);
390+
}
391+
384392
#[test]
385393
fn mul_base_field_with_degree_2_extension() {
386394
let a = FieldElement::<BLS12381PrimeField>::from(3);

math/src/field/element.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,14 @@ where
444444
}
445445
}
446446

447+
/// Returns the double of `self`
448+
#[inline(always)]
449+
pub fn double(&self) -> Self {
450+
Self {
451+
value: F::double(&self.value),
452+
}
453+
}
454+
447455
/// Returns `self` raised to the power of `exponent`
448456
#[inline(always)]
449457
pub fn pow<T>(&self, exponent: T) -> Self

math/src/field/fields/montgomery_backed_prime_fields.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,14 @@ mod tests_u384_prime_fields {
495495
assert_eq!(x_deserialized, x);
496496
}
497497

498+
#[test]
499+
fn doubling() {
500+
assert_eq!(
501+
U384F23Element::from(2).double(),
502+
U384F23Element::from(2) + U384F23Element::from(2),
503+
);
504+
}
505+
498506
const ORDER: usize = 23;
499507
#[test]
500508
fn two_plus_one_is_three() {
@@ -843,6 +851,14 @@ mod tests_u256_prime_fields {
843851
assert_eq!(x * y, c);
844852
}
845853

854+
#[test]
855+
fn doubling() {
856+
assert_eq!(
857+
U256F29Element::from(2).double(),
858+
U256F29Element::from(2) + U256F29Element::from(2),
859+
);
860+
}
861+
846862
const ORDER: usize = 29;
847863
#[test]
848864
fn two_plus_one_is_three() {

math/src/field/traits.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ pub trait IsField: Debug + Clone {
106106
/// Returns the sum of `a` and `b`.
107107
fn add(a: &Self::BaseType, b: &Self::BaseType) -> Self::BaseType;
108108

109+
/// Returns the double of `a`.
110+
fn double(a: &Self::BaseType) -> Self::BaseType {
111+
Self::add(a, a)
112+
}
113+
109114
/// Returns the multiplication of `a` and `b`.
110115
fn mul(a: &Self::BaseType, b: &Self::BaseType) -> Self::BaseType;
111116

math/src/unsigned_integer/element.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,6 +587,11 @@ impl<const NUM_LIMBS: usize> UnsignedInteger<NUM_LIMBS> {
587587
(UnsignedInteger { limbs }, carry > 0)
588588
}
589589

590+
/// Returns the double of `self`.
591+
pub fn double(a: &UnsignedInteger<NUM_LIMBS>) -> (UnsignedInteger<NUM_LIMBS>, bool) {
592+
Self::add(a, a)
593+
}
594+
590595
/// Multi-precision subtraction.
591596
/// Adapted from Algorithm 14.9 of "Handbook of Applied Cryptography" (https://cacr.uwaterloo.ca/hac/)
592597
/// Returns the results and a flag that is set if the substraction underflowed
@@ -1442,6 +1447,16 @@ mod tests_u384 {
14421447
assert!(!U384::const_ne(&a, &b));
14431448
}
14441449

1450+
#[test]
1451+
fn double_two_384_bit_integers() {
1452+
let a = U384::from_u64(2);
1453+
let b = U384::from_u64(5);
1454+
let c = U384::from_u64(7);
1455+
assert_eq!(U384::double(&a).0, a + a);
1456+
assert_eq!(U384::double(&b).0, b + b);
1457+
assert_eq!(U384::double(&c).0, c + c);
1458+
}
1459+
14451460
#[test]
14461461
fn add_two_384_bit_integers_1() {
14471462
let a = U384::from_u64(2);
@@ -1550,6 +1565,12 @@ mod tests_u384 {
15501565
assert!(overflow);
15511566
}
15521567

1568+
#[test]
1569+
fn double_384_bit_integer_12_with_overflow() {
1570+
let a = U384::from_hex_unchecked("b07bc844363dd56467d9ebdd5929e9bb34a8e2577db77df6cf8f2ac45bd3d0bc2fc3078d265fe761af51d6aec5b59428");
1571+
assert_eq!(U384::double(&a), U384::add(&a, &a));
1572+
}
1573+
15531574
#[test]
15541575
fn sub_two_384_bit_integers_1() {
15551576
let a = U384::from_u64(2);
@@ -2406,6 +2427,16 @@ mod tests_u256 {
24062427
assert_ne!(a, b);
24072428
}
24082429

2430+
#[test]
2431+
fn double_256_bit_integer_1() {
2432+
let a = U256::from_u64(2);
2433+
let b = U256::from_u64(5);
2434+
let c = U256::from_u64(7);
2435+
assert_eq!(U256::double(&a).0, a + a);
2436+
assert_eq!(U256::double(&b).0, b + b);
2437+
assert_eq!(U256::double(&c).0, c + c);
2438+
}
2439+
24092440
#[test]
24102441
fn add_two_256_bit_integers_1() {
24112442
let a = U256::from_u64(2);
@@ -2544,6 +2575,18 @@ mod tests_u256 {
25442575
assert!(overflow);
25452576
}
25462577

2578+
#[test]
2579+
fn double_256_bit_integer_12_with_overflow() {
2580+
let a = U256::from_hex_unchecked(
2581+
"b07bc844363dd56467d9ebdd5929e9bb34a8e2577db77df6cf8f2ac45bd3d0bc",
2582+
);
2583+
let b = U256::from_hex_unchecked(
2584+
"cbbc474761bb7995ff54e25fa5d30295604fe3545d0cde405e72d8c0acebb119",
2585+
);
2586+
assert_eq!(U256::double(&a), U256::add(&a, &a));
2587+
assert_eq!(U256::double(&b), U256::add(&b, &b));
2588+
}
2589+
25472590
#[test]
25482591
fn sub_two_256_bit_integers_1() {
25492592
let a = U256::from_u64(2);

0 commit comments

Comments
 (0)