Skip to content

Commit 19e54d2

Browse files
multi-plonk
1 parent 65387a2 commit 19e54d2

16 files changed

Lines changed: 3212 additions & 15 deletions

File tree

Cargo.lock

Lines changed: 568 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
[workspace]
2+
members = ["multi-plonk"]
3+
14
[package]
25
name = "multi-stark"
36
version = "0.1.0"

multi-plonk/Cargo.toml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
[package]
2+
name = "multi-plonk"
3+
version = "0.1.0"
4+
edition = "2024"
5+
authors = ["Argument Engineering <engineering@argument.xyz>"]
6+
license = "MIT OR Apache-2.0"
7+
rust-version = "1.88"
8+
9+
[dependencies]
10+
ark-bls12-381 = { version = "0.5", features = ["curve"] }
11+
ark-ec = "0.5"
12+
ark-ff = "0.5"
13+
ark-poly = "0.5"
14+
ark-std = "0.5"
15+
ark-crypto-primitives = { version = "0.5", features = ["sponge"] }
16+
ark-poly-commit = "0.5"
17+
ark-serialize = "0.5"
18+
19+
[lints.clippy]
20+
cast_lossless = "warn"
21+
cast_possible_truncation = "warn"
22+
cast_precision_loss = "warn"
23+
cast_sign_loss = "warn"
24+
cast_possible_wrap = "warn"
25+
ptr_as_ptr = "warn"
26+
checked_conversions = "warn"
27+
dbg_macro = "warn"
28+
derive_partial_eq_without_eq = "warn"
29+
enum_glob_use = "warn"
30+
explicit_into_iter_loop = "warn"
31+
fallible_impl_from = "warn"
32+
filter_map_next = "warn"
33+
flat_map_option = "warn"
34+
from_iter_instead_of_collect = "warn"
35+
implicit_clone = "warn"
36+
inefficient_to_string = "warn"
37+
large_stack_arrays = "warn"
38+
large_types_passed_by_value = "warn"
39+
macro_use_imports = "warn"
40+
manual_assert = "warn"
41+
map_err_ignore = "warn"
42+
map_unwrap_or = "warn"
43+
match_same_arms = "warn"
44+
match_wild_err_arm = "warn"
45+
needless_continue = "warn"
46+
needless_for_each = "warn"
47+
needless_pass_by_value = "warn"
48+
option_option = "warn"
49+
same_functions_in_if_condition = "warn"
50+
trait_duplication_in_bounds = "warn"
51+
unnecessary_wraps = "warn"
52+
unnested_or_patterns = "warn"
53+
use_self = "warn"
54+
55+
[lints.rust]
56+
nonstandard_style = "warn"
57+
rust_2024_compatibility = "warn"
58+
trivial_numeric_casts = "warn"
59+
unused_lifetimes = "warn"
60+
unused_qualifications = "warn"
61+
unreachable_pub = "warn"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! End-to-end smoke test: prove `a² + b² == c²` over a 4-row trace using
2+
//! the multi-plonk KZG-based prover/verifier on BLS12-381.
3+
4+
use ark_ff::Zero;
5+
use ark_serialize::CanonicalSerialize;
6+
use ark_std::test_rng;
7+
8+
use multi_plonk::air::{Air, AirBuilder, BaseAir};
9+
use multi_plonk::lookup::LookupAir;
10+
use multi_plonk::matrix::Matrix;
11+
use multi_plonk::system::{System, SystemWitness};
12+
use multi_plonk::types::{PlonkConfig, Val};
13+
14+
struct PythagoreanAir;
15+
16+
impl BaseAir for PythagoreanAir {
17+
fn width(&self) -> usize {
18+
3
19+
}
20+
}
21+
22+
impl<AB: AirBuilder> Air<AB> for PythagoreanAir {
23+
fn eval(&self, builder: &mut AB) {
24+
let row = builder.main_local();
25+
let a: AB::Expr = row[0].into();
26+
let b: AB::Expr = row[1].into();
27+
let c: AB::Expr = row[2].into();
28+
// a² + b² == c²
29+
builder.assert_eq(a.clone() * a + b.clone() * b, c.clone() * c);
30+
}
31+
}
32+
33+
fn main() {
34+
let mut rng = test_rng();
35+
// largest expected polynomial degree fits comfortably below this:
36+
// trace size n = 4, max constraint degree 2 → coset size 8,
37+
// quotient degree ≤ 2n - 1 = 7.
38+
let config = PlonkConfig::setup(64, &mut rng);
39+
40+
let air = PythagoreanAir;
41+
let width = air.width();
42+
let lookup_air = LookupAir::new(air, vec![]);
43+
let (system, key) = System::new(&config, [lookup_air]);
44+
45+
let f = |x: u64| Val::from(x);
46+
let trace = Matrix::new(
47+
vec![
48+
f(3),
49+
f(4),
50+
f(5),
51+
f(5),
52+
f(12),
53+
f(13),
54+
f(8),
55+
f(15),
56+
f(17),
57+
f(7),
58+
f(24),
59+
f(25),
60+
],
61+
width,
62+
);
63+
let witness = SystemWitness::from_stage_1(vec![trace], &system);
64+
65+
let no_claims = &[];
66+
let proof = system.prove_multiple_claims(&config, &key, no_claims, witness);
67+
println!(
68+
"intermediate accumulators: {:?}",
69+
proof.intermediate_accumulators
70+
);
71+
assert_eq!(
72+
proof.intermediate_accumulators.last().copied(),
73+
Some(Val::zero())
74+
);
75+
76+
system
77+
.verify_multiple_claims(&config, no_claims, &proof)
78+
.expect("verify");
79+
println!("Verified.");
80+
81+
let mut bytes = vec![];
82+
proof.serialize_uncompressed(&mut bytes).expect("serialize");
83+
let compressed_size = proof.compressed_size();
84+
println!(
85+
"Proof size: {} bytes (uncompressed), {} bytes (compressed)",
86+
bytes.len(),
87+
compressed_size
88+
);
89+
}

multi-plonk/src/air.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//! AIR trait + minimal `AirBuilder` hierarchy.
2+
//!
3+
//! Mirrors the `p3_air::Air` / `p3_air::AirBuilder` shape, simplified for a
4+
//! single field (no extension). Each builder exposes:
5+
//! * a 2-row window over the main, preprocessed, and stage-2 traces
6+
//! * row selectors (`is_first_row`, `is_last_row`, `is_transition`)
7+
//! * stage-2 public values (lookup challenges + accumulators)
8+
//! * an `assert_zero` sink that constraint folders accumulate into
9+
10+
use std::ops::{Add, Mul, Neg, Sub};
11+
12+
use crate::matrix::Matrix;
13+
use crate::types::Val;
14+
15+
/// Static circuit metadata: trace width and optional preprocessed trace.
16+
pub trait BaseAir {
17+
fn width(&self) -> usize;
18+
fn preprocessed_trace(&self) -> Option<Matrix<Val>> {
19+
None
20+
}
21+
}
22+
23+
/// Constraint-evaluation interface implemented by symbolic, prover, verifier
24+
/// and debug builders.
25+
pub trait AirBuilder: Sized {
26+
type Var: Copy + Into<Self::Expr>;
27+
type Expr: Sized
28+
+ Clone
29+
+ From<Val>
30+
+ From<Self::Var>
31+
+ Add<Self::Expr, Output = Self::Expr>
32+
+ Sub<Self::Expr, Output = Self::Expr>
33+
+ Mul<Self::Expr, Output = Self::Expr>
34+
+ Neg<Output = Self::Expr>;
35+
36+
fn main_local(&self) -> &[Self::Var];
37+
fn main_next(&self) -> &[Self::Var];
38+
fn preprocessed_local(&self) -> &[Self::Var];
39+
fn preprocessed_next(&self) -> &[Self::Var];
40+
fn stage_2_local(&self) -> &[Self::Var];
41+
fn stage_2_next(&self) -> &[Self::Var];
42+
fn stage_2_public_values(&self) -> &[Self::Var];
43+
44+
fn is_first_row(&self) -> Self::Expr;
45+
fn is_last_row(&self) -> Self::Expr;
46+
fn is_transition(&self) -> Self::Expr;
47+
48+
fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I);
49+
50+
fn assert_eq<I, J>(&mut self, x: I, y: J)
51+
where
52+
I: Into<Self::Expr>,
53+
J: Into<Self::Expr>,
54+
{
55+
self.assert_zero(x.into() - y.into());
56+
}
57+
58+
fn assert_one<I: Into<Self::Expr>>(&mut self, x: I) {
59+
let one: Self::Expr = Val::from(1u64).into();
60+
self.assert_zero(x.into() - one);
61+
}
62+
63+
fn assert_bool<I: Into<Self::Expr>>(&mut self, x: I) {
64+
let x = x.into();
65+
let one: Self::Expr = Val::from(1u64).into();
66+
self.assert_zero(x.clone() * (x - one));
67+
}
68+
69+
fn when<I: Into<Self::Expr>>(&mut self, condition: I) -> FilteredBuilder<'_, Self> {
70+
FilteredBuilder {
71+
inner: self,
72+
condition: condition.into(),
73+
}
74+
}
75+
76+
fn when_first_row(&mut self) -> FilteredBuilder<'_, Self> {
77+
let cond = self.is_first_row();
78+
self.when(cond)
79+
}
80+
81+
fn when_last_row(&mut self) -> FilteredBuilder<'_, Self> {
82+
let cond = self.is_last_row();
83+
self.when(cond)
84+
}
85+
86+
fn when_transition(&mut self) -> FilteredBuilder<'_, Self> {
87+
let cond = self.is_transition();
88+
self.when(cond)
89+
}
90+
}
91+
92+
/// Sub-builder that scopes assertions under a boolean `condition` expression
93+
/// (multiplies the assertion by the condition before forwarding it).
94+
pub struct FilteredBuilder<'a, AB: AirBuilder> {
95+
inner: &'a mut AB,
96+
condition: AB::Expr,
97+
}
98+
99+
impl<'a, AB: AirBuilder> FilteredBuilder<'a, AB> {
100+
pub fn assert_zero<I: Into<AB::Expr>>(&mut self, x: I) {
101+
let x: AB::Expr = x.into();
102+
self.inner.assert_zero(self.condition.clone() * x);
103+
}
104+
105+
pub fn assert_eq<I, J>(&mut self, x: I, y: J)
106+
where
107+
I: Into<AB::Expr>,
108+
J: Into<AB::Expr>,
109+
{
110+
self.assert_zero(x.into() - y.into());
111+
}
112+
113+
pub fn assert_one<I: Into<AB::Expr>>(&mut self, x: I) {
114+
let one: AB::Expr = Val::from(1u64).into();
115+
self.assert_eq(x, one);
116+
}
117+
}
118+
119+
/// AIR generic over the builder type, so the same constraints can be evaluated
120+
/// symbolically (degree analysis), concretely on a coset (prover), or at a
121+
/// single point (verifier).
122+
pub trait Air<AB: AirBuilder>: BaseAir {
123+
fn eval(&self, builder: &mut AB);
124+
}

multi-plonk/src/builder/check.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Debug builder that checks every constraint on every row, used in tests to
2+
//! locate constraint violations before invoking the full prover.
3+
4+
use ark_ff::Zero;
5+
6+
use crate::air::{Air, AirBuilder};
7+
use crate::matrix::Matrix;
8+
use crate::types::Val;
9+
10+
pub fn check_constraints<A>(
11+
air: &A,
12+
preprocessed: Option<&Matrix<Val>>,
13+
stage_1: &Matrix<Val>,
14+
stage_2: &Matrix<Val>,
15+
stage_2_public_values: &[Val],
16+
) where
17+
A: for<'a> Air<DebugConstraintBuilder<'a>>,
18+
{
19+
let height = stage_1.height();
20+
for i in 0..height {
21+
let i_next = (i + 1) % height;
22+
let mut builder = DebugConstraintBuilder {
23+
row_index: i,
24+
preprocessed_local: &[],
25+
preprocessed_next: &[],
26+
stage_1_local: stage_1.row(i),
27+
stage_1_next: stage_1.row(i_next),
28+
stage_2_local: stage_2.row(i),
29+
stage_2_next: stage_2.row(i_next),
30+
stage_2_public_values,
31+
is_first_row: Val::from(u64::from(i == 0)),
32+
is_last_row: Val::from(u64::from(i == height - 1)),
33+
is_transition: Val::from(u64::from(i != height - 1)),
34+
};
35+
if let Some(preprocessed) = preprocessed {
36+
builder.preprocessed_local = preprocessed.row(i);
37+
builder.preprocessed_next = preprocessed.row(i_next);
38+
air.eval(&mut builder);
39+
} else {
40+
air.eval(&mut builder);
41+
}
42+
}
43+
}
44+
45+
pub struct DebugConstraintBuilder<'a> {
46+
pub row_index: usize,
47+
pub preprocessed_local: &'a [Val],
48+
pub preprocessed_next: &'a [Val],
49+
pub stage_1_local: &'a [Val],
50+
pub stage_1_next: &'a [Val],
51+
pub stage_2_local: &'a [Val],
52+
pub stage_2_next: &'a [Val],
53+
pub stage_2_public_values: &'a [Val],
54+
pub is_first_row: Val,
55+
pub is_last_row: Val,
56+
pub is_transition: Val,
57+
}
58+
59+
impl<'a> AirBuilder for DebugConstraintBuilder<'a> {
60+
type Var = Val;
61+
type Expr = Val;
62+
63+
fn main_local(&self) -> &[Val] {
64+
self.stage_1_local
65+
}
66+
fn main_next(&self) -> &[Val] {
67+
self.stage_1_next
68+
}
69+
fn preprocessed_local(&self) -> &[Val] {
70+
self.preprocessed_local
71+
}
72+
fn preprocessed_next(&self) -> &[Val] {
73+
self.preprocessed_next
74+
}
75+
fn stage_2_local(&self) -> &[Val] {
76+
self.stage_2_local
77+
}
78+
fn stage_2_next(&self) -> &[Val] {
79+
self.stage_2_next
80+
}
81+
fn stage_2_public_values(&self) -> &[Val] {
82+
self.stage_2_public_values
83+
}
84+
85+
fn is_first_row(&self) -> Val {
86+
self.is_first_row
87+
}
88+
fn is_last_row(&self) -> Val {
89+
self.is_last_row
90+
}
91+
fn is_transition(&self) -> Val {
92+
self.is_transition
93+
}
94+
95+
fn assert_zero<I: Into<Val>>(&mut self, x: I) {
96+
let x: Val = x.into();
97+
assert!(
98+
x.is_zero(),
99+
"constraint had nonzero value on row {}: {:?}",
100+
self.row_index,
101+
x
102+
);
103+
}
104+
}

0 commit comments

Comments
 (0)