Skip to content

Commit 2491764

Browse files
Merge branch 'main' into feat/multilinear_sumcheck
2 parents 23dc778 + fed12d6 commit 2491764

File tree

33 files changed

+6840
-62
lines changed

33 files changed

+6840
-62
lines changed

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22

3-
members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "provers/groth16", "provers/groth16/arkworks-adapter", "examples/merkle-tree-cli", "examples/prove-miden", "winterfell_adapter", "examples/shamir_secret_sharing"]
3+
members = ["math", "crypto", "gpu", "benches", "provers/plonk", "provers/stark", "provers/cairo", "provers/groth16", "provers/groth16/arkworks-adapter", "provers/groth16/circom-adapter", "examples/merkle-tree-cli", "examples/prove-miden", "winterfell_adapter", "examples/shamir_secret_sharing", "examples/prove-verify-circom"]
44
exclude = ["ensure-no_std"]
55
resolver = "2"
66

@@ -18,6 +18,8 @@ lambdaworks-math = { path = "./math", version = "0.5.0", default-features = fals
1818
stark-platinum-prover = { path = "./provers/stark" }
1919
cairo-platinum-prover = { path = "./provers/cairo" }
2020
lambdaworks-winterfell-adapter = { path = "./winterfell_adapter"}
21+
lambdaworks-groth16 = { path = "./provers/groth16" }
22+
lambdaworks-circom-adapter = { path = "./provers/groth16/circom-adapter" }
2123

2224
[patch.crates-io]
2325
winter-air = { git = "https://github.com/lambdaclass/winterfell-for-lambdaworks.git", branch = "derive-clone-v6.4"}

crypto/src/hash/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod hash_to_field;
2+
pub mod monolith;
23
pub mod pedersen;
34
pub mod poseidon;
45
pub mod sha3;

crypto/src/hash/monolith/mod.rs

+224
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
use alloc::vec::Vec;
2+
use lambdaworks_math::field::{
3+
fields::mersenne31::field::MERSENNE_31_PRIME_FIELD_ORDER, traits::IsField,
4+
};
5+
use sha3::{
6+
digest::{ExtendableOutput, Update},
7+
Shake128, Shake128Reader,
8+
};
9+
10+
mod utils;
11+
use utils::*;
12+
13+
// Ported from https://github.com/Plonky3/Plonky3/blob/main/monolith
14+
15+
pub const NUM_BARS: usize = 8;
16+
const MATRIX_CIRC_MDS_16_MERSENNE31_MONOLITH: [u32; 16] = [
17+
61402, 17845, 26798, 59689, 12021, 40901, 41351, 27521, 56951, 12034, 53865, 43244, 7454,
18+
33823, 28750, 1108,
19+
];
20+
21+
pub struct MonolithMersenne31<const WIDTH: usize, const NUM_FULL_ROUNDS: usize> {
22+
round_constants: Vec<Vec<u32>>,
23+
lookup1: Vec<u16>,
24+
lookup2: Vec<u16>,
25+
}
26+
27+
impl<const WIDTH: usize, const NUM_FULL_ROUNDS: usize> Default
28+
for MonolithMersenne31<WIDTH, NUM_FULL_ROUNDS>
29+
{
30+
fn default() -> Self {
31+
Self::new()
32+
}
33+
}
34+
35+
impl<const WIDTH: usize, const NUM_FULL_ROUNDS: usize> MonolithMersenne31<WIDTH, NUM_FULL_ROUNDS> {
36+
pub fn new() -> Self {
37+
assert!(WIDTH >= 8);
38+
assert!(WIDTH <= 24);
39+
assert!(WIDTH % 4 == 0);
40+
Self {
41+
round_constants: Self::instantiate_round_constants(),
42+
lookup1: Self::instantiate_lookup1(),
43+
lookup2: Self::instantiate_lookup2(),
44+
}
45+
}
46+
47+
fn instantiate_round_constants() -> Vec<Vec<u32>> {
48+
let mut shake = Shake128::default();
49+
shake.update("Monolith".as_bytes());
50+
shake.update(&[WIDTH as u8, (NUM_FULL_ROUNDS + 1) as u8]);
51+
shake.update(&MERSENNE_31_PRIME_FIELD_ORDER.to_le_bytes());
52+
shake.update(&[8, 8, 8, 7]);
53+
let mut shake_finalized = shake.finalize_xof();
54+
random_matrix(&mut shake_finalized, NUM_FULL_ROUNDS, WIDTH)
55+
}
56+
57+
fn instantiate_lookup1() -> Vec<u16> {
58+
(0..=u16::MAX)
59+
.map(|i| {
60+
let hi = (i >> 8) as u8;
61+
let lo = i as u8;
62+
((Self::s_box(hi) as u16) << 8) | Self::s_box(lo) as u16
63+
})
64+
.collect()
65+
}
66+
67+
fn instantiate_lookup2() -> Vec<u16> {
68+
(0..(1 << 15))
69+
.map(|i| {
70+
let hi = (i >> 8) as u8;
71+
let lo: u8 = i as u8;
72+
((Self::final_s_box(hi) as u16) << 8) | Self::s_box(lo) as u16
73+
})
74+
.collect()
75+
}
76+
77+
fn s_box(y: u8) -> u8 {
78+
(y ^ !y.rotate_left(1) & y.rotate_left(2) & y.rotate_left(3)).rotate_left(1)
79+
}
80+
81+
fn final_s_box(y: u8) -> u8 {
82+
debug_assert_eq!(y >> 7, 0);
83+
84+
let y_rot_1 = (y >> 6) | (y << 1);
85+
let y_rot_2 = (y >> 5) | (y << 2);
86+
87+
let tmp = (y ^ !y_rot_1 & y_rot_2) & 0x7F;
88+
((tmp >> 6) | (tmp << 1)) & 0x7F
89+
}
90+
91+
pub fn permutation(&self, state: &mut Vec<u32>) {
92+
self.concrete(state);
93+
for round in 0..NUM_FULL_ROUNDS {
94+
self.bars(state);
95+
Self::bricks(state);
96+
self.concrete(state);
97+
Self::add_round_constants(state, &self.round_constants[round]);
98+
}
99+
self.bars(state);
100+
Self::bricks(state);
101+
self.concrete(state);
102+
}
103+
104+
// MDS matrix
105+
fn concrete(&self, state: &mut Vec<u32>) {
106+
*state = if WIDTH == 16 {
107+
Self::apply_circulant(&mut MATRIX_CIRC_MDS_16_MERSENNE31_MONOLITH.clone(), state)
108+
} else {
109+
let mut shake = Shake128::default();
110+
shake.update("Monolith".as_bytes());
111+
shake.update(&[WIDTH as u8, (NUM_FULL_ROUNDS + 1) as u8]);
112+
shake.update(&MERSENNE_31_PRIME_FIELD_ORDER.to_le_bytes());
113+
shake.update(&[16, 15]);
114+
shake.update("MDS".as_bytes());
115+
let mut shake_finalized = shake.finalize_xof();
116+
Self::apply_cauchy_mds_matrix(&mut shake_finalized, state)
117+
};
118+
}
119+
120+
// S-box lookups
121+
fn bars(&self, state: &mut [u32]) {
122+
for state in state.iter_mut().take(NUM_BARS) {
123+
*state = (self.lookup2[(*state >> 16) as u16 as usize] as u32) << 16
124+
| self.lookup1[*state as u16 as usize] as u32;
125+
}
126+
}
127+
128+
// (x_{n+1})² = (x_n)² + x_{n+1}
129+
fn bricks(state: &mut [u32]) {
130+
for i in (0..state.len() - 1).rev() {
131+
state[i + 1] = F::add(&state[i + 1], &F::square(&state[i]));
132+
}
133+
}
134+
135+
fn add_round_constants(state: &mut [u32], round_constants: &[u32]) {
136+
for (x, rc) in state.iter_mut().zip(round_constants) {
137+
*x = F::add(x, rc);
138+
}
139+
}
140+
141+
// O(n²)
142+
fn apply_circulant(circ_matrix: &mut [u32], input: &[u32]) -> Vec<u32> {
143+
let mut output = vec![F::zero(); WIDTH];
144+
for out_i in output.iter_mut().take(WIDTH - 1) {
145+
*out_i = dot_product(circ_matrix, input);
146+
circ_matrix.rotate_right(1);
147+
}
148+
output[WIDTH - 1] = dot_product(circ_matrix, input);
149+
output
150+
}
151+
152+
fn apply_cauchy_mds_matrix(shake: &mut Shake128Reader, to_multiply: &[u32]) -> Vec<u32> {
153+
let mut output = vec![F::zero(); WIDTH];
154+
155+
let bits: u32 = u64::BITS
156+
- (MERSENNE_31_PRIME_FIELD_ORDER as u64)
157+
.saturating_sub(1)
158+
.leading_zeros();
159+
160+
let x_mask = (1 << (bits - 9)) - 1;
161+
let y_mask = ((1 << bits) - 1) >> 2;
162+
163+
let y = get_random_y_i(shake, WIDTH, x_mask, y_mask);
164+
let mut x = y.clone();
165+
x.iter_mut().for_each(|x_i| *x_i &= x_mask);
166+
167+
for (i, x_i) in x.iter().enumerate() {
168+
for (j, yj) in y.iter().enumerate() {
169+
output[i] = F::add(&output[i], &F::div(&to_multiply[j], &F::add(x_i, yj)));
170+
}
171+
}
172+
173+
output
174+
}
175+
}
176+
177+
#[cfg(test)]
178+
mod tests {
179+
use super::*;
180+
181+
fn get_test_input(width: usize) -> Vec<u32> {
182+
(0..width).map(|i| F::from_base_type(i as u32)).collect()
183+
}
184+
185+
#[test]
186+
fn from_plonky3_concrete_width_16() {
187+
let mut input = get_test_input(16);
188+
MonolithMersenne31::<16, 5>::new().concrete(&mut input);
189+
assert_eq!(
190+
input,
191+
[
192+
3470365, 3977394, 4042151, 4025740, 4431233, 4264086, 3927003, 4259216, 3872757,
193+
3957178, 3820319, 3690660, 4023081, 3592814, 3688803, 3928040
194+
]
195+
);
196+
}
197+
198+
#[test]
199+
fn from_plonky3_concrete_width_12() {
200+
let mut input = get_test_input(12);
201+
MonolithMersenne31::<12, 5>::new().concrete(&mut input);
202+
assert_eq!(
203+
input,
204+
[
205+
365726249, 1885122147, 379836542, 860204337, 889139350, 1052715727, 151617411,
206+
700047874, 925910152, 339398001, 721459023, 464532407
207+
]
208+
);
209+
}
210+
211+
#[test]
212+
fn from_plonky3_width_16() {
213+
let mut input = get_test_input(16);
214+
MonolithMersenne31::<16, 5>::new().permutation(&mut input);
215+
assert_eq!(
216+
input,
217+
[
218+
609156607, 290107110, 1900746598, 1734707571, 2050994835, 1648553244, 1307647296,
219+
1941164548, 1707113065, 1477714255, 1170160793, 93800695, 769879348, 375548503,
220+
1989726444, 1349325635
221+
]
222+
);
223+
}
224+
}

crypto/src/hash/monolith/utils.rs

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
use alloc::vec::Vec;
2+
use lambdaworks_math::field::{
3+
fields::mersenne31::field::{Mersenne31Field, MERSENNE_31_PRIME_FIELD_ORDER},
4+
traits::IsField,
5+
};
6+
use sha3::{digest::XofReader, Shake128Reader};
7+
8+
// Ported from https://github.com/Plonky3/Plonky3/blob/main/monolith
9+
10+
pub type F = Mersenne31Field;
11+
12+
pub fn random_matrix(shake: &mut Shake128Reader, n: usize, m: usize) -> Vec<Vec<u32>> {
13+
(0..n)
14+
.map(|_| (0..m).map(|_| random_field_element(shake)).collect())
15+
.collect()
16+
}
17+
18+
fn random_field_element(shake: &mut Shake128Reader) -> u32 {
19+
let mut val = shake_random_u32(shake);
20+
while val >= MERSENNE_31_PRIME_FIELD_ORDER {
21+
val = shake_random_u32(shake);
22+
}
23+
F::from_base_type(val)
24+
}
25+
26+
pub fn dot_product(u: &[u32], v: &[u32]) -> u32 {
27+
u.iter()
28+
.zip(v)
29+
.map(|(x, y)| F::mul(x, y))
30+
.reduce(|a, b| F::add(&a, &b))
31+
.unwrap()
32+
}
33+
34+
pub fn get_random_y_i(
35+
shake: &mut Shake128Reader,
36+
width: usize,
37+
x_mask: u32,
38+
y_mask: u32,
39+
) -> Vec<u32> {
40+
let mut res = vec![0; width];
41+
42+
for i in 0..width {
43+
let mut y_i = shake_random_u32(shake) & y_mask;
44+
let mut x_i = y_i & x_mask;
45+
while res.iter().take(i).any(|r| r & x_mask == x_i) {
46+
y_i = shake_random_u32(shake) & y_mask;
47+
x_i = y_i & x_mask;
48+
}
49+
res[i] = y_i;
50+
}
51+
52+
res
53+
}
54+
55+
fn shake_random_u32(shake: &mut Shake128Reader) -> u32 {
56+
let mut rand = [0u8; 4];
57+
shake.read(&mut rand);
58+
u32::from_le_bytes(rand)
59+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "prove-verify-circom"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
8+
[[bin]]
9+
name = "prove-verify-circom"
10+
path = "src/main.rs"
11+
12+
[dependencies]
13+
lambdaworks-crypto = { workspace = true }
14+
lambdaworks-groth16 = { workspace = true }
15+
lambdaworks-math = { workspace = true, features = ["lambdaworks-serde-string"] }
16+
lambdaworks-circom-adapter = { workspace = true }
17+
serde = { version = "1.0" }
18+
serde_json = "1"
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div align="center">
2+
3+
# Lambdaworks Circom Proving & Verification Example
4+
5+
This programs converts Circom & SnarkJS generated constraints and witnesses into Lambdaworks-compatible instances, performs trusted setup, generates proof, and finally verifies the integrity of witness assignments.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"in": 3,
3+
"k": 12331548239589023489032859403859043
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma circom 2.0.0;
2+
3+
include "circomlib/mimc.circom";
4+
5+
template Test() {
6+
signal input in;
7+
signal input k;
8+
9+
signal output out;
10+
11+
component hash = MiMC7(10);
12+
13+
hash.x_in <== in;
14+
hash.k <== k;
15+
16+
out <== hash.out;
17+
}
18+
19+
component main = Test();

0 commit comments

Comments
 (0)