diff --git a/.gitignore b/.gitignore index f770c0ae..ecc680c0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ params_for_recursive_verifier params artifacts/ +spartan_vm_debug/ # Don't ignore benchmarking artifacts !tooling/provekit-bench/benches/* diff --git a/Cargo.toml b/Cargo.toml index 72dfd4db..fb6b9165 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -147,6 +147,7 @@ ark-ff = { version = "0.5", features = ["asm", "std"] } ark-poly = "0.5" ark-serialize = "0.5" ark-std = { version = "0.5", features = ["std"] } +spartan-vm = { path = "../spartan-vm" } spongefish = { git = "https://github.com/arkworks-rs/spongefish", features = [ "arkworks-algebra", ], rev = "ecb4f08373ed930175585c856517efdb1851fb47" } diff --git a/noir-examples/many_poseidons/Nargo.toml b/noir-examples/many_poseidons/Nargo.toml new file mode 100644 index 00000000..cbfd474f --- /dev/null +++ b/noir-examples/many_poseidons/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "basic" +type = "bin" +authors = [""] + +[dependencies] +poseidon = { tag = "v0.1.1", git = "https://github.com/noir-lang/poseidon" } diff --git a/noir-examples/many_poseidons/Prover.toml b/noir-examples/many_poseidons/Prover.toml new file mode 100644 index 00000000..52764ef9 --- /dev/null +++ b/noir-examples/many_poseidons/Prover.toml @@ -0,0 +1,2 @@ +input = "2" +out = "1851489996817039498840903388622400291095679538238885635067493836951352608371" diff --git a/noir-examples/many_poseidons/src/main.nr b/noir-examples/many_poseidons/src/main.nr new file mode 100644 index 00000000..c3054807 --- /dev/null +++ b/noir-examples/many_poseidons/src/main.nr @@ -0,0 +1,8 @@ +use poseidon::poseidon::bn254; + +fn main(mut input: Field, out: pub Field) { + for i in 0..10 { + input = bn254::hash_1([input]); + } + assert_eq(input, out); +} diff --git a/noir-examples/power/Nargo.toml b/noir-examples/power/Nargo.toml new file mode 100644 index 00000000..839ecb85 --- /dev/null +++ b/noir-examples/power/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "basic" +type = "bin" +authors = [""] +compiler_version = ">=0.22.0" + +[dependencies] diff --git a/noir-examples/power/Prover.toml b/noir-examples/power/Prover.toml new file mode 100644 index 00000000..6affebd5 --- /dev/null +++ b/noir-examples/power/Prover.toml @@ -0,0 +1,2 @@ +x = "1" +y = "1" \ No newline at end of file diff --git a/noir-examples/power/prove.sh b/noir-examples/power/prove.sh new file mode 100755 index 00000000..4ea8f91e --- /dev/null +++ b/noir-examples/power/prove.sh @@ -0,0 +1,8 @@ +nargo compile +nargo check +nargo execute my-witness +bb prove -b ./target/power.json -w ./target/my-witness.gz -o ./target/proof +echo "✅ Proof generated at ./target/proof" +bb write_vk -b ./target/power.json -o ./target/vk +bb verify -k ./target/vk -p ./target/proof +echo "✅ Verified the proof at ./target/proof" diff --git a/noir-examples/power/src/main.nr b/noir-examples/power/src/main.nr new file mode 100644 index 00000000..67f8f4ea --- /dev/null +++ b/noir-examples/power/src/main.nr @@ -0,0 +1,7 @@ +fn main(mut x: Field, y: pub Field) { + let mut r = 1; + for i in 0..10 { + r *= x; + } + assert(r == y); +} diff --git a/noir-examples/rangechecks/Nargo.toml b/noir-examples/rangechecks/Nargo.toml new file mode 100644 index 00000000..a6e701cc --- /dev/null +++ b/noir-examples/rangechecks/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "basic" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/noir-examples/rangechecks/Prover.toml b/noir-examples/rangechecks/Prover.toml new file mode 100644 index 00000000..2133a4e3 --- /dev/null +++ b/noir-examples/rangechecks/Prover.toml @@ -0,0 +1 @@ +x = "0" \ No newline at end of file diff --git a/noir-examples/rangechecks/src/main.nr b/noir-examples/rangechecks/src/main.nr new file mode 100644 index 00000000..d384e9ff --- /dev/null +++ b/noir-examples/rangechecks/src/main.nr @@ -0,0 +1,7 @@ +fn main(x: Field) { + for i in 0..10000 { + let r = x + i as Field; + r.assert_max_bit_size::<32>(); + } +} + diff --git a/provekit/common/Cargo.toml b/provekit/common/Cargo.toml index d39db74e..c26a8de7 100644 --- a/provekit/common/Cargo.toml +++ b/provekit/common/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] # Workspace crates skyscraper.workspace = true +spartan-vm.workspace = true # Noir language acir.workspace = true diff --git a/provekit/common/src/interner.rs b/provekit/common/src/interner.rs index 822a6a7d..6352aaaa 100644 --- a/provekit/common/src/interner.rs +++ b/provekit/common/src/interner.rs @@ -1,12 +1,48 @@ use { crate::{utils::serde_ark, FieldElement}, - serde::{Deserialize, Serialize}, + ark_ff::PrimeField, + serde::{Deserialize, Deserializer, Serialize}, + std::collections::HashMap, }; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +type FieldKey = [u64; 4]; + +#[inline(always)] +fn field_to_key(value: FieldElement) -> FieldKey { + value.into_bigint().0 +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct Interner { #[serde(with = "serde_ark")] - values: Vec, + values: Vec, + #[serde(skip)] + index_map: HashMap, +} + +impl<'de> Deserialize<'de> for Interner { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct InternerData { + #[serde(with = "serde_ark")] + values: Vec, + } + + let data = InternerData::deserialize(deserializer)?; + + let mut index_map = HashMap::with_capacity(data.values.len()); + for (index, &value) in data.values.iter().enumerate() { + index_map.insert(field_to_key(value), index); + } + + Ok(Self { + values: data.values, + index_map, + }) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -19,24 +55,35 @@ impl Default for Interner { } impl Interner { - pub const fn new() -> Self { - Self { values: Vec::new() } + pub fn new() -> Self { + Self { + values: Vec::new(), + index_map: HashMap::new(), + } } - /// Interning is slow in favour of faster lookups. pub fn intern(&mut self, value: FieldElement) -> InternedFieldElement { - // Deduplicate - if let Some(index) = self.values.iter().position(|v| *v == value) { + let key = field_to_key(value); + + if let Some(&index) = self.index_map.get(&key) { return InternedFieldElement(index); } - // Insert let index = self.values.len(); self.values.push(value); + self.index_map.insert(key, index); InternedFieldElement(index) } pub fn get(&self, el: InternedFieldElement) -> Option { self.values.get(el.0).copied() } + + pub fn rebuild_index_map(&mut self) { + self.index_map.clear(); + self.index_map.reserve(self.values.len()); + for (index, &value) in self.values.iter().enumerate() { + self.index_map.insert(field_to_key(value), index); + } + } } diff --git a/provekit/common/src/lib.rs b/provekit/common/src/lib.rs index 680715d8..4b48d858 100644 --- a/provekit/common/src/lib.rs +++ b/provekit/common/src/lib.rs @@ -10,17 +10,15 @@ mod verifier; mod whir_r1cs; pub mod witness; -use crate::{ - interner::{InternedFieldElement, Interner}, - sparse_matrix::{HydratedSparseMatrix, SparseMatrix}, -}; +use crate::sparse_matrix::{HydratedSparseMatrix, SparseMatrix}; pub use { acir::FieldElement as NoirElement, + interner::{InternedFieldElement, Interner}, noir_proof_scheme::{NoirProof, NoirProofScheme}, prover::Prover, r1cs::R1CS, + spartan_vm::compiler::Field as FieldElement, verifier::Verifier, - whir::crypto::fields::Field256 as FieldElement, whir_r1cs::{IOPattern, WhirConfig, WhirR1CSProof, WhirR1CSScheme}, }; diff --git a/provekit/common/src/noir_proof_scheme.rs b/provekit/common/src/noir_proof_scheme.rs index 14a3e6ef..1ed692a4 100644 --- a/provekit/common/src/noir_proof_scheme.rs +++ b/provekit/common/src/noir_proof_scheme.rs @@ -1,21 +1,19 @@ use { crate::{ whir_r1cs::{WhirR1CSProof, WhirR1CSScheme}, - witness::{LayeredWitnessBuilders, NoirWitnessGenerator}, - NoirElement, R1CS, + R1CS, }, - acir::circuit::Program, serde::{Deserialize, Serialize}, + spartan_vm::compiled_artifacts::CompiledArtifacts, }; /// A scheme for proving a Noir program. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NoirProofScheme { - pub program: Program, - pub r1cs: R1CS, - pub layered_witness_builders: LayeredWitnessBuilders, - pub witness_generator: NoirWitnessGenerator, - pub whir_for_witness: WhirR1CSScheme, + pub whir_for_witness: WhirR1CSScheme, + pub artifacts: CompiledArtifacts, + /// R1CS in the format expected by the recursive verifier (Go/gnark) + pub r1cs: R1CS, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -26,6 +24,9 @@ pub struct NoirProof { impl NoirProofScheme { #[must_use] pub const fn size(&self) -> (usize, usize) { - (self.r1cs.num_constraints(), self.r1cs.num_witnesses()) + ( + self.artifacts.r1cs.constraints.len(), + self.artifacts.r1cs.witness_layout.algebraic_size, + ) } } diff --git a/provekit/common/src/prover.rs b/provekit/common/src/prover.rs index 87972959..35289357 100644 --- a/provekit/common/src/prover.rs +++ b/provekit/common/src/prover.rs @@ -1,36 +1,30 @@ use { - crate::{ - noir_proof_scheme::NoirProofScheme, - whir_r1cs::WhirR1CSScheme, - witness::{LayeredWitnessBuilders, NoirWitnessGenerator}, - NoirElement, R1CS, - }, - acir::circuit::Program, + crate::{noir_proof_scheme::NoirProofScheme, whir_r1cs::WhirR1CSScheme, R1CS}, serde::{Deserialize, Serialize}, + spartan_vm::CompiledArtifacts, }; /// A prover for a Noir Proof Scheme #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Prover { - pub program: Program, - pub r1cs: R1CS, - pub layered_witness_builders: LayeredWitnessBuilders, - pub witness_generator: NoirWitnessGenerator, - pub whir_for_witness: WhirR1CSScheme, + pub whir_for_witness: WhirR1CSScheme, + pub artifacts: CompiledArtifacts, + pub r1cs: R1CS, } impl Prover { pub fn from_noir_proof_scheme(noir_proof_scheme: NoirProofScheme) -> Self { Self { - program: noir_proof_scheme.program, - r1cs: noir_proof_scheme.r1cs, - layered_witness_builders: noir_proof_scheme.layered_witness_builders, - witness_generator: noir_proof_scheme.witness_generator, - whir_for_witness: noir_proof_scheme.whir_for_witness, + whir_for_witness: noir_proof_scheme.whir_for_witness, + artifacts: noir_proof_scheme.artifacts, + r1cs: noir_proof_scheme.r1cs, } } pub const fn size(&self) -> (usize, usize) { - (self.r1cs.num_constraints(), self.r1cs.num_witnesses()) + ( + self.artifacts.r1cs.constraints.len(), + self.artifacts.r1cs.witness_layout.algebraic_size, + ) } } diff --git a/provekit/common/src/r1cs.rs b/provekit/common/src/r1cs.rs index 48392bb0..a5414f18 100644 --- a/provekit/common/src/r1cs.rs +++ b/provekit/common/src/r1cs.rs @@ -1,5 +1,5 @@ use { - crate::{FieldElement, HydratedSparseMatrix, Interner, SparseMatrix}, + crate::{FieldElement, HydratedSparseMatrix, InternedFieldElement, Interner, SparseMatrix}, serde::{Deserialize, Serialize}, }; @@ -101,4 +101,28 @@ impl R1CS { ); } } + + pub fn reserve_constraints(&mut self, num_constraints: usize, total_entries: usize) { + let entries_per_matrix = total_entries / 3 + 1; + self.a.reserve(num_constraints, entries_per_matrix); + self.b.reserve(num_constraints, entries_per_matrix); + self.c.reserve(num_constraints, entries_per_matrix); + } + + #[inline] + pub fn push_constraint( + &mut self, + a: impl Iterator, + b: impl Iterator, + c: impl Iterator, + ) { + self.a.push_row(a); + self.b.push_row(b); + self.c.push_row(c); + } + + #[inline] + pub fn intern(&mut self, value: FieldElement) -> InternedFieldElement { + self.interner.intern(value) + } } diff --git a/provekit/common/src/sparse_matrix.rs b/provekit/common/src/sparse_matrix.rs index 7b4ee28a..9cfb6c71 100644 --- a/provekit/common/src/sparse_matrix.rs +++ b/provekit/common/src/sparse_matrix.rs @@ -64,6 +64,23 @@ impl SparseMatrix { self.new_row_indices.resize(rows, self.values.len() as u32); } + pub fn reserve(&mut self, additional_rows: usize, additional_entries: usize) { + self.new_row_indices.reserve(additional_rows); + self.col_indices.reserve(additional_entries); + self.values.reserve(additional_entries); + } + + #[inline] + pub fn push_row(&mut self, entries: impl Iterator) { + self.new_row_indices.push(self.values.len() as u32); + self.num_rows += 1; + for (col, value) in entries { + debug_assert!((col as usize) < self.num_cols, "column index out of bounds"); + self.col_indices.push(col); + self.values.push(value); + } + } + /// Set the value at the given row and column. pub fn set(&mut self, row: usize, col: usize, value: InternedFieldElement) { assert!(row < self.num_rows, "row index out of bounds"); diff --git a/provekit/common/src/utils/mod.rs b/provekit/common/src/utils/mod.rs index a5f6aa5b..a8e76283 100644 --- a/provekit/common/src/utils/mod.rs +++ b/provekit/common/src/utils/mod.rs @@ -68,6 +68,61 @@ pub fn noir_to_native(n: NoirElement) -> FieldElement { FieldElement::from(BigInt(limbs)) } +pub fn convert_spartan_r1cs_to_provekit( + spartan_r1cs: &spartan_vm::compiler::r1cs_gen::R1CS, +) -> crate::R1CS { + let num_witnesses = spartan_r1cs.witness_layout.size(); + let num_constraints = spartan_r1cs.constraints.len(); + + let total_entries: usize = spartan_r1cs + .constraints + .iter() + .map(|c| c.a.len() + c.b.len() + c.c.len()) + .sum(); + + let mut r1cs = crate::R1CS::new(); + r1cs.add_witnesses(num_witnesses); + r1cs.reserve_constraints(num_constraints, total_entries); + + let mut a_buf: Vec<(u32, crate::InternedFieldElement)> = Vec::with_capacity(64); + let mut b_buf: Vec<(u32, crate::InternedFieldElement)> = Vec::with_capacity(64); + let mut c_buf: Vec<(u32, crate::InternedFieldElement)> = Vec::with_capacity(64); + + for constraint in &spartan_r1cs.constraints { + a_buf.clear(); + a_buf.extend( + constraint + .a + .iter() + .map(|(idx, coeff)| (*idx as u32, r1cs.intern(*coeff))), + ); + + b_buf.clear(); + b_buf.extend( + constraint + .b + .iter() + .map(|(idx, coeff)| (*idx as u32, r1cs.intern(*coeff))), + ); + + c_buf.clear(); + c_buf.extend( + constraint + .c + .iter() + .map(|(idx, coeff)| (*idx as u32, r1cs.intern(*coeff))), + ); + + r1cs.push_constraint( + a_buf.iter().copied(), + b_buf.iter().copied(), + c_buf.iter().copied(), + ); + } + + r1cs +} + /// Calculates the degree of the next smallest power of two pub const fn next_power_of_two(n: usize) -> usize { let mut power = 1; diff --git a/provekit/common/src/utils/sumcheck.rs b/provekit/common/src/utils/sumcheck.rs index 7e1c5a24..6213a57b 100644 --- a/provekit/common/src/utils/sumcheck.rs +++ b/provekit/common/src/utils/sumcheck.rs @@ -5,6 +5,7 @@ use { }, ark_std::{One, Zero}, rayon::iter::{IndexedParallelIterator as _, IntoParallelRefIterator, ParallelIterator as _}, + spartan_vm::{api as spartan_api, compiled_artifacts::CompiledArtifacts}, spongefish::codecs::arkworks_algebra::FieldDomainSeparator, std::array, tracing::instrument, @@ -216,3 +217,18 @@ pub fn calculate_external_row_of_r1cs_matrices( ); [a, b, c] } + +#[instrument(skip_all)] +pub fn calculate_external_row_of_r1cs_matrices_with_ad( + alpha: Vec, + r1cs: R1CS, + artifacts: &mut CompiledArtifacts, +) -> [Vec; 3] { + let eq_alpha = calculate_evaluations_over_boolean_hypercube_for_eq(alpha); + let eq_alpha = &eq_alpha[..r1cs.num_constraints()]; + + let (ad_a, ad_b, ad_c, _ad_instrumenter) = + spartan_api::run_ad_from_binary(&mut artifacts.ad_binary, &artifacts.r1cs, &eq_alpha); + + [ad_a, ad_b, ad_c] +} diff --git a/provekit/prover/Cargo.toml b/provekit/prover/Cargo.toml index f031a3b2..1d24f089 100644 --- a/provekit/prover/Cargo.toml +++ b/provekit/prover/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true # Workspace crates provekit-common.workspace = true skyscraper.workspace = true +spartan-vm.workspace = true # Noir language acir.workspace = true @@ -21,6 +22,7 @@ noir_artifact_cli.workspace = true noirc_abi.workspace = true # Cryptography and proof systems +ark-bn254.workspace = true ark-ff.workspace = true ark-std.workspace = true spongefish.workspace = true diff --git a/provekit/prover/src/lib.rs b/provekit/prover/src/lib.rs index 46d8ddd4..18ef4543 100644 --- a/provekit/prover/src/lib.rs +++ b/provekit/prover/src/lib.rs @@ -1,31 +1,19 @@ use { - crate::{ - r1cs::R1CSSolver, - whir_r1cs::WhirR1CSProver, - witness::{fill_witness, witness_io_pattern::WitnessIOPattern}, - }, - acir::native_types::WitnessMap, + crate::{whir_r1cs::WhirR1CSProver, witness::witness_io_pattern::WitnessIOPattern}, anyhow::{Context, Result}, - bn254_blackbox_solver::Bn254BlackBoxSolver, - nargo::foreign_calls::DefaultForeignCallBuilder, - noir_artifact_cli::fs::inputs::read_inputs_from_file, - noirc_abi::InputMap, provekit_common::{ - skyscraper::SkyscraperSponge, utils::noir_to_native, witness::WitnessBuilder, FieldElement, - IOPattern, NoirElement, NoirProof, Prover, + skyscraper::SkyscraperSponge, utils::convert_spartan_r1cs_to_provekit, FieldElement, + IOPattern, NoirProof, Prover, }, - spongefish::{codecs::arkworks_algebra::FieldToUnitSerialize, ProverState}, + spartan_vm::api as spartan_api, + spongefish::ProverState, std::path::Path, - tracing::instrument, }; -mod r1cs; mod whir_r1cs; mod witness; pub trait Prove { - fn generate_witness(&mut self, input_map: InputMap) -> Result>; - fn prove(self, prover_toml: impl AsRef) -> Result; fn create_witness_io_pattern(&self) -> IOPattern; @@ -33,112 +21,94 @@ pub trait Prove { fn seed_witness_merlin( &mut self, merlin: &mut ProverState, - witness: &WitnessMap, + witness: &Vec, ) -> Result<()>; } impl Prove for Prover { - #[instrument(skip_all)] - fn generate_witness(&mut self, input_map: InputMap) -> Result> { - let solver = Bn254BlackBoxSolver::default(); - let mut output_buffer = Vec::new(); - let mut foreign_call_executor = DefaultForeignCallBuilder { - output: &mut output_buffer, - enable_mocks: false, - resolver_url: None, - root_path: None, - package_name: None, - } - .build(); - - let initial_witness = self.witness_generator.abi().encode(&input_map, None)?; - - let mut witness_stack = nargo::ops::execute_program( - &self.program, - initial_witness, - &solver, - &mut foreign_call_executor, - )?; - - Ok(witness_stack - .pop() - .context("Missing witness results")? - .witness) - } - - #[instrument(skip_all)] fn prove(mut self, prover_toml: impl AsRef) -> Result { - let (input_map, _expected_return) = - read_inputs_from_file(prover_toml.as_ref(), self.witness_generator.abi())?; + // Derive the project directory from the Prover.toml path. + let project_path = prover_toml + .as_ref() + .parent() + .context("Could not derive project path from Prover.toml path")?; - let acir_witness_idx_to_value_map = self.generate_witness(input_map)?; + let (driver, _) = spartan_api::compile_to_r1cs(project_path.to_path_buf(), false)?; - // Solve R1CS instance - let witness_io = self.create_witness_io_pattern(); - let mut witness_merlin = witness_io.to_prover_state(); - self.seed_witness_merlin(&mut witness_merlin, &acir_witness_idx_to_value_map)?; + let params = spartan_api::read_prover_inputs(&project_path.to_path_buf(), driver.abi())?; - let partial_witness = self.r1cs.solve_witness_vec( - self.layered_witness_builders, - acir_witness_idx_to_value_map, - &mut witness_merlin, + let witgen_result = spartan_api::run_witgen_from_binary( + &mut self.artifacts.witgen_binary, + &self.artifacts.r1cs, + ¶ms, ); - let witness = fill_witness(partial_witness).context("while filling witness")?; + let witness: Vec = [ + witgen_result.out_wit_pre_comm.clone(), + witgen_result.out_wit_post_comm.clone(), + ] + .concat(); + + // TODO: Implement witness splitting and commitments + // let witness_io = self.create_witness_io_pattern(); + // let mut witness_merlin = witness_io.to_prover_state(); + // self.seed_witness_merlin(&mut witness_merlin, + // &acir_witness_idx_to_value_map)?; - // Verify witness (redudant with solve) #[cfg(test)] - self.r1cs - .test_witness_satisfaction(&witness) - .context("While verifying R1CS instance")?; + assert!(spartan_api::check_witgen( + &self.artifacts.r1cs, + &witgen_result + )); + + let converted_r1cs = convert_spartan_r1cs_to_provekit(&self.artifacts.r1cs); - // Prove R1CS instance let whir_r1cs_proof = self .whir_for_witness - .prove(self.r1cs, witness) + .prove(converted_r1cs, witness, &mut self.artifacts) .context("While proving R1CS instance")?; Ok(NoirProof { whir_r1cs_proof }) } fn create_witness_io_pattern(&self) -> IOPattern { - let circuit = &self.program.functions[0]; - let public_idxs = circuit.public_inputs().indices(); - let num_challenges = self - .layered_witness_builders - .layers - .iter() - .flat_map(|layer| &layer.witness_builders) - .filter(|b| matches!(b, WitnessBuilder::Challenge(_))) - .count(); - - // Create witness IO pattern + // let circuit = &self.program.functions[0]; + // let public_idxs = circuit.public_inputs().indices(); + // let num_challenges = self + // .layered_witness_builders + // .layers + // .iter() + // .flat_map(|layer| &layer.witness_builders) + // .filter(|b| matches!(b, WitnessBuilder::Challenge(_))) + // .count(); + + // // Create witness IO pattern IOPattern::new("📜") - .add_shape() - .add_public_inputs(public_idxs.len()) - .add_logup_challenges(num_challenges) + // .add_shape() + // .add_public_inputs(public_idxs.len()) + // .add_logup_challenges(num_challenges) } fn seed_witness_merlin( &mut self, merlin: &mut ProverState, - witness: &WitnessMap, + public_inputs: &Vec, ) -> Result<()> { - // Absorb circuit shape - let _ = merlin.add_scalars(&[ - FieldElement::from(self.r1cs.num_constraints() as u64), - FieldElement::from(self.r1cs.num_witnesses() as u64), - ]); - - // Absorb public inputs (values) in canonical order - let circuit = &self.program.functions[0]; - let public_idxs = circuit.public_inputs().indices(); - if !public_idxs.is_empty() { - let pub_vals: Vec = public_idxs - .iter() - .map(|&i| noir_to_native(*witness.get_index(i).expect("missing public input"))) - .collect(); - let _ = merlin.add_scalars(&pub_vals); - } + // // Absorb circuit shape + // let _ = merlin.add_scalars(&[ + // FieldElement::from(self.r1cs.num_constraints() as u64), + // FieldElement::from(self.r1cs.num_witnesses() as u64), + // ]); + + // // Absorb public inputs (values) in canonical order + // let circuit = &self.program.functions[0]; + // let public_idxs = circuit.public_inputs().indices(); + // if !public_idxs.is_empty() { + // let pub_vals: Vec = public_idxs + // .iter() + // .map(|&i| noir_to_native(*witness.get_index(i).expect("missing public + // input"))) .collect(); + // let _ = merlin.add_scalars(&pub_vals); + // } Ok(()) } diff --git a/provekit/prover/src/r1cs.rs b/provekit/prover/src/r1cs.rs deleted file mode 100644 index 4cef6595..00000000 --- a/provekit/prover/src/r1cs.rs +++ /dev/null @@ -1,131 +0,0 @@ -#[cfg(test)] -use anyhow::{ensure, Result}; -use { - crate::witness::witness_builder::WitnessBuilderSolver, - acir::native_types::WitnessMap, - provekit_common::{ - skyscraper::SkyscraperSponge, - utils::batch_inverse_montgomery, - witness::{LayerType, LayeredWitnessBuilders, WitnessBuilder}, - FieldElement, NoirElement, R1CS, - }, - spongefish::ProverState, - tracing::instrument, -}; - -pub trait R1CSSolver { - fn solve_witness_vec( - &self, - plan: LayeredWitnessBuilders, - acir_map: WitnessMap, - transcript: &mut ProverState, - ) -> Vec>; - - #[cfg(test)] - fn test_witness_satisfaction(&self, witness: &[FieldElement]) -> Result<()>; -} - -impl R1CSSolver for R1CS { - /// Solves the R1CS witness vector using layered execution with batch - /// inversion. - /// - /// Executes witness builders in segments: each segment consists of a PRE - /// phase (non-inverse operations) followed by a batch inversion phase. - /// This approach minimizes expensive field inversions by batching them - /// using Montgomery's trick. - /// - /// # Algorithm - /// - /// For each segment: - /// 1. Execute all PRE builders (non-inverse operations) serially - /// 2. Collect denominators from pending inverse operations - /// 3. Perform batch inversion using Montgomery's algorithm - /// 4. Write inverse results to witness vector - /// - /// # Panics - /// - /// Panics if a denominator witness is not set when needed for inversion. - /// This indicates a bug in the layer scheduling algorithm. - #[instrument(skip_all)] - fn solve_witness_vec( - &self, - plan: LayeredWitnessBuilders, - acir_map: WitnessMap, - transcript: &mut ProverState, - ) -> Vec> { - let mut witness = vec![None; self.num_witnesses()]; - - for layer in &plan.layers { - match layer.typ { - LayerType::Other => { - // Execute regular operations - for builder in &layer.witness_builders { - builder.solve(&acir_map, &mut witness, transcript); - } - } - LayerType::Inverse => { - // Execute inverse batch using Montgomery batch inversion - let batch_size = layer.witness_builders.len(); - let mut output_witnesses = Vec::with_capacity(batch_size); - let mut denominators = Vec::with_capacity(batch_size); - - for inverse_builder in &layer.witness_builders { - let WitnessBuilder::Inverse(output_witness, denominator_witness) = - inverse_builder - else { - panic!( - "Invalid builder in inverse batch: expected Inverse, got {:?}", - inverse_builder - ); - }; - - output_witnesses.push(*output_witness); - - let denominator = witness[*denominator_witness].unwrap_or_else(|| { - panic!( - "Denominator witness {} not set before inverse operation", - denominator_witness - ) - }); - denominators.push(denominator); - } - - // Perform batch inversion and write results - let inverses = batch_inverse_montgomery(&denominators); - for (output_witness, inverse_value) in - output_witnesses.into_iter().zip(inverses) - { - witness[output_witness] = Some(inverse_value); - } - } - } - } - - witness - } - - // Tests R1CS Witness satisfaction given the constraints provided by the - // R1CS Matrices. - #[cfg(test)] - #[instrument(skip_all, fields(size = witness.len()))] - fn test_witness_satisfaction(&self, witness: &[FieldElement]) -> Result<()> { - ensure!( - witness.len() == self.num_witnesses(), - "Witness size does not match" - ); - - // Verify - let a = self.a() * witness; - let b = self.b() * witness; - let c = self.c() * witness; - for (row, ((a, b), c)) in a - .into_iter() - .zip(b.into_iter()) - .zip(c.into_iter()) - .enumerate() - { - ensure!(a * b == c, "Constraint {row} failed"); - } - Ok(()) - } -} diff --git a/provekit/prover/src/whir_r1cs.rs b/provekit/prover/src/whir_r1cs.rs index 4f92e79c..95d6e43d 100644 --- a/provekit/prover/src/whir_r1cs.rs +++ b/provekit/prover/src/whir_r1cs.rs @@ -8,14 +8,15 @@ use { pad_to_power_of_two, sumcheck::{ calculate_evaluations_over_boolean_hypercube_for_eq, - calculate_external_row_of_r1cs_matrices, calculate_witness_bounds, eval_cubic_poly, - sumcheck_fold_map_reduce, + calculate_external_row_of_r1cs_matrices_with_ad, calculate_witness_bounds, + eval_cubic_poly, sumcheck_fold_map_reduce, }, zk_utils::{create_masked_polynomial, generate_random_multilinear_polynomial}, HALF, }, FieldElement, IOPattern, WhirConfig, WhirR1CSProof, WhirR1CSScheme, R1CS, }, + spartan_vm::CompiledArtifacts, spongefish::{ codecs::arkworks_algebra::{FieldToUnitSerialize, UnitToField}, ProverState, @@ -33,12 +34,21 @@ use { }; pub trait WhirR1CSProver { - fn prove(&self, r1cs: R1CS, witness: Vec) -> Result; + fn prove( + &self, + r1cs: R1CS, + witness: Vec, + artifacts: &mut CompiledArtifacts, + ) -> Result; } impl WhirR1CSProver for WhirR1CSScheme { - #[instrument(skip_all)] - fn prove(&self, r1cs: R1CS, witness: Vec) -> Result { + fn prove( + &self, + r1cs: R1CS, + witness: Vec, + artifacts: &mut CompiledArtifacts, + ) -> Result { ensure!( witness.len() == r1cs.num_witnesses(), "Unexpected witness length for R1CS instance" @@ -92,7 +102,7 @@ impl WhirR1CSProver for WhirR1CSScheme { drop(z); // Compute weights from R1CS instance - let alphas = calculate_external_row_of_r1cs_matrices(alpha, r1cs); + let alphas = calculate_external_row_of_r1cs_matrices_with_ad(alpha, r1cs, artifacts); let (statement, f_sums, g_sums) = create_combined_statement_over_two_polynomials::<3>( self.m, &commitment_to_witness, @@ -146,10 +156,7 @@ pub fn compute_blinding_coefficients_for_round( for _ in 0..(n - 1 - compute_for) { prefix_multiplier = prefix_multiplier + prefix_multiplier; } - let suffix_multiplier: ark_ff::Fp< - ark_ff::MontBackend, - 4, - > = prefix_multiplier / two; + let suffix_multiplier: FieldElement = prefix_multiplier / two; let constant_term_from_other_items = prefix_multiplier * prefix_sum + suffix_multiplier * suffix_sum; @@ -255,7 +262,6 @@ pub fn pad_to_pow2_len_min2(v: &mut Vec) { } } -#[instrument(skip_all)] pub fn run_zk_sumcheck_prover( r1cs: &R1CS, z: &[FieldElement], @@ -475,7 +481,6 @@ fn create_combined_statement_over_two_polynomials( (statement, f_sums, g_sums) } -#[instrument(skip_all)] pub fn run_zk_whir_pcs_prover( witness: Witness, statement: Statement, diff --git a/provekit/prover/src/witness/digits.rs b/provekit/prover/src/witness/digits.rs deleted file mode 100644 index 409ba279..00000000 --- a/provekit/prover/src/witness/digits.rs +++ /dev/null @@ -1,28 +0,0 @@ -use provekit_common::{ - witness::{decompose_into_digits, DigitalDecompositionWitnesses}, - FieldElement, -}; - -pub(crate) trait DigitalDecompositionWitnessesSolver { - fn solve(&self, witness: &mut [Option]); -} - -impl DigitalDecompositionWitnessesSolver for DigitalDecompositionWitnesses { - fn solve(&self, witness: &mut [Option]) { - self.witnesses_to_decompose - .iter() - .enumerate() - .for_each(|(i, value_witness_idx)| { - let value = witness[*value_witness_idx].unwrap(); - let digits = decompose_into_digits(value, &self.log_bases); - digits - .iter() - .enumerate() - .for_each(|(digit_place, digit_value)| { - witness[self.first_witness_idx - + digit_place * self.witnesses_to_decompose.len() - + i] = Some(*digit_value); - }); - }); - } -} diff --git a/provekit/prover/src/witness/mod.rs b/provekit/prover/src/witness/mod.rs index 2bbcfcea..1b28e163 100644 --- a/provekit/prover/src/witness/mod.rs +++ b/provekit/prover/src/witness/mod.rs @@ -1,30 +1 @@ -use { - anyhow::Result, - provekit_common::FieldElement, - rand::{rng, Rng}, - tracing::{info, instrument}, -}; - -mod digits; -mod ram; -pub(crate) mod witness_builder; pub(crate) mod witness_io_pattern; - -/// Complete a partial witness with random values. -#[instrument(skip_all, fields(size = witness.len()))] -pub(crate) fn fill_witness(witness: Vec>) -> Result> { - // TODO: Use better entropy source and proper sampling. - let mut rng = rng(); - let mut count = 0; - let witness = witness - .iter() - .map(|f| { - f.unwrap_or_else(|| { - count += 1; - FieldElement::from(rng.random::()) - }) - }) - .collect::>(); - info!("Filled witness with {count} random values"); - Ok(witness) -} diff --git a/provekit/prover/src/witness/ram.rs b/provekit/prover/src/witness/ram.rs deleted file mode 100644 index 71e7c0c2..00000000 --- a/provekit/prover/src/witness/ram.rs +++ /dev/null @@ -1,47 +0,0 @@ -use { - ark_ff::PrimeField, - provekit_common::{ - witness::{SpiceMemoryOperation, SpiceWitnesses}, - FieldElement, - }, -}; - -pub(crate) trait SpiceWitnessesSolver { - fn solve(&self, witness: &mut [Option]); -} - -impl SpiceWitnessesSolver for SpiceWitnesses { - fn solve(&self, witness: &mut [Option]) { - let mut rv_final = witness - [self.initial_values_start..self.initial_values_start + self.memory_length] - .to_vec(); - let mut rt_final = vec![0; self.memory_length]; - for (mem_op_index, mem_op) in self.memory_operations.iter().enumerate() { - match mem_op { - SpiceMemoryOperation::Load(addr, value, read_timestamp) => { - let addr = witness[*addr].unwrap(); - let addr_as_usize = addr.into_bigint().0[0] as usize; - witness[*read_timestamp] = - Some(FieldElement::from(rt_final[addr_as_usize] as u64)); - rv_final[addr_as_usize] = witness[*value]; - rt_final[addr_as_usize] = mem_op_index + 1; - } - SpiceMemoryOperation::Store(addr, old_value, new_value, read_timestamp) => { - let addr = witness[*addr].unwrap(); - let addr_as_usize = addr.into_bigint().0[0] as usize; - witness[*old_value] = rv_final[addr_as_usize]; - witness[*read_timestamp] = - Some(FieldElement::from(rt_final[addr_as_usize] as u64)); - let new_value = witness[*new_value]; - rv_final[addr_as_usize] = new_value; - rt_final[addr_as_usize] = mem_op_index + 1; - } - } - } - // Copy the final values and read timestamps into the witness vector - for i in 0..self.memory_length { - witness[self.rv_final_start + i] = rv_final[i]; - witness[self.rt_final_start + i] = Some(FieldElement::from(rt_final[i] as u64)); - } - } -} diff --git a/provekit/prover/src/witness/witness_builder.rs b/provekit/prover/src/witness/witness_builder.rs deleted file mode 100644 index 4739ad9a..00000000 --- a/provekit/prover/src/witness/witness_builder.rs +++ /dev/null @@ -1,270 +0,0 @@ -use { - crate::witness::{digits::DigitalDecompositionWitnessesSolver, ram::SpiceWitnessesSolver}, - acir::native_types::WitnessMap, - ark_ff::{BigInteger, PrimeField}, - ark_std::Zero, - provekit_common::{ - skyscraper::SkyscraperSponge, - utils::noir_to_native, - witness::{ - ConstantOrR1CSWitness, ConstantTerm, ProductLinearTerm, SumTerm, WitnessBuilder, - WitnessCoefficient, BINOP_ATOMIC_BITS, - }, - FieldElement, NoirElement, - }, - spongefish::{codecs::arkworks_algebra::UnitToField, ProverState}, -}; - -pub trait WitnessBuilderSolver { - fn solve( - &self, - acir_witness_idx_to_value_map: &WitnessMap, - witness: &mut [Option], - transcript: &mut ProverState, - ); -} - -impl WitnessBuilderSolver for WitnessBuilder { - fn solve( - &self, - acir_witness_idx_to_value_map: &WitnessMap, - witness: &mut [Option], - transcript: &mut ProverState, - ) { - match self { - WitnessBuilder::Constant(ConstantTerm(witness_idx, c)) => { - witness[*witness_idx] = Some(*c); - } - WitnessBuilder::Acir(witness_idx, acir_witness_idx) => { - witness[*witness_idx] = Some(noir_to_native( - *acir_witness_idx_to_value_map - .get_index(*acir_witness_idx as u32) - .unwrap(), - )); - } - WitnessBuilder::Sum(witness_idx, operands) => { - witness[*witness_idx] = Some( - operands - .iter() - .map(|SumTerm(coeff, witness_idx)| { - if let Some(coeff) = coeff { - *coeff * witness[*witness_idx].unwrap() - } else { - witness[*witness_idx].unwrap() - } - }) - .fold(FieldElement::zero(), |acc, x| acc + x), - ); - } - WitnessBuilder::Product(witness_idx, operand_idx_a, operand_idx_b) => { - let a: FieldElement = witness[*operand_idx_a].unwrap(); - let b: FieldElement = witness[*operand_idx_b].unwrap(); - witness[*witness_idx] = Some(a * b); - } - WitnessBuilder::Inverse(..) => { - unreachable!("Inverse should not be called") - } - WitnessBuilder::IndexedLogUpDenominator( - witness_idx, - sz_challenge, - WitnessCoefficient(index_coeff, index), - rs_challenge, - value, - ) => { - let index = witness[*index].unwrap(); - let value = witness[*value].unwrap(); - let rs_challenge = witness[*rs_challenge].unwrap(); - let sz_challenge = witness[*sz_challenge].unwrap(); - witness[*witness_idx] = - Some(sz_challenge - (*index_coeff * index + rs_challenge * value)); - } - WitnessBuilder::MultiplicitiesForRange(start_idx, range_size, value_witnesses) => { - let mut multiplicities = vec![0u32; *range_size]; - for value_witness_idx in value_witnesses { - // If the value is representable as just a u64, then it should be the least - // significant value in the BigInt representation. - let value = witness[*value_witness_idx].unwrap().into_bigint().0[0]; - multiplicities[value as usize] += 1; - } - for (i, count) in multiplicities.iter().enumerate() { - witness[start_idx + i] = Some(FieldElement::from(*count)); - } - } - WitnessBuilder::Challenge(witness_idx) => { - let mut one = [FieldElement::zero(); 1]; - let _ = transcript.fill_challenge_scalars(&mut one); - witness[*witness_idx] = Some(one[0]); - } - WitnessBuilder::LogUpDenominator( - witness_idx, - sz_challenge, - WitnessCoefficient(value_coeff, value), - ) => { - witness[*witness_idx] = Some( - witness[*sz_challenge].unwrap() - (*value_coeff * witness[*value].unwrap()), - ); - } - WitnessBuilder::ProductLinearOperation( - witness_idx, - ProductLinearTerm(x, a, b), - ProductLinearTerm(y, c, d), - ) => { - witness[*witness_idx] = - Some((*a * witness[*x].unwrap() + *b) * (*c * witness[*y].unwrap() + *d)); - } - WitnessBuilder::DigitalDecomposition(dd_struct) => { - dd_struct.solve(witness); - } - WitnessBuilder::SpiceMultisetFactor( - witness_idx, - sz_challenge, - rs_challenge, - WitnessCoefficient(addr, addr_witness), - value, - WitnessCoefficient(timer, timer_witness), - ) => { - witness[*witness_idx] = Some( - witness[*sz_challenge].unwrap() - - (*addr * witness[*addr_witness].unwrap() - + witness[*rs_challenge].unwrap() * witness[*value].unwrap() - + witness[*rs_challenge].unwrap() - * witness[*rs_challenge].unwrap() - * *timer - * witness[*timer_witness].unwrap()), - ); - } - WitnessBuilder::SpiceWitnesses(spice_witnesses) => { - spice_witnesses.solve(witness); - } - WitnessBuilder::BinOpLookupDenominator( - witness_idx, - sz_challenge, - rs_challenge, - rs_challenge_sqrd, - lhs, - rhs, - output, - ) => { - let lhs = match lhs { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - let rhs = match rhs { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - let output = match output { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - witness[*witness_idx] = Some( - witness[*sz_challenge].unwrap() - - (lhs - + witness[*rs_challenge].unwrap() * rhs - + witness[*rs_challenge_sqrd].unwrap() * output), - ); - } - WitnessBuilder::MultiplicitiesForBinOp(witness_idx, operands) => { - let mut multiplicities = vec![0u32; 2usize.pow(2 * BINOP_ATOMIC_BITS as u32)]; - for (lhs, rhs) in operands { - let lhs = match lhs { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => { - witness[*witness_idx].unwrap() - } - }; - let rhs = match rhs { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => { - witness[*witness_idx].unwrap() - } - }; - let index = - (lhs.into_bigint().0[0] << BINOP_ATOMIC_BITS) + rhs.into_bigint().0[0]; - multiplicities[index as usize] += 1; - } - for (i, count) in multiplicities.iter().enumerate() { - witness[witness_idx + i] = Some(FieldElement::from(*count)); - } - } - WitnessBuilder::U32Addition(result_witness_idx, carry_witness_idx, a, b) => { - let a_val = match a { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(idx) => witness[*idx].unwrap(), - }; - let b_val = match b { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(idx) => witness[*idx].unwrap(), - }; - assert!( - a_val.into_bigint().num_bits() <= 32, - "a_val must be less than or equal to 32 bits, got {}", - a_val.into_bigint().num_bits() - ); - assert!( - b_val.into_bigint().num_bits() <= 32, - "b_val must be less than or equal to 32 bits, got {}", - b_val.into_bigint().num_bits() - ); - let sum = a_val + b_val; - let sum_big = sum.into_bigint(); - let two_pow_32 = 1u64 << 32; - let remainder = sum_big.0[0] % two_pow_32; // result - let quotient = sum_big.0[0] / two_pow_32; // carry - assert!( - quotient == 0 || quotient == 1, - "quotient must be 0 or 1, got {}", - quotient - ); - witness[*result_witness_idx] = Some(FieldElement::from(remainder)); - witness[*carry_witness_idx] = Some(FieldElement::from(quotient)); - } - WitnessBuilder::And(result_witness_idx, lh, rh) => { - let lh_val = match lh { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - let rh_val = match rh { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - assert!( - lh_val.into_bigint().num_bits() <= 32, - "lh_val must be less than or equal to 32 bits, got {}", - lh_val.into_bigint().num_bits() - ); - assert!( - rh_val.into_bigint().num_bits() <= 32, - "rh_val must be less than or equal to 32 bits, got {}", - rh_val.into_bigint().num_bits() - ); - witness[*result_witness_idx] = Some(FieldElement::new( - lh_val.into_bigint() & rh_val.into_bigint(), - )); - } - WitnessBuilder::Xor(result_witness_idx, lh, rh) => { - let lh_val = match lh { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - let rh_val = match rh { - ConstantOrR1CSWitness::Constant(c) => *c, - ConstantOrR1CSWitness::Witness(witness_idx) => witness[*witness_idx].unwrap(), - }; - assert!( - lh_val.into_bigint().num_bits() <= 32, - "lh_val must be less than or equal to 32 bits, got {}", - lh_val.into_bigint().num_bits() - ); - assert!( - rh_val.into_bigint().num_bits() <= 32, - "rh_val must be less than or equal to 32 bits, got {}", - rh_val.into_bigint().num_bits() - ); - witness[*result_witness_idx] = Some(FieldElement::new( - lh_val.into_bigint() ^ rh_val.into_bigint(), - )); - } - } - } -} diff --git a/provekit/r1cs-compiler/Cargo.toml b/provekit/r1cs-compiler/Cargo.toml index ab99798b..742483f0 100644 --- a/provekit/r1cs-compiler/Cargo.toml +++ b/provekit/r1cs-compiler/Cargo.toml @@ -16,6 +16,7 @@ provekit-common.workspace = true acir.workspace = true noirc_abi.workspace = true noirc_artifacts.workspace = true +spartan-vm.workspace = true # Cryptography and proof systems ark-ff.workspace = true diff --git a/provekit/r1cs-compiler/src/noir_proof_scheme.rs b/provekit/r1cs-compiler/src/noir_proof_scheme.rs index b952a8ff..d7d33b49 100644 --- a/provekit/r1cs-compiler/src/noir_proof_scheme.rs +++ b/provekit/r1cs-compiler/src/noir_proof_scheme.rs @@ -1,15 +1,12 @@ use { - crate::{ - noir_to_r1cs, whir_r1cs::WhirR1CSSchemeBuilder, - witness_generator::NoirWitnessGeneratorBuilder, - }, + crate::whir_r1cs::WhirR1CSSchemeBuilder, anyhow::{ensure, Context as _, Result}, noirc_artifacts::program::ProgramArtifact, provekit_common::{ - utils::PrintAbi, - witness::{NoirWitnessGenerator, WitnessBuilder}, + utils::{convert_spartan_r1cs_to_provekit, PrintAbi}, NoirProofScheme, WhirR1CSScheme, }, + spartan_vm::api as spartan_api, std::{fs::File, path::Path}, tracing::{info, instrument}, }; @@ -19,7 +16,7 @@ pub trait NoirProofSchemeBuilder { where Self: Sized; - fn from_program(program: ProgramArtifact) -> Result + fn from_program(program: ProgramArtifact, project_path: impl AsRef) -> Result where Self: Sized; } @@ -27,14 +24,22 @@ pub trait NoirProofSchemeBuilder { impl NoirProofSchemeBuilder for NoirProofScheme { #[instrument(fields(size = path.as_ref().metadata().map(|m| m.len()).ok()))] fn from_file(path: impl AsRef + std::fmt::Debug) -> Result { + let path = path.as_ref(); let file = File::open(path).context("while opening Noir program")?; let program = serde_json::from_reader(file).context("while reading Noir program")?; - Self::from_program(program) + // Derive the project directory from the JSON file path. + // The JSON file is typically at `project/target/name.json`, so we go up 2 + // levels. + let project_path = path + .parent() + .and_then(|p| p.parent()) + .context("Could not derive project path from JSON file path")?; + + Self::from_program(program, project_path) } - #[instrument(skip_all)] - fn from_program(program: ProgramArtifact) -> Result { + fn from_program(program: ProgramArtifact, project_path: impl AsRef) -> Result { info!("Program noir version: {}", program.noir_version); info!("Program entry point: fn main{};", PrintAbi(&program.abi)); ensure!( @@ -42,7 +47,6 @@ impl NoirProofSchemeBuilder for NoirProofScheme { "Program must have one entry point." ); - // Extract bits from Program Artifact. let main = &program.bytecode.functions[0]; info!( "ACIR: {} witnesses, {} opcodes.", @@ -50,31 +54,16 @@ impl NoirProofSchemeBuilder for NoirProofScheme { main.opcodes.len() ); - // Compile to R1CS schemes - let (r1cs, witness_map, witness_builders) = noir_to_r1cs(main)?; - info!( - "R1CS {} constraints, {} witnesses, A {} entries, B {} entries, C {} entries", - r1cs.num_constraints(), - r1cs.num_witnesses(), - r1cs.a.num_entries(), - r1cs.b.num_entries(), - r1cs.c.num_entries() - ); - let layered_witness_builders = WitnessBuilder::prepare_layers(&witness_builders); + let artifacts = + spartan_api::compile_to_artifacts(project_path.as_ref().to_path_buf(), false)?; - // Configure witness generator - let witness_generator = - NoirWitnessGenerator::new(&program, witness_map, r1cs.num_witnesses()); - - // Configure Whir - let whir_for_witness = WhirR1CSScheme::new_for_r1cs(&r1cs); + let whir_for_witness = WhirR1CSScheme::new_from_spartan_r1cs(&artifacts.r1cs); + let r1cs = convert_spartan_r1cs_to_provekit(&artifacts.r1cs); Ok(Self { - program: program.bytecode, - r1cs, - layered_witness_builders, - witness_generator, whir_for_witness, + artifacts, + r1cs, }) } } @@ -113,9 +102,6 @@ mod tests { let path = PathBuf::from("../../tooling/provekit-bench/benches/poseidon_rounds.json"); let proof_schema = NoirProofScheme::from_file(path).unwrap(); - test_serde(&proof_schema.r1cs); - test_serde(&proof_schema.layered_witness_builders); - test_serde(&proof_schema.witness_generator); test_serde(&proof_schema.whir_for_witness); } diff --git a/provekit/r1cs-compiler/src/whir_r1cs.rs b/provekit/r1cs-compiler/src/whir_r1cs.rs index a2811095..c7f9859c 100644 --- a/provekit/r1cs-compiler/src/whir_r1cs.rs +++ b/provekit/r1cs-compiler/src/whir_r1cs.rs @@ -1,5 +1,6 @@ use { provekit_common::{utils::next_power_of_two, WhirConfig, WhirR1CSScheme, R1CS}, + spartan_vm::compiler::r1cs_gen::R1CS as SpartanR1CS, std::sync::Arc, whir::{ ntt::RSDefault, @@ -12,26 +13,54 @@ use { // Minimum log2 of the WHIR evaluation domain (lower bound for m). const MIN_WHIR_NUM_VARIABLES: usize = 12; -// Minimum number of variables in the sumcheck’s multilinear polynomial (lower +// Minimum number of variables in the sumcheck's multilinear polynomial (lower // bound for m_0). const MIN_SUMCHECK_NUM_VARIABLES: usize = 1; pub trait WhirR1CSSchemeBuilder { fn new_for_r1cs(r1cs: &R1CS) -> Self; + fn new_from_spartan_r1cs(r1cs: &SpartanR1CS) -> Self; + + fn new_from_dimensions( + num_witnesses: usize, + num_constraints: usize, + a_num_entries: usize, + ) -> Self; + fn new_whir_config_for_size(num_variables: usize, batch_size: usize) -> WhirConfig; } impl WhirR1CSSchemeBuilder for WhirR1CSScheme { fn new_for_r1cs(r1cs: &R1CS) -> Self { + Self::new_from_dimensions( + r1cs.num_witnesses(), + r1cs.num_constraints(), + r1cs.a().iter().count(), + ) + } + + fn new_from_spartan_r1cs(r1cs: &SpartanR1CS) -> Self { + let num_witnesses = r1cs.witness_layout.size(); + let num_constraints = r1cs.constraints.len(); + let a_num_entries: usize = r1cs.constraints.iter().map(|c| c.a.len()).sum(); + + Self::new_from_dimensions(num_witnesses, num_constraints, a_num_entries) + } + + fn new_from_dimensions( + num_witnesses: usize, + num_constraints: usize, + a_num_entries: usize, + ) -> Self { // m_raw is equal to ceiling(log(number of variables in constraint system)). It // is equal to the log of the width of the matrices. - let m_raw = next_power_of_two(r1cs.num_witnesses()); + let m_raw = next_power_of_two(num_witnesses); // m0_raw is equal to ceiling(log(number_of_constraints)). It is equal to the // number of variables in the multilinear polynomial we are running our sumcheck // on. - let m0_raw = next_power_of_two(r1cs.num_constraints()); + let m0_raw = next_power_of_two(num_constraints); let m = m_raw.max(MIN_WHIR_NUM_VARIABLES); let m_0 = m0_raw.max(MIN_SUMCHECK_NUM_VARIABLES); @@ -40,7 +69,7 @@ impl WhirR1CSSchemeBuilder for WhirR1CSScheme { Self { m: m + 1, m_0, - a_num_terms: next_power_of_two(r1cs.a().iter().count()), + a_num_terms: next_power_of_two(a_num_entries), whir_witness: Self::new_whir_config_for_size(m + 1, 2), whir_for_hiding_spartan: Self::new_whir_config_for_size( next_power_of_two(4 * m_0) + 1, diff --git a/recursive-verifier/app/circuit/common.go b/recursive-verifier/app/circuit/common.go index 52acea04..6f4f0a86 100644 --- a/recursive-verifier/app/circuit/common.go +++ b/recursive-verifier/app/circuit/common.go @@ -115,7 +115,7 @@ func PrepareAndVerifyCircuit(config Config, r1cs R1CS, pk *groth16.ProvingKey, v var interner Interner _, err = arkSerialize.CanonicalDeserializeWithMode( - bytes.NewReader(internerBytes), &interner, false, false, + bytes.NewReader(internerBytes), &interner.Values, false, false, ) if err != nil { return fmt.Errorf("failed to deserialize interner: %w", err) diff --git a/recursive-verifier/app/circuit/matrix_evaluation.go b/recursive-verifier/app/circuit/matrix_evaluation.go index adaa8466..cfd29543 100644 --- a/recursive-verifier/app/circuit/matrix_evaluation.go +++ b/recursive-verifier/app/circuit/matrix_evaluation.go @@ -23,13 +23,11 @@ type InternerAsString struct { } type R1CS struct { - PublicInputs uint64 `json:"public_inputs"` - Witnesses uint64 `json:"witnesses"` - Constraints uint64 `json:"constraints"` - Interner InternerAsString `json:"interner"` - A SparseMatrix `json:"a"` - B SparseMatrix `json:"b"` - C SparseMatrix `json:"c"` + NumPublicInputs uint64 `json:"num_public_inputs"` + Interner InternerAsString `json:"interner"` + A SparseMatrix `json:"a"` + B SparseMatrix `json:"b"` + C SparseMatrix `json:"c"` } type MatrixCell struct { diff --git a/tooling/cli/src/cmd/generate_gnark_inputs.rs b/tooling/cli/src/cmd/generate_gnark_inputs.rs index 42fa6a00..a9d5fe63 100644 --- a/tooling/cli/src/cmd/generate_gnark_inputs.rs +++ b/tooling/cli/src/cmd/generate_gnark_inputs.rs @@ -63,7 +63,7 @@ impl Command for Args { &self.params_for_recursive_verifier, ); - let json = serde_json::to_string_pretty(&prover.r1cs).unwrap(); // Or `to_string` for compact + let json = serde_json::to_string_pretty(&prover.r1cs).unwrap(); let mut file = File::create(&self.r1cs_path)?; file.write_all(json.as_bytes())?;