From b9c684b97cb6783dd394e694b80aca9a6de9819d Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sun, 22 Dec 2024 16:53:57 +0100 Subject: [PATCH 1/9] exploratory work --- Cargo.toml | 24 +++- examples/less_than.rs | 175 ++++++++++++++++------------ src/bellpepper/mod.rs | 79 ++++++------- src/bellpepper/r1cs.rs | 106 ++--------------- src/bellpepper/shape_cs.rs | 4 +- src/bellpepper/solver.rs | 5 +- src/bellpepper/test_shape_cs.rs | 4 +- src/digest.rs | 13 +-- src/lib.rs | 127 ++++++++++++--------- src/provider/ark.rs | 190 +++++++++++++++++++++++++++++++ src/provider/ark_serde.rs | 36 ++++++ src/provider/bn256_grumpkin.rs | 28 ++--- src/provider/ipa_pc.rs | 10 +- src/provider/keccak.rs | 60 ++++++---- src/provider/mod.rs | 108 ++++++------------ src/provider/pasta.rs | 8 ++ src/provider/pedersen.rs | 8 ++ src/provider/secp_secq.rs | 49 +++----- src/r1cs.rs | 49 +++++++- src/spartan/mod.rs | 2 +- src/spartan/polys/eq.rs | 12 +- src/spartan/polys/multilinear.rs | 34 +++--- src/spartan/polys/univariate.rs | 41 +++---- src/spartan/ppsnark.rs | 104 +++++++++++++---- src/spartan/snark.rs | 91 ++++++++++----- src/spartan/sumcheck.rs | 3 +- src/traits/mod.rs | 46 ++++---- src/traits/snark.rs | 10 +- 28 files changed, 876 insertions(+), 550 deletions(-) create mode 100644 src/provider/ark.rs create mode 100644 src/provider/ark_serde.rs diff --git a/Cargo.toml b/Cargo.toml index 8aa97b6..850b415 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,6 @@ license-file = "LICENSE" keywords = ["zkSNARKs", "cryptography", "proofs"] [dependencies] -bellpepper-core = "0.2.1" -ff = { version = "0.13.0", features = ["derive"] } digest = "0.10" sha3 = "0.10" rayon = "1.7" @@ -27,13 +25,27 @@ num-traits = "0.2" num-integer = "0.1" serde = { version = "1.0", features = ["derive"] } bincode = "1.3" -flate2 = "1.0" bitvec = "1.0" byteorder = "1.4.3" thiserror = "1.0" halo2curves = { version = "0.4.0", features = ["derive_serde"] } group = "0.13.0" once_cell = "1.18.0" +derive_more = { version = "^1.0.0", features = ["full"] } + +# Arkworks +ark-ec = { version = "^0.5.0", default-features = false } +ark-ff = { version = "^0.5.0", default-features = false } +ark-std = { version = "^0.5.0", default-features = false } +ark-serialize = { version = "^0.5.0", default-features = false, features = ["derive"] } +ark-snark = { version = "^0.5.1", default-features = false } +ark-bls12-381 = { version = "^0.5.0", default-features = false, features = ["curve"] } +ark-relations = { version = "^0.5.1", default-features = false } +zeroize = { version = "^1.8.1", default-features = false } +delegate = "0.13.1" +serde_with = "3.12.0" +sha2 = "0.10.7" +ark-r1cs-std = "0.5.0" [target.'cfg(any(target_arch = "x86_64", target_arch = "aarch64"))'.dependencies] pasta-msm = { version = "0.1.4" } @@ -52,7 +64,11 @@ sha2 = "0.10.7" proptest = "1.2.0" [features] -default = [] +default = [ + "ark-ec/parallel", + "ark-ff/parallel", + "ark-std/parallel", +] # Compiles in portable mode, w/o ISA extensions => binary can be executed on all systems. portable = ["pasta-msm/portable"] flamegraph = ["pprof/flamegraph", "pprof/criterion"] diff --git a/examples/less_than.rs b/examples/less_than.rs index a3bac59..bca5392 100644 --- a/examples/less_than.rs +++ b/examples/less_than.rs @@ -1,57 +1,67 @@ -use bellpepper_core::{ - boolean::AllocatedBit, num::AllocatedNum, Circuit, ConstraintSystem, LinearCombination, - SynthesisError, +use ark_bls12_381::Fr; +use ark_ff::{BigInteger, PrimeField}; +use ark_r1cs_std::alloc::AllocVar; +use ark_r1cs_std::boolean::AllocatedBool; +use ark_r1cs_std::fields::fp::AllocatedFp; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, + Variable, }; -use ff::{PrimeField, PrimeFieldBits}; -use pasta_curves::Fq; +use num_traits::One; use spartan2::{ errors::SpartanError, traits::{snark::RelaxedR1CSSNARKTrait, Group}, SNARK, }; -fn num_to_bits_le_bounded>( - cs: &mut CS, - n: AllocatedNum, +fn num_to_bits_le_bounded( + cs: ConstraintSystemRef, + n: AllocatedFp, num_bits: u8, -) -> Result, SynthesisError> { - let opt_bits = match n.get_value() { - Some(v) => v - .to_le_bits() - .into_iter() - .take(num_bits as usize) - .map(Some) - .collect::>>(), - None => vec![None; num_bits as usize], - }; +) -> Result>, SynthesisError> { + let opt_bits = n + .value()? + .into_bigint() + .to_bits_le() + .into_iter() + .take(num_bits as usize) + .map(Some) + .collect::>>(); // Add one witness per input bit in little-endian bit order let bits_circuit = opt_bits.into_iter() .enumerate() - // AllocateBit enforces the value to be 0 or 1 at the constraint level - .map(|(i, b)| AllocatedBit::alloc(cs.namespace(|| format!("b_{}", i)), b).unwrap()) - .collect::>(); + // Boolean enforces the value to be 0 or 1 at the constraint level + .map(|(i, b)| { + // TODO: Why do I need namespaces here? + let namespaced_cs = Namespace::from(cs.clone()); + // TODO: Is it a "new_input" or a different type of a variable? + AllocatedBool::::new_input(namespaced_cs, || b.ok_or(SynthesisError::AssignmentMissing)) + }) + .collect::>, SynthesisError>>()?; let mut weighted_sum_lc = LinearCombination::zero(); let mut pow2 = F::ONE; for bit in bits_circuit.iter() { - weighted_sum_lc = weighted_sum_lc + (pow2, bit.get_variable()); + weighted_sum_lc = weighted_sum_lc + (pow2, bit.variable()); pow2 = pow2.double(); } - cs.enforce( - || "bit decomposition check", - |lc| lc + &weighted_sum_lc, - |lc| lc + CS::one(), - |lc| lc + n.get_variable(), - ); + // weighted_sum_lc == n + let constraint_lc = weighted_sum_lc - n.variable; + + // Enforce constraint_lc == 0 + let one_lc = LinearCombination::from((One::one(), Variable::One)); + cs.enforce_constraint(constraint_lc, one_lc, LinearCombination::zero()) + .expect("Failed to enforce the linear combination constraint"); Ok(bits_circuit) } -fn get_msb_index(n: F) -> u8 { - n.to_le_bits() +fn get_msb_index(n: F) -> u8 { + n.into_bigint() + .to_bits_le() .into_iter() .enumerate() .rev() @@ -69,12 +79,12 @@ fn get_msb_index(n: F) -> u8 { // a safe version. #[derive(Clone, Debug)] struct LessThanCircuitUnsafe { - bound: F, // Will be a constant in the constraits, not a variable + bound: F, // Will be a constant in the constraints, not a variable input: F, // Will be an input/output variable num_bits: u8, } -impl LessThanCircuitUnsafe { +impl LessThanCircuitUnsafe { fn new(bound: F, input: F, num_bits: u8) -> Self { assert!(get_msb_index(bound) < num_bits); Self { @@ -85,32 +95,46 @@ impl LessThanCircuitUnsafe { } } -impl Circuit for LessThanCircuitUnsafe { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - assert!(F::NUM_BITS > self.num_bits as u32 + 1); +impl ConstraintSynthesizer for LessThanCircuitUnsafe { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + assert!(F::MODULUS_BIT_SIZE > self.num_bits as u32 + 1); - let input = AllocatedNum::alloc(cs.namespace(|| "input"), || Ok(self.input))?; + // TODO: It is possible to create Namespace with an numerical id, tracing::Id. + // Would that be useful? + // TODO: Use ns!() macro instead + let input_ns = Namespace::from(cs.clone()); + let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; - let shifted_diff = AllocatedNum::alloc(cs.namespace(|| "shifted_diff"), || { + let shifted_ns = Namespace::from(cs.clone()); + // TODO: Is this an input or a variable? + let shifted_diff = AllocatedFp::::new_input(shifted_ns, || { Ok(self.input + F::from(1 << self.num_bits) - self.bound) })?; - cs.enforce( - || "shifted_diff_computation", - |lc| lc + input.get_variable() + (F::from(1 << self.num_bits) - self.bound, CS::one()), - |lc: LinearCombination| lc + CS::one(), - |lc| lc + shifted_diff.get_variable(), - ); + let shifted_diff_lc = LinearCombination::from(input.variable) + + LinearCombination::from((F::from(1 << self.num_bits) - self.bound, Variable::One)) + - shifted_diff.variable; + + // Enforce the linear combination (shifted_diff_lc == 0) + cs.enforce_constraint( + shifted_diff_lc, + LinearCombination::from((F::ONE, Variable::One)), + LinearCombination::zero(), + )?; + + let shifted_diff_bits = + num_to_bits_le_bounded::(cs.clone(), shifted_diff, self.num_bits + 1)?; - let shifted_diff_bits = num_to_bits_le_bounded::(cs, shifted_diff, self.num_bits + 1)?; + // Check that the last (i.e. most significant) bit is 0 + let msb_var = shifted_diff_bits[self.num_bits as usize].variable(); + let zero_lc = LinearCombination::zero(); - // Check that the last (i.e. most sifnificant) bit is 0 - cs.enforce( - || "bound_check", - |lc| lc + shifted_diff_bits[self.num_bits as usize].get_variable(), - |lc| lc + CS::one(), - |lc| lc + (F::ZERO, CS::one()), - ); + // Enforce the constraint that the most significant bit is 0 + cs.enforce_constraint( + LinearCombination::from((F::ONE, msb_var)), + LinearCombination::from((F::ONE, Variable::One)), + zero_lc, + )?; Ok(()) } @@ -122,13 +146,13 @@ impl Circuit for LessThanCircuitUnsafe { // Furthermore, the input must fit into `num_bits`, which is enforced at the // constraint level. #[derive(Clone, Debug)] -struct LessThanCircuitSafe { +struct LessThanCircuitSafe { bound: F, input: F, num_bits: u8, } -impl LessThanCircuitSafe { +impl LessThanCircuitSafe { fn new(bound: F, input: F, num_bits: u8) -> Self { assert!(get_msb_index(bound) < num_bits); Self { @@ -139,23 +163,26 @@ impl LessThanCircuitSafe { } } -impl Circuit for LessThanCircuitSafe { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - let input = AllocatedNum::alloc(cs.namespace(|| "input"), || Ok(self.input))?; +impl ConstraintSynthesizer for LessThanCircuitSafe { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + // TODO: Do we need to use a namespace here? + let input_ns = Namespace::from(cs.clone()); + let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; // Perform the input bit decomposition check - num_to_bits_le_bounded::(cs, input, self.num_bits)?; + num_to_bits_le_bounded::(cs.clone(), input, self.num_bits)?; + // TODO: Not sure how/why to do this in Arkworks // Entering a new namespace to prefix variables in the // LessThanCircuitUnsafe, thus avoiding name clashes - cs.push_namespace(|| "less_than_safe"); + // cs.push_namespace(|| "less_than_safe"); LessThanCircuitUnsafe { bound: self.bound, input: self.input, num_bits: self.num_bits, } - .synthesize(cs) + .generate_constraints(cs) } } @@ -194,43 +221,43 @@ fn verify_circuit_safe>( } fn main() { - type G = pasta_curves::pallas::Point; + type G = ark_bls12_381::G1Projective; type EE = spartan2::provider::ipa_pc::EvaluationEngine; type S = spartan2::spartan::snark::RelaxedR1CSSNARK; println!("Executing unsafe circuit..."); //Typical example, ok - assert!(verify_circuit_unsafe::(Fq::from(17), Fq::from(9), 10).is_ok()); + assert!(verify_circuit_unsafe::(Fr::from(17), Fr::from(9), 10).is_ok()); // Typical example, err - assert!(verify_circuit_unsafe::(Fq::from(17), Fq::from(20), 10).is_err()); + assert!(verify_circuit_unsafe::(Fr::from(17), Fr::from(20), 10).is_err()); // Edge case, err - assert!(verify_circuit_unsafe::(Fq::from(4), Fq::from(4), 10).is_err()); + assert!(verify_circuit_unsafe::(Fr::from(4), Fr::from(4), 10).is_err()); // Edge case, ok - assert!(verify_circuit_unsafe::(Fq::from(4), Fq::from(3), 10).is_ok()); + assert!(verify_circuit_unsafe::(Fr::from(4), Fr::from(3), 10).is_ok()); // Minimum number of bits for the bound, ok - assert!(verify_circuit_unsafe::(Fq::from(4), Fq::from(3), 3).is_ok()); + assert!(verify_circuit_unsafe::(Fr::from(4), Fr::from(3), 3).is_ok()); // Insufficient number of bits for the input, but this is not detected by the // unsafety of the circuit (compare with the last example below) - // Note that -Fq::one() is corresponds to q - 1 > bound - assert!(verify_circuit_unsafe::(Fq::from(4), -Fq::one(), 3).is_ok()); + // Note that -Fr::one() is corresponds to q - 1 > bound + assert!(verify_circuit_unsafe::(Fr::from(4), -Fr::one(), 3).is_ok()); println!("Unsafe circuit OK"); println!("Executing safe circuit..."); // Typical example, ok - assert!(verify_circuit_safe::(Fq::from(17), Fq::from(9), 10).is_ok()); + assert!(verify_circuit_safe::(Fr::from(17), Fr::from(9), 10).is_ok()); // Typical example, err - assert!(verify_circuit_safe::(Fq::from(17), Fq::from(20), 10).is_err()); + assert!(verify_circuit_safe::(Fr::from(17), Fr::from(20), 10).is_err()); // Edge case, err - assert!(verify_circuit_safe::(Fq::from(4), Fq::from(4), 10).is_err()); + assert!(verify_circuit_safe::(Fr::from(4), Fr::from(4), 10).is_err()); // Edge case, ok - assert!(verify_circuit_safe::(Fq::from(4), Fq::from(3), 10).is_ok()); + assert!(verify_circuit_safe::(Fr::from(4), Fr::from(3), 10).is_ok()); // Minimum number of bits for the bound, ok - assert!(verify_circuit_safe::(Fq::from(4), Fq::from(3), 3).is_ok()); + assert!(verify_circuit_safe::(Fr::from(4), Fr::from(3), 3).is_ok()); // Insufficient number of bits for the input, err (compare with the last example // above). - // Note that -Fq::one() is corresponds to q - 1 > bound - assert!(verify_circuit_safe::(Fq::from(4), -Fq::one(), 3).is_err()); + // Note that -Fr::one() is corresponds to q - 1 > bound + assert!(verify_circuit_safe::(Fr::from(4), -Fr::one(), 3).is_err()); println!("Safe circuit OK"); } diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index bc2b5b7..71ed2cd 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -3,43 +3,41 @@ //! [Bellperson]: https://github.com/filecoin-project/bellperson pub mod r1cs; -pub mod shape_cs; -pub mod solver; -pub mod test_shape_cs; +// pub mod solver; + +// In Arkworks, shape of R1CS is derived from ark_relations::r1cs::ConstraintSystem +// pub mod shape_cs; +// pub mod test_shape_cs; #[cfg(test)] mod tests { - use crate::{ - bellpepper::{ - r1cs::{SpartanShape, SpartanWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, - }, - traits::Group, - }; - use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; - use ff::PrimeField; + use crate::{bellpepper::r1cs::SpartanWitness, traits::Group}; + + use crate::r1cs::{R1CSShape, R1CS}; + use ark_ff::Field; + use ark_relations::r1cs::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; + + fn synthesize_alloc_bit(cs: &mut ConstraintSystem) -> Result<(), SynthesisError> { + // Allocate 'a' as a public input + let a_var = cs.new_input_variable(|| Ok(F::ONE))?; + + // Enforce: a * (1 - a) = 0 (this ensures that this is an 0 or 1) + cs.enforce_constraint( + LinearCombination::from(a_var), // Left: a + LinearCombination::from(Variable::One) - a_var, // Right: 1 - a + LinearCombination::zero(), // Output: 0 + )?; + + // Allocate 'b' as a public input + let b_var = cs.new_input_variable(|| Ok(F::ONE))?; + + // Enforce: b * (1 - b) = 0 (this ensures that b is 0 or 1) + cs.enforce_constraint( + LinearCombination::from(b_var), // Left: b + LinearCombination::from(Variable::One) - b_var, // Right: 1 - b + LinearCombination::zero(), // Output: 0 + )?; - fn synthesize_alloc_bit>( - cs: &mut CS, - ) -> Result<(), SynthesisError> { - // get two bits as input and check that they are indeed bits - let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(Fr::ONE))?; - let _ = a.inputize(cs.namespace(|| "a is input")); - cs.enforce( - || "check a is 0 or 1", - |lc| lc + CS::one() - a.get_variable(), - |lc| lc + a.get_variable(), - |lc| lc, - ); - let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(Fr::ONE))?; - let _ = b.inputize(cs.namespace(|| "b is input")); - cs.enforce( - || "check b is 0 or 1", - |lc| lc + CS::one() - b.get_variable(), - |lc| lc + b.get_variable(), - |lc| lc, - ); Ok(()) } @@ -48,12 +46,16 @@ mod tests { G: Group, { // First create the shape - let mut cs: ShapeCS = ShapeCS::new(); + let mut cs: ConstraintSystem = ConstraintSystem::new(); let _ = synthesize_alloc_bit(&mut cs); - let (shape, ck) = cs.r1cs_shape(); + let r1cs_cm = cs + .to_matrices() + .expect("Failed to convert constraint system to R1CS"); + let shape: R1CSShape = R1CSShape::from(&r1cs_cm); + let ck = R1CS::commitment_key(&shape); // Now get the assignment - let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); + let mut cs: ConstraintSystem = ConstraintSystem::new(); let _ = synthesize_alloc_bit(&mut cs); let (inst, witness) = cs.r1cs_instance_and_witness(&shape, &ck).unwrap(); @@ -63,7 +65,8 @@ mod tests { #[test] fn test_alloc_bit() { - test_alloc_bit_with::(); - test_alloc_bit_with::(); + // test_alloc_bit_with::(); + // test_alloc_bit_with::(); + test_alloc_bit_with::(); } } diff --git a/src/bellpepper/r1cs.rs b/src/bellpepper/r1cs.rs index 62806a0..2283394 100644 --- a/src/bellpepper/r1cs.rs +++ b/src/bellpepper/r1cs.rs @@ -2,15 +2,13 @@ #![allow(non_snake_case)] -use super::{shape_cs::ShapeCS, solver::SatisfyingAssignment, test_shape_cs::TestShapeCS}; use crate::{ errors::SpartanError, - r1cs::{R1CSInstance, R1CSShape, R1CSWitness, R1CS}, + r1cs::{R1CSInstance, R1CSShape, R1CSWitness}, traits::Group, CommitmentKey, }; -use bellpepper_core::{Index, LinearCombination}; -use ff::PrimeField; +use ark_relations::r1cs::ConstraintSystem; /// `SpartanWitness` provide a method for acquiring an `R1CSInstance` and `R1CSWitness` from implementers. pub trait SpartanWitness { @@ -22,23 +20,25 @@ pub trait SpartanWitness { ) -> Result<(R1CSInstance, R1CSWitness), SpartanError>; } +// TODO: Currently not used, move some helper methods here? Or remove it? /// `SpartanShape` provides methods for acquiring `R1CSShape` and `CommitmentKey` from implementers. pub trait SpartanShape { /// Return an appropriate `R1CSShape` and `CommitmentKey` structs. fn r1cs_shape(&self) -> (R1CSShape, CommitmentKey); } -impl SpartanWitness for SatisfyingAssignment -where - G::Scalar: PrimeField, +impl SpartanWitness for ConstraintSystem +// TODO: Not sure I need that +// where +// G::Scalar: PrimeField, { fn r1cs_instance_and_witness( &self, shape: &R1CSShape, ck: &CommitmentKey, ) -> Result<(R1CSInstance, R1CSWitness), SpartanError> { - let W = R1CSWitness::::new(shape, &self.aux_assignment)?; - let X = &self.input_assignment[1..]; + let W = R1CSWitness::::new(shape, &self.witness_assignment)?; + let X = &self.instance_assignment[1..]; let comm_W = W.commit(ck); @@ -47,91 +47,3 @@ where Ok((instance, W)) } } - -macro_rules! impl_spartan_shape { - ( $name:ident) => { - impl SpartanShape for $name - where - G::Scalar: PrimeField, - { - fn r1cs_shape(&self) -> (R1CSShape, CommitmentKey) { - let mut A: Vec<(usize, usize, G::Scalar)> = Vec::new(); - let mut B: Vec<(usize, usize, G::Scalar)> = Vec::new(); - let mut C: Vec<(usize, usize, G::Scalar)> = Vec::new(); - - let mut num_cons_added = 0; - let mut X = (&mut A, &mut B, &mut C, &mut num_cons_added); - - let num_inputs = self.num_inputs(); - let num_constraints = self.num_constraints(); - let num_vars = self.num_aux(); - - for constraint in self.constraints.iter() { - add_constraint( - &mut X, - num_vars, - &constraint.0, - &constraint.1, - &constraint.2, - ); - } - - assert_eq!(num_cons_added, num_constraints); - - let S: R1CSShape = { - // Don't count One as an input for shape's purposes. - let res = R1CSShape::new(num_constraints, num_vars, num_inputs - 1, &A, &B, &C); - res.unwrap() - }; - - let ck = R1CS::::commitment_key(&S); - - (S, ck) - } - } - }; -} - -impl_spartan_shape!(ShapeCS); -impl_spartan_shape!(TestShapeCS); - -fn add_constraint( - X: &mut ( - &mut Vec<(usize, usize, S)>, - &mut Vec<(usize, usize, S)>, - &mut Vec<(usize, usize, S)>, - &mut usize, - ), - num_vars: usize, - a_lc: &LinearCombination, - b_lc: &LinearCombination, - c_lc: &LinearCombination, -) { - let (A, B, C, nn) = X; - let n = **nn; - let one = S::ONE; - - let add_constraint_component = |index: Index, coeff, V: &mut Vec<_>| { - match index { - Index::Input(idx) => { - // Inputs come last, with input 0, reprsenting 'one', - // at position num_vars within the witness vector. - let i = idx + num_vars; - V.push((n, i, one * coeff)) - } - Index::Aux(idx) => V.push((n, idx, one * coeff)), - } - }; - - for (index, coeff) in a_lc.iter() { - add_constraint_component(index.0, coeff, A); - } - for (index, coeff) in b_lc.iter() { - add_constraint_component(index.0, coeff, B) - } - for (index, coeff) in c_lc.iter() { - add_constraint_component(index.0, coeff, C) - } - - **nn += 1; -} diff --git a/src/bellpepper/shape_cs.rs b/src/bellpepper/shape_cs.rs index 51646ff..46f7954 100644 --- a/src/bellpepper/shape_cs.rs +++ b/src/bellpepper/shape_cs.rs @@ -6,9 +6,9 @@ use std::{ }; use crate::traits::Group; -use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; +use ark_ff::{Field, PrimeField}; +use ark_relations::r1cs::Variable; use core::fmt::Write; -use ff::{Field, PrimeField}; #[derive(Clone, Copy)] struct OrderedVariable(Variable); diff --git a/src/bellpepper/solver.rs b/src/bellpepper/solver.rs index 8b1261c..ebe7015 100644 --- a/src/bellpepper/solver.rs +++ b/src/bellpepper/solver.rs @@ -1,9 +1,8 @@ //! Support for generating R1CS witness using bellperson. use crate::traits::Group; -use ff::{Field, PrimeField}; - -use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; +use ark_ff::{AdditiveGroup, Field, PrimeField}; +use ark_relations::r1cs::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; /// A `ConstraintSystem` which calculates witness values for a concrete instance of an R1CS circuit. pub struct SatisfyingAssignment diff --git a/src/bellpepper/test_shape_cs.rs b/src/bellpepper/test_shape_cs.rs index 988835f..03af624 100644 --- a/src/bellpepper/test_shape_cs.rs +++ b/src/bellpepper/test_shape_cs.rs @@ -7,9 +7,9 @@ use std::{ }; use crate::traits::Group; -use bellpepper_core::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; +use ark_ff::{Field, PrimeField}; +use ark_relations::r1cs::Variable; use core::fmt::Write; -use ff::{Field, PrimeField}; #[derive(Clone, Copy)] struct OrderedVariable(Variable); diff --git a/src/digest.rs b/src/digest.rs index c0b1e48..bf7b7d5 100644 --- a/src/digest.rs +++ b/src/digest.rs @@ -1,5 +1,5 @@ +use ark_ff::PrimeField; use bincode::Options; -use ff::PrimeField; use serde::Serialize; use sha3::{Digest, Sha3_256}; use std::io; @@ -47,8 +47,8 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> { }); // turn the bit vector into a scalar - let mut digest = F::ZERO; - let mut coeff = F::ONE; + let mut digest = F::zero(); + let mut coeff = F::one(); for bit in bv { if bit { digest += coeff; @@ -80,9 +80,8 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> { #[cfg(test)] mod tests { - use ff::Field; + use ark_ff::Field; use once_cell::sync::OnceCell; - use pasta_curves::pallas; use serde::{Deserialize, Serialize}; use crate::traits::Group; @@ -115,7 +114,7 @@ mod tests { } } - type G = pallas::Point; + type G = ark_bls12_381::G1Projective; #[test] fn test_digest_field_not_ingested_in_computation() { @@ -123,7 +122,7 @@ mod tests { // let's set up a struct with a weird digest field to make sure the digest computation does not depend of it let oc = OnceCell::new(); - oc.set(::Scalar::ONE).unwrap(); + oc.set(<::Scalar as Field>::ONE).unwrap(); let s2: S = S { i: 42, digest: oc }; diff --git a/src/lib.rs b/src/lib.rs index b55f0f0..7e0d8e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,20 @@ //! This library implements Spartan, a high-speed SNARK. #![deny( - warnings, - unused, + // TODO: Uncomment + // warnings, + // unused, future_incompatible, nonstandard_style, rust_2018_idioms, - missing_docs + // TODO: Uncomment + // missing_docs )] #![allow(non_snake_case)] #![allow(clippy::type_complexity)] #![forbid(unsafe_code)] // private modules -mod bellpepper; +mod bellpepper; // TODO: Replace with arkworks module? mod constants; mod digest; mod r1cs; @@ -23,7 +25,7 @@ pub mod provider; pub mod spartan; pub mod traits; -use bellpepper_core::Circuit; +use ark_relations::r1cs::ConstraintSynthesizer; use core::marker::PhantomData; use errors::SpartanError; use serde::{Deserialize, Serialize}; @@ -65,14 +67,14 @@ pub struct SNARK where G: Group, S: RelaxedR1CSSNARKTrait, - C: Circuit, + C: ConstraintSynthesizer, { snark: S, // snark proving the witness is satisfying _p: PhantomData, _p2: PhantomData, } -impl, C: Circuit> SNARK { +impl, C: ConstraintSynthesizer> SNARK { /// Produces prover and verifier keys for the direct SNARK pub fn setup(circuit: C) -> Result<(ProverKey, VerifierKey), SpartanError> { let (pk, vk) = S::setup(circuit)?; @@ -99,7 +101,7 @@ impl, C: Circuit> SNARK = <::CE as CommitmentEngineTrait>::CommitmentKey; +type CommitmentKey = <::CE as CommitmentEngineTrait>::CommitmentKey; type Commitment = <::CE as CommitmentEngineTrait>::Commitment; type CompressedCommitment = <<::CE as CommitmentEngineTrait>::Commitment as CommitmentTrait>::CompressedCommitment; type CE = ::CE; @@ -107,42 +109,48 @@ type CE = ::CE; #[cfg(test)] mod tests { use super::*; - use crate::provider::{bn256_grumpkin::bn256, secp_secq::secp256k1}; - use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; - use ff::PrimeField; - + use ark_ff::PrimeField; + use ark_relations::lc; + use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError, Variable}; #[derive(Clone, Debug, Default)] struct CubicCircuit {} - impl Circuit for CubicCircuit - where - F: PrimeField, - { - fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { - // Consider a cubic equation: `x^3 + x + 5 = y`, where `x` and `y` are respectively the input and output. - let x = AllocatedNum::alloc(cs.namespace(|| "x"), || Ok(F::ONE + F::ONE))?; - let x_sq = x.square(cs.namespace(|| "x_sq"))?; - let x_cu = x_sq.mul(cs.namespace(|| "x_cu"), &x)?; - let y = AllocatedNum::alloc(cs.namespace(|| "y"), || { - Ok(x_cu.get_value().unwrap() + x.get_value().unwrap() + F::from(5u64)) - })?; - - cs.enforce( - || "y = x^3 + x + 5", - |lc| { - lc + x_cu.get_variable() - + x.get_variable() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - + CS::one() - }, - |lc| lc + CS::one(), - |lc| lc + y.get_variable(), - ); - - let _ = y.inputize(cs.namespace(|| "output")); + impl ConstraintSynthesizer for CubicCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + // Fixed toy values for testing + let x = F::from(2u64); // Example: x = 2 + let y = F::from(15u64); // Example: y = 15 (2^3 + 2 + 5 = 15) + + // 1. Allocate x^2 as an intermediate variable (witness) + let x_squared_var = cs.new_witness_variable(|| Ok(x * x))?; + + // Enforce x^2 = x * x + cs.enforce_constraint( + lc!() + (x, Variable::One), + lc!() + (x, Variable::One), + lc!() + x_squared_var, + )?; + + // 2. Allocate x^3 as another intermediate variable (witness) + let x_cubed_var = cs.new_witness_variable(|| Ok(x * x * x))?; + + // Enforce x^3 = x^2 * x + cs.enforce_constraint( + lc!() + x_squared_var, + lc!() + (x, Variable::One), + lc!() + x_cubed_var, + )?; + + // 3. Add the constraint: x^3 + x + 5 = y + let constant_five = F::from(5u64); + cs.enforce_constraint( + lc!() + x_cubed_var + (x, Variable::One) + (constant_five, Variable::One), + lc!() + Variable::One, // Identity multiplier for y + lc!() + (y, Variable::One), + )?; + + // 4. Expose y as a public input + cs.new_input_variable(|| Ok(y))?; Ok(()) } @@ -150,24 +158,31 @@ mod tests { #[test] fn test_snark() { - type G = pasta_curves::pallas::Point; + type G = ark_bls12_381::G1Projective; type EE = crate::provider::ipa_pc::EvaluationEngine; type S = crate::spartan::snark::RelaxedR1CSSNARK; type Spp = crate::spartan::ppsnark::RelaxedR1CSSNARK; - test_snark_with::(); - test_snark_with::(); - - type G2 = bn256::Point; - type EE2 = crate::provider::ipa_pc::EvaluationEngine; - type S2 = crate::spartan::snark::RelaxedR1CSSNARK; - type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; - test_snark_with::(); - test_snark_with::(); - - type G3 = secp256k1::Point; - type EE3 = crate::provider::ipa_pc::EvaluationEngine; - type S3 = crate::spartan::snark::RelaxedR1CSSNARK; - type S3pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; + // test_snark_with::(); // TODO + // test_snark_with::(); // TODO + + // type G2 = bn256::Point; + // type EE2 = crate::provider::ipa_pc::EvaluationEngine; + // type S2 = crate::spartan::snark::RelaxedR1CSSNARK; + // type S2pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; + // test_snark_with::(); + // test_snark_with::(); + + // type G3 = secp256k1::Point; + // type EE3 = crate::provider::ipa_pc::EvaluationEngine; + // type S3 = crate::spartan::snark::RelaxedR1CSSNARK; + // type S3pp = crate::spartan::ppsnark::RelaxedR1CSSNARK; + // test_snark_with::(); + // test_snark_with::(); + + type G3 = ark_bls12_381::G1Projective; + type EE3 = provider::ipa_pc::EvaluationEngine; + type S3 = spartan::snark::RelaxedR1CSSNARK; + type S3pp = spartan::ppsnark::RelaxedR1CSSNARK; test_snark_with::(); test_snark_with::(); } diff --git a/src/provider/ark.rs b/src/provider/ark.rs new file mode 100644 index 0000000..6bbc8fe --- /dev/null +++ b/src/provider/ark.rs @@ -0,0 +1,190 @@ +use crate::provider::keccak::Keccak256Transcript; +use crate::provider::pedersen::CommitmentEngine; +use crate::traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}; +use ark_bls12_381::g1::Config as G1Config; +use ark_bls12_381::{Fq, Fr, G1Affine, G1Projective}; +use ark_ec::short_weierstrass::SWCurveConfig; +use ark_ec::{ + hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve}, + short_weierstrass::Projective, +}; +use ark_ec::{AffineRepr, CurveGroup, PrimeGroup, VariableBaseMSM}; +use ark_ff::field_hashers::DefaultFieldHasher; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use derive_more::Into; +use num_bigint::BigInt; +use num_traits::{Num, Zero}; +use serde::ser::SerializeStruct; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sha2::Sha256; + +/// Compressed representation of BLS12-381 group elements +#[derive(Clone, Copy, Debug, Eq, PartialEq, CanonicalDeserialize, CanonicalSerialize)] +pub struct BLS12CompressedElement { + repr: [u8; 48], +} + +impl Serialize for BLS12CompressedElement { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("BLS12CompressedElement", 1)?; + state.serialize_field("repr", &self.repr.as_slice())?; + state.end() + } +} + +impl<'de> Deserialize<'de> for BLS12CompressedElement { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let repr: [u8; 48] = { + let repr_vec = Vec::::deserialize(deserializer)?; + if repr_vec.len() != 48 { + return Err(serde::de::Error::custom( + "Invalid length for BLS12CompressedElement repr", + )); + } + let mut repr_arr = [0u8; 48]; + repr_arr.copy_from_slice(&repr_vec); + repr_arr + }; + Ok(BLS12CompressedElement::new(repr)) + } +} + +impl BLS12CompressedElement { + /// Creates a new `BLS12CompressedElement` + pub const fn new(repr: [u8; 48]) -> Self { + Self { repr } + } +} + +impl TranscriptReprTrait for BLS12CompressedElement { + fn to_transcript_bytes(&self) -> Vec { + self.repr.to_vec() + } +} + +impl TranscriptReprTrait for ark_bls12_381::Fq { + fn to_transcript_bytes(&self) -> Vec { + let mut serialized_data = Vec::new(); + self + .serialize_compressed(&mut serialized_data) + .expect("Serialization failed"); + serialized_data + } +} + +impl TranscriptReprTrait for ark_bls12_381::Fr { + fn to_transcript_bytes(&self) -> Vec { + let mut serialized_data = Vec::new(); + self + .serialize_compressed(&mut serialized_data) + .expect("Serialization failed"); + serialized_data + } +} + +impl Group for G1Projective { + type Base = Fq; + type Scalar = Fr; + type CompressedGroupElement = BLS12CompressedElement; + type PreprocessedGroupElement = G1Affine; + type TE = Keccak256Transcript; + type CE = CommitmentEngine; + + fn vartime_multiscalar_mul( + scalars: &[Self::Scalar], + bases: &[Self::PreprocessedGroupElement], + ) -> Self { + // TODO: Properly handle error + VariableBaseMSM::msm(bases, scalars).expect("Failed to perform MSM") + } + + fn compress(&self) -> Self::CompressedGroupElement { + let mut repr = Vec::new(); + self + .into_affine() + .serialize_compressed(&mut repr) + .expect("Serialization should not fail"); + BLS12CompressedElement::new( + repr + .try_into() + .expect("Serialized data has incorrect length"), + ) + } + + fn preprocessed(&self) -> Self::PreprocessedGroupElement { + self.into_affine() + } + + // TODO: This is not actually a label "from_uniform_bytes", fix it + fn from_label(label: &[u8], n: usize) -> Vec { + let domain_separator = b"from_uniform_bytes"; + // TODO: Doesn't work with sha3::Shake256, which was originally used here, what do? + let hasher = MapToCurveBasedHasher::< + Projective, + DefaultFieldHasher, + WBMap, + >::new(domain_separator) + .expect("Failed to create MapToCurveBasedHasher"); + + // Generate `n` curve points from the label + (0..n) + .map(|i| { + let input = [label, &i.to_be_bytes()].concat(); + hasher.hash(&input).expect("Failed to hash to curve") + }) + .collect() + } + + fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { + let affine = self.into_affine(); + if affine.is_zero() { + (Self::Base::zero(), Self::Base::zero(), true) + } else { + let coords = affine + .xy() + .expect("Point is not at infinity; coordinates must exist"); + (coords.0, coords.1, false) + } + } + + fn zero() -> Self { + todo!() + } + fn get_generator() -> Self { + G1Projective::generator() + } + + fn get_curve_params() -> (Self::Base, Self::Base, BigInt) { + let a = ark_bls12_381::g1::Config::COEFF_A; + let b = ark_bls12_381::g1::Config::COEFF_B; + let order = BigInt::from_str_radix( + "52435875175126190479447740508185965837690552500527637822603658699938581184512", + 10, + ) + .unwrap(); + (a, b, order) + } +} + +impl CompressedGroup for BLS12CompressedElement { + type GroupElement = G1Projective; + + fn decompress(&self) -> Option { + G1Affine::deserialize_compressed(&self.repr[..]) + .ok() + .map(Into::into) + } +} + +impl PrimeFieldExt for Fr { + fn from_uniform(bytes: &[u8]) -> Self { + Self::from_be_bytes_mod_order(bytes.try_into().unwrap()) + } +} diff --git a/src/provider/ark_serde.rs b/src/provider/ark_serde.rs new file mode 100644 index 0000000..5abc775 --- /dev/null +++ b/src/provider/ark_serde.rs @@ -0,0 +1,36 @@ +//! Implements serialization helpers for compatibility with ark-serialize + +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use serde::{Deserialize, Deserializer, Serializer}; +use serde_with::{DeserializeAs, SerializeAs}; + +pub struct Canonical(std::marker::PhantomData); + +impl SerializeAs for Canonical +where + T: CanonicalSerialize, +{ + fn serialize_as(source: &T, serializer: S) -> Result + where + S: Serializer, + { + let mut buffer = Vec::new(); + source + .serialize_compressed(&mut buffer) + .map_err(serde::ser::Error::custom)?; + serializer.serialize_bytes(&buffer) + } +} + +impl<'de, T> DeserializeAs<'de, T> for Canonical +where + T: CanonicalDeserialize, +{ + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let bytes: Vec = Deserialize::deserialize(deserializer)?; + T::deserialize_compressed(&*bytes).map_err(serde::de::Error::custom) + } +} diff --git a/src/provider/bn256_grumpkin.rs b/src/provider/bn256_grumpkin.rs index eb3fc9f..1c0c884 100644 --- a/src/provider/bn256_grumpkin.rs +++ b/src/provider/bn256_grumpkin.rs @@ -1,19 +1,8 @@ //! This module implements the Spartan traits for `bn256::Point`, `bn256::Scalar`, `grumpkin::Point`, `grumpkin::Scalar`. -use crate::{ - impl_traits, - provider::{cpu_best_multiexp, keccak::Keccak256Transcript, pedersen::CommitmentEngine}, - traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}, -}; -use digest::{ExtendableOutput, Update}; -use ff::{FromUniformBytes, PrimeField}; -use group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}; -use num_bigint::BigInt; -use num_traits::Num; +use crate::traits::{Group, TranscriptReprTrait}; // Remove this when https://github.com/zcash/pasta_curves/issues/41 resolves -use pasta_curves::arithmetic::{CurveAffine, CurveExt}; -use rayon::prelude::*; -use sha3::Shake256; -use std::io::Read; +use crate::impl_traits; +use group::ff::PrimeField; use halo2curves::bn256::{ G1Affine as Bn256Affine, G1Compressed as Bn256Compressed, G1 as Bn256Point, @@ -21,6 +10,15 @@ use halo2curves::bn256::{ use halo2curves::grumpkin::{ G1Affine as GrumpkinAffine, G1Compressed as GrumpkinCompressed, G1 as GrumpkinPoint, }; +use pasta_curves::arithmetic::{CurveAffine, CurveExt}; +use rayon::prelude::*; +use sha3::Shake256; +use std::io::Read; +use ark_ec::CurveGroup; +use crate::provider::keccak::Keccak256Transcript; +use crate::provider::pedersen::CommitmentEngine; +use crate::provider::AffineRepr; +use crate::provider::VariableBaseMSM; /// Re-exports that give access to the standard aliases used in the code base, for bn256 pub mod bn256 { @@ -66,6 +64,8 @@ impl_traits!( #[cfg(test)] mod tests { + use digest::{ExtendableOutput, Update}; + use group::Curve; use super::*; type G = bn256::Point; diff --git a/src/provider/ipa_pc.rs b/src/provider/ipa_pc.rs index 413ae47..14125a8 100644 --- a/src/provider/ipa_pc.rs +++ b/src/provider/ipa_pc.rs @@ -1,5 +1,6 @@ //! This module implements `EvaluationEngine` using an IPA-based polynomial commitment scheme #![allow(clippy::too_many_arguments)] +use crate::provider::ark_serde::Canonical; use crate::{ errors::SpartanError, provider::pedersen::CommitmentKeyExtTrait, @@ -11,10 +12,11 @@ use crate::{ }, Commitment, CommitmentKey, CompressedCommitment, CE, }; +use ark_ff::{AdditiveGroup, Field}; use core::iter; -use ff::Field; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; use std::marker::PhantomData; /// Provides an implementation of the prover key @@ -152,11 +154,13 @@ impl InnerProductWitness { } /// An inner product argument +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(bound = "")] pub struct InnerProductArgument { L_vec: Vec>, R_vec: Vec>, + #[serde_as(as = "Canonical")] a_hat: G::Scalar, } @@ -235,7 +239,7 @@ where transcript.absorb(b"R", &R); let r = transcript.squeeze(b"r")?; - let r_inverse = r.invert().unwrap(); + let r_inverse = r.inverse().unwrap(); // fold the left half and the right half let a_vec_folded = a_vec[0..n / 2] @@ -324,7 +328,7 @@ where } // compute the inverse once for all entries - acc = acc.invert().unwrap(); + acc = acc.inverse().unwrap(); let mut inv = vec![G::Scalar::ZERO; v.len()]; for i in 0..v.len() { diff --git a/src/provider/keccak.rs b/src/provider/keccak.rs index 2046072..6858564 100644 --- a/src/provider/keccak.rs +++ b/src/provider/keccak.rs @@ -99,11 +99,11 @@ impl TranscriptEngineTrait for Keccak256Transcript { #[cfg(test)] mod tests { use crate::{ - provider::bn256_grumpkin::bn256, - provider::{self, keccak::Keccak256Transcript, secp_secq}, + provider::keccak::Keccak256Transcript, traits::{Group, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait}, }; - use ff::PrimeField; + use ark_serialize::CanonicalSerialize; + use bincode::Options; use rand::Rng; use sha3::{Digest, Keccak256}; @@ -120,7 +120,9 @@ mod tests { // make a challenge let c1: ::Scalar = transcript.squeeze(b"c1").unwrap(); - assert_eq!(hex::encode(c1.to_repr().as_ref()), expected_h1); + let mut buf = Vec::new(); + c1.serialize_compressed(&mut buf).unwrap(); + assert_eq!(hex::encode(buf), expected_h1); // a scalar let s3 = ::Scalar::from(128u64); @@ -130,24 +132,31 @@ mod tests { // make a challenge let c2: ::Scalar = transcript.squeeze(b"c2").unwrap(); - assert_eq!(hex::encode(c2.to_repr().as_ref()), expected_h2); + let mut buf = Vec::new(); + c2.serialize_compressed(&mut buf).unwrap(); + assert_eq!(hex::encode(buf), expected_h2); } #[test] fn test_keccak_transcript() { - test_keccak_transcript_with::( - "5ddffa8dc091862132788b8976af88b9a2c70594727e611c7217ba4c30c8c70a", - "4d4bf42c065870395749fa1c4fb641df1e0d53f05309b03d5b1db7f0be3aa13d", - ); - - test_keccak_transcript_with::( - "9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e", - "bfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18", - ); - - test_keccak_transcript_with::( - "9723aafb69ec8f0e9c7de756df0993247d98cf2b2f72fa353e3de654a177e310", - "a6a90fcb6e1b1a2a2f84c950ef1510d369aea8e42085f5c629bfa66d00255f25", + // test_keccak_transcript_with::( + // "5ddffa8dc091862132788b8976af88b9a2c70594727e611c7217ba4c30c8c70a", + // "4d4bf42c065870395749fa1c4fb641df1e0d53f05309b03d5b1db7f0be3aa13d", + // ); + + // test_keccak_transcript_with::( + // "9fb71e3b74bfd0b60d97349849b895595779a240b92a6fae86bd2812692b6b0e", + // "bfd4c50b7d6317e9267d5d65c985eb455a3561129c0b3beef79bfc8461a84f18", + // ); + + // test_keccak_transcript_with::( + // "9723aafb69ec8f0e9c7de756df0993247d98cf2b2f72fa353e3de654a177e310", + // "a6a90fcb6e1b1a2a2f84c950ef1510d369aea8e42085f5c629bfa66d00255f25", + // ); + + test_keccak_transcript_with::( + "4231f8722433b9500d7ae8e7743102d4ecc07d34bb887dc9aa11697b9c72f403", + "cc0b9d88915cf5a5568368c96882c89f99334877b1dee1363ce66af7f42c8d53", ); } @@ -236,15 +245,20 @@ mod tests { let c1: ::Scalar = transcript.squeeze(b"c1").unwrap(); let c1_bytes = squeeze_for_testing(&manual_transcript[..], 0u16, initial_state, b"c1"); - let to_hex = |g: G::Scalar| hex::encode(g.to_repr().as_ref()); + let to_hex = |g: G::Scalar| { + let mut buf = Vec::new(); + g.serialize_compressed(&mut buf).unwrap(); + hex::encode(buf) + }; assert_eq!(to_hex(c1), to_hex(G::Scalar::from_uniform(&c1_bytes))); } #[test] fn test_keccak_transcript_incremental_vs_explicit() { - test_keccak_transcript_incremental_vs_explicit_with::(); - test_keccak_transcript_incremental_vs_explicit_with::(); - test_keccak_transcript_incremental_vs_explicit_with::(); - test_keccak_transcript_incremental_vs_explicit_with::(); + // test_keccak_transcript_incremental_vs_explicit_with::(); + // test_keccak_transcript_incremental_vs_explicit_with::(); + // test_keccak_transcript_incremental_vs_explicit_with::(); + // test_keccak_transcript_incremental_vs_explicit_with::(); + test_keccak_transcript_incremental_vs_explicit_with::(); } } diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 2ef0027..a8936c2 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -4,47 +4,50 @@ //! `RO` traits with Poseidon //! `EvaluationEngine` with an IPA-based polynomial evaluation argument -pub mod bn256_grumpkin; +// pub mod bn256_grumpkin; // Replaced by ark-bls12-381, TODO: Fix later? pub mod ipa_pc; pub mod keccak; -pub mod pasta; +// pub mod pasta; // Replaced by ark-bls12-381, TODO: Fix later? +pub mod ark; +pub mod ark_serde; pub mod pedersen; -pub mod secp_secq; +// pub mod secp_secq; // Replaced by ark-bls12-381, TODO: Fix later? -use ff::PrimeField; -use pasta_curves::{self, arithmetic::CurveAffine, group::Group as AnotherGroup}; +use ark_ec::AffineRepr; +use ark_ff::{AdditiveGroup, PrimeField}; +use num_traits::Zero; +// TODO: Replaced by VariableMSM from Arkworks, remove? /// Native implementation of fast multiexp /// Adapted from zcash/halo2 -fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: &mut C::Curve) { - let coeffs: Vec<_> = coeffs.iter().map(|a| a.to_repr()).collect(); +fn cpu_multiexp_serial(coeffs: &[C::ScalarField], bases: &[C], acc: &mut C::Group) +where + C: AffineRepr, + C::ScalarField: PrimeField, +{ + let coeffs: Vec<_> = coeffs.iter().map(|a| *a).collect(); let c = if bases.len() < 4 { 1 } else if bases.len() < 32 { 3 } else { - (f64::from(bases.len() as u32)).ln().ceil() as usize + f64::from(bases.len() as u32).ln().ceil() as usize }; - fn get_at(segment: usize, c: usize, bytes: &F::Repr) -> usize { + /// Returns the `c`-bit integer value at the specified segment from the given bytes. + fn get_at(segment: usize, c: usize, bytes: &F::BigInt) -> usize { + // Calculate the bit position and check bounds let skip_bits = segment * c; - let skip_bytes = skip_bits / 8; - - if skip_bytes >= 32 { + if skip_bits / 8 >= bytes.as_ref().len() { return 0; } - - let mut v = [0; 8]; - for (v, o) in v.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { - *v = *o; + // Process up to 8 bytes and extract relevant bits + let mut tmp = 0u64; + for (i, &byte) in bytes.as_ref()[skip_bits / 8..].iter().take(8).enumerate() { + tmp |= (byte as u64) << (i * 8); } - - let mut tmp = u64::from_le_bytes(v); - tmp >>= skip_bits - (skip_bytes * 8); - tmp %= 1 << c; - - tmp as usize + (tmp >> (skip_bits % 8)) as usize & ((1 << c) - 1) } let segments = (256 / c) + 1; @@ -55,13 +58,13 @@ fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: & } #[derive(Clone, Copy)] - enum Bucket { + enum Bucket { None, Affine(C), - Projective(C::Curve), + Projective(C::Group), } - impl Bucket { + impl Bucket { fn add_assign(&mut self, other: &C) { *self = match *self { Bucket::None => Bucket::Affine(*other), @@ -73,7 +76,7 @@ fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: & } } - fn add(self, mut other: C::Curve) -> C::Curve { + fn add(self, mut other: C::Group) -> C::Group { match self { Bucket::None => other, Bucket::Affine(a) => { @@ -88,7 +91,8 @@ fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: & let mut buckets: Vec> = vec![Bucket::None; (1 << c) - 1]; for (coeff, base) in coeffs.iter().zip(bases.iter()) { - let coeff = get_at::(current_segment, c, coeff); + let coeff_bigint = coeff.into_bigint(); + let coeff = get_at::(current_segment, c, &coeff_bigint); if coeff != 0 { buckets[coeff - 1].add_assign(base); } @@ -98,52 +102,15 @@ fn cpu_multiexp_serial(coeffs: &[C::Scalar], bases: &[C], acc: & // e.g. 3a + 2b + 1c = a + // (a) + b + // ((a) + b) + c - let mut running_sum = C::Curve::identity(); + let mut running_sum = C::Group::zero(); for exp in buckets.into_iter().rev() { running_sum = exp.add(running_sum); - *acc += &running_sum; + *acc += running_sum; } } } -/// Performs a multi-exponentiation operation without GPU acceleration. -/// -/// This function will panic if coeffs and bases have a different length. -/// -/// This will use multithreading if beneficial. -/// Adapted from zcash/halo2 -pub(crate) fn cpu_best_multiexp(coeffs: &[C::Scalar], bases: &[C]) -> C::Curve { - assert_eq!(coeffs.len(), bases.len()); - - let num_threads = rayon::current_num_threads(); - if coeffs.len() > num_threads { - let chunk = coeffs.len() / num_threads; - let num_chunks = coeffs.chunks(chunk).len(); - let mut results = vec![C::Curve::identity(); num_chunks]; - rayon::scope(|scope| { - for ((coeffs, bases), acc) in coeffs - .chunks(chunk) - .zip(bases.chunks(chunk)) - .zip(results.iter_mut()) - { - scope.spawn(move |_| { - cpu_multiexp_serial(coeffs, bases, acc); - }); - } - }); - results.iter().fold(C::Curve::identity(), |a, b| a + b) - } else { - let mut acc = C::Curve::identity(); - cpu_multiexp_serial(coeffs, bases, &mut acc); - acc - } -} - /// Curve ops -/// This implementation behaves in ways specific to the halo2curves suite of curves in: -// - to_coordinates, -// - vartime_multiscalar_mul, where it does not call into accelerated implementations. -// A specific reimplementation exists for the pasta curves in their own module. #[macro_export] macro_rules! impl_traits { ( @@ -161,11 +128,8 @@ macro_rules! impl_traits { type TE = Keccak256Transcript; type CE = CommitmentEngine; - fn vartime_multiscalar_mul( - scalars: &[Self::Scalar], - bases: &[Self::PreprocessedGroupElement], - ) -> Self { - cpu_best_multiexp(scalars, bases) + fn vartime_multiscalar_mul(scalars: &[Self::Scalar], bases: &[C]) -> C::Group { + VariableBaseMSM::multi_scalar_mul(bases, scalars) } fn preprocessed(&self) -> Self::PreprocessedGroupElement { @@ -176,7 +140,7 @@ macro_rules! impl_traits { self.to_bytes() } - fn from_label(label: &'static [u8], n: usize) -> Vec { + fn from_label(label: &'static [u8], n: usize) -> Vec { let mut shake = Shake256::default(); shake.update(label); let mut reader = shake.finalize_xof(); diff --git a/src/provider/pasta.rs b/src/provider/pasta.rs index 9a40f01..9b7affa 100644 --- a/src/provider/pasta.rs +++ b/src/provider/pasta.rs @@ -180,6 +180,14 @@ macro_rules! impl_traits { Some($name_curve::from_bytes(&self.repr).unwrap()) } } + + impl zeroize::DefaultIsZeroes for $name_compressed {} + + impl Default for $name_compressed { + fn default() -> Self { + Self { repr: [0u8; 32]} + } + } }; } diff --git a/src/provider/pedersen.rs b/src/provider/pedersen.rs index 3d687b4..adfea8d 100644 --- a/src/provider/pedersen.rs +++ b/src/provider/pedersen.rs @@ -1,4 +1,5 @@ //! This module provides an implementation of a commitment engine +use crate::provider::ark_serde::Canonical; use crate::{ errors::SpartanError, traits::{ @@ -13,24 +14,31 @@ use core::{ }; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; /// A type that holds commitment generators +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct CommitmentKey { + #[serde_as(as = "Vec>")] ck: Vec, } /// A type that holds a commitment +#[serde_as] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] pub struct Commitment { + #[serde_as(as = "Canonical")] pub(crate) comm: G, } /// A type that holds a compressed commitment +#[serde_as] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] pub struct CompressedCommitment { + #[serde_as(as = "Canonical")] comm: G::CompressedGroupElement, } diff --git a/src/provider/secp_secq.rs b/src/provider/secp_secq.rs index 7247f67..b63992e 100644 --- a/src/provider/secp_secq.rs +++ b/src/provider/secp_secq.rs @@ -1,20 +1,6 @@ //! This module implements the Spartan traits for secp::Point, secp::Scalar, secq::Point, secq::Scalar. -use crate::{ - impl_traits, - provider::{cpu_best_multiexp, keccak::Keccak256Transcript, pedersen::CommitmentEngine}, - traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}, -}; -use digest::{ExtendableOutput, Update}; -use ff::{FromUniformBytes, PrimeField}; -use group::{cofactor::CofactorCurveAffine, Curve, Group as AnotherGroup, GroupEncoding}; -use halo2curves::secp256k1::{Secp256k1, Secp256k1Affine, Secp256k1Compressed}; -use halo2curves::secq256k1::{Secq256k1, Secq256k1Affine, Secq256k1Compressed}; -use num_bigint::BigInt; -use num_traits::Num; -use pasta_curves::arithmetic::{CurveAffine, CurveExt}; -use rayon::prelude::*; -use sha3::Shake256; -use std::io::Read; +use crate::traits::{Group, TranscriptReprTrait}; +use group::ff::PrimeField; /// Re-exports that give access to the standard aliases used in the code base, for secp pub mod secp256k1 { @@ -44,21 +30,22 @@ impl TranscriptReprTrait for secp256k1::Scalar { } } -impl_traits!( - secp256k1, - Secp256k1Compressed, - Secp256k1, - Secp256k1Affine, - "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" -); - -impl_traits!( - secq256k1, - Secq256k1Compressed, - Secq256k1, - Secq256k1Affine, - "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" -); +// TODO: Fails after switching provider/mod.rs to use ark_ff::PrimeField +// impl_traits!( +// secp256k1, +// Secp256k1Compressed, +// Secp256k1, +// Secp256k1Affine, +// "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" +// ); +// +// impl_traits!( +// secq256k1, +// Secq256k1Compressed, +// Secq256k1, +// Secq256k1Affine, +// "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f" +// ); #[cfg(test)] mod tests { diff --git a/src/r1cs.rs b/src/r1cs.rs index 86c71ce..196d3b9 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -1,15 +1,18 @@ //! This module defines R1CS related types and a folding scheme for Relaxed R1CS #![allow(clippy::type_complexity)] +use crate::provider::ark_serde::Canonical; use crate::{ errors::SpartanError, traits::{commitment::CommitmentEngineTrait, Group, TranscriptReprTrait}, Commitment, CommitmentKey, CE, }; +use ark_ff::{AdditiveGroup, Field}; +use ark_relations::r1cs::ConstraintMatrices; use core::{cmp::max, marker::PhantomData}; -use ff::Field; use itertools::concat; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; /// Public parameters for a given R1CS #[derive(Clone, Serialize, Deserialize)] @@ -19,44 +22,84 @@ pub struct R1CS { } /// A type that holds the shape of the R1CS matrices +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct R1CSShape { pub(crate) num_cons: usize, pub(crate) num_vars: usize, pub(crate) num_io: usize, - pub(crate) A: Vec<(usize, usize, G::Scalar)>, + #[serde_as(as = "Vec<(_, _, Canonical)>")] + pub(crate) A: Vec<(usize, usize, G::Scalar)>, // TODO: Consider turning this into a type + #[serde_as(as = "Vec<(_, _, Canonical)>")] pub(crate) B: Vec<(usize, usize, G::Scalar)>, + #[serde_as(as = "Vec<(_, _, Canonical)>")] pub(crate) C: Vec<(usize, usize, G::Scalar)>, } +impl From<&ConstraintMatrices> for R1CSShape { + fn from(r1cs_cm: &ConstraintMatrices) -> Self { + Self { + num_cons: r1cs_cm.num_constraints, + num_vars: r1cs_cm.num_instance_variables, + num_io: r1cs_cm.num_witness_variables, // TODO: Is this correct? + A: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), + B: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), + C: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.c), + } + } +} + +impl R1CSShape { + /// Helper method to flatten and index a nested R1CS matrix with tuples + pub fn flatten_r1cs_cm( + nested_matrix: &[Vec<(G::Scalar, usize)>], + ) -> Vec<(usize, usize, G::Scalar)> { + nested_matrix + .iter() + .enumerate() + .flat_map(|(row, cols)| cols.iter().map(move |(value, col)| (row, *col, *value))) + .collect() + } +} + /// A type that holds a witness for a given R1CS instance +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct R1CSWitness { + #[serde_as(as = "Vec>")] W: Vec, } /// A type that holds an R1CS instance +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] pub struct R1CSInstance { pub(crate) comm_W: Commitment, + #[serde_as(as = "Vec>")] pub(crate) X: Vec, } /// A type that holds a witness for a given Relaxed R1CS instance +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RelaxedR1CSWitness { + #[serde_as(as = "Vec>")] pub(crate) W: Vec, + #[serde_as(as = "Vec>")] pub(crate) E: Vec, } /// A type that holds a Relaxed R1CS instance +#[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(bound = "")] pub struct RelaxedR1CSInstance { pub(crate) comm_W: Commitment, pub(crate) comm_E: Commitment, + #[serde_as(as = "Vec>")] pub(crate) X: Vec, + #[serde_as(as = "Canonical")] pub(crate) u: G::Scalar, } @@ -294,7 +337,7 @@ impl R1CSShape { } /// Pads the R1CSShape so that the number of variables is a power of two - /// Renumbers variables to accomodate padded variables + /// Renumbers variables to accommodate padded variables pub fn pad(&self) -> Self { // equalize the number of variables and constraints let m = max(self.num_vars, self.num_cons).next_power_of_two(); diff --git a/src/spartan/mod.rs b/src/spartan/mod.rs index d8ae008..52c923f 100644 --- a/src/spartan/mod.rs +++ b/src/spartan/mod.rs @@ -12,7 +12,7 @@ pub mod snark; mod sumcheck; use crate::{traits::Group, Commitment}; -use ff::Field; +use ark_ff::{AdditiveGroup, Field}; use polys::multilinear::SparsePolynomial; fn powers(s: &G::Scalar, n: usize) -> Vec { diff --git a/src/spartan/polys/eq.rs b/src/spartan/polys/eq.rs index cbe0884..aa7a50f 100644 --- a/src/spartan/polys/eq.rs +++ b/src/spartan/polys/eq.rs @@ -1,6 +1,6 @@ //! `EqPolynomial`: Represents multilinear extension of equality polynomials, evaluated based on binary input values. -use ff::PrimeField; +use ark_ff::PrimeField; use rayon::iter::IntoParallelIterator; use rayon::prelude::{IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator}; @@ -71,10 +71,7 @@ impl EqPolynomial { #[cfg(test)] mod tests { - use crate::provider; - use super::*; - use pasta_curves::Fp; fn test_eq_polynomial_with() { let eq_poly = EqPolynomial::::new(vec![F::ONE, F::ZERO, F::ONE]); @@ -96,8 +93,9 @@ mod tests { #[test] fn test_eq_polynomial() { - test_eq_polynomial_with::(); - test_eq_polynomial_with::(); - test_eq_polynomial_with::(); + // test_eq_polynomial_with::(); + // test_eq_polynomial_with::(); + // test_eq_polynomial_with::(); + test_eq_polynomial_with::(); } } diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index 9ed128d..c398a3c 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -4,7 +4,7 @@ use std::ops::{Add, Index}; -use ff::PrimeField; +use ark_ff::PrimeField; use rayon::prelude::{ IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, @@ -196,10 +196,11 @@ impl Add for MultilinearPolynomial { #[cfg(test)] mod tests { - use crate::provider::{self, bn256_grumpkin::bn256, secp_secq::secp256k1}; + // use crate::provider::{self, bn256_grumpkin::bn256, secp_secq::secp256k1}; use super::*; - use pasta_curves::Fp; + use ark_bls12_381::Fr; + use ark_ff::Fp; fn make_mlp(len: usize, value: F) -> MultilinearPolynomial { MultilinearPolynomial { @@ -251,12 +252,12 @@ mod tests { #[test] fn test_multilinear_polynomial() { - test_multilinear_polynomial_with::(); + test_multilinear_polynomial_with::(); } #[test] fn test_sparse_polynomial() { - test_sparse_polynomial_with::(); + test_sparse_polynomial_with::(); } fn test_mlp_add_with() { @@ -278,23 +279,25 @@ mod tests { #[test] fn test_mlp_add() { - test_mlp_add_with::(); - test_mlp_add_with::(); - test_mlp_add_with::(); + // test_mlp_add_with::(); + // test_mlp_add_with::(); + // test_mlp_add_with::(); + test_mlp_add_with::(); } #[test] fn test_mlp_scalar_mul() { - test_mlp_scalar_mul_with::(); - test_mlp_scalar_mul_with::(); - test_mlp_scalar_mul_with::(); + // test_mlp_scalar_mul_with::(); + // test_mlp_scalar_mul_with::(); + // test_mlp_scalar_mul_with::(); + test_mlp_scalar_mul_with::(); } fn test_evaluation_with() { let num_evals = 4; let mut evals: Vec = Vec::with_capacity(num_evals); for _ in 0..num_evals { - evals.push(F::from_u128(8)); + evals.push(F::from(8)); } let dense_poly: MultilinearPolynomial = MultilinearPolynomial::new(evals.clone()); @@ -314,8 +317,9 @@ mod tests { #[test] fn test_evaluation() { - test_evaluation_with::(); - test_evaluation_with::(); - test_evaluation_with::(); + // test_evaluation_with::(); + // test_evaluation_with::(); + // test_evaluation_with::(); + test_evaluation_with::(); } } diff --git a/src/spartan/polys/univariate.rs b/src/spartan/polys/univariate.rs index 29040b0..0b67e9d 100644 --- a/src/spartan/polys/univariate.rs +++ b/src/spartan/polys/univariate.rs @@ -1,11 +1,12 @@ //! Main components: //! - UniPoly: an univariate dense polynomial in coefficient form (big endian), //! - CompressedUniPoly: a univariate dense polynomial, compressed (omitted linear term), in coefficient form (little endian), -use ff::PrimeField; +use crate::provider::ark_serde::Canonical; +use crate::traits::{Group, TranscriptReprTrait}; +use ark_ff::PrimeField; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; - -use crate::traits::{Group, TranscriptReprTrait}; +use serde_with::serde_as; // ax^2 + bx + c stored as vec![c, b, a] // ax^3 + bx^2 + cx + d stored as vec![d, c, b, a] @@ -16,8 +17,10 @@ pub struct UniPoly { // ax^2 + bx + c stored as vec![c, a] // ax^3 + bx^2 + cx + d stored as vec![d, c, a] +#[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CompressedUniPoly { + #[serde_as(as = "Vec>")] coeffs_except_linear_term: Vec, } @@ -25,7 +28,7 @@ impl UniPoly { pub fn from_evals(evals: &[Scalar]) -> Self { // we only support degree-2 or degree-3 univariate polynomials assert!(evals.len() == 3 || evals.len() == 4); - let two_inv = Scalar::from(2).invert().unwrap(); + let two_inv = Scalar::from(2).inverse().unwrap(); let coeffs = if evals.len() == 3 { // ax^2 + bx + c let c = evals[0]; @@ -34,7 +37,7 @@ impl UniPoly { vec![c, b, a] } else { // ax^3 + bx^2 + cx + d - let six_inv = Scalar::from(6).invert().unwrap(); + let six_inv = Scalar::from(6).inverse().unwrap(); let d = evals[0]; let a = six_inv @@ -114,8 +117,6 @@ impl TranscriptReprTrait for UniPoly { } #[cfg(test)] mod tests { - use crate::provider::{bn256_grumpkin, secp_secq::secp256k1}; - use super::*; fn test_from_evals_quad_with() { @@ -144,12 +145,13 @@ mod tests { assert_eq!(poly.evaluate(&F::from(3)), e3); } - #[test] - fn test_from_evals_quad() { - test_from_evals_quad_with::(); - test_from_evals_quad_with::(); - test_from_evals_quad_with::(); - } + // TODO: ark_ff::PrimeField is not implemented for these Scalars + // #[test] + // fn test_from_evals_quad() { + // test_from_evals_quad_with::(); + // test_from_evals_quad_with::(); + // test_from_evals_quad_with::(); + // } fn test_from_evals_cubic_with() { // polynomial is x^3 + 2x^2 + 3x + 1 @@ -179,10 +181,11 @@ mod tests { assert_eq!(poly.evaluate(&F::from(4)), e4); } - #[test] - fn test_from_evals_cubic() { - test_from_evals_cubic_with::(); - test_from_evals_cubic_with::(); - test_from_evals_cubic_with::() - } + // TODO: ark_ff::PrimeField is not implemented for these Scalars + // #[test] + // fn test_from_evals_cubic() { + // test_from_evals_cubic_with::(); + // test_from_evals_cubic_with::(); + // test_from_evals_cubic_with::() + // } } diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 4eefd3c..7ee62c4 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -2,14 +2,12 @@ //! sparse multilinear polynomials involved in Spartan's sum-check protocol, thereby providing a preprocessing SNARK //! The verifier in this preprocessing SNARK maintains a commitment to R1CS matrices. This is beneficial when using a //! polynomial commitment scheme in which the verifier's costs is succinct. +use crate::r1cs::R1CS; use crate::{ - bellpepper::{ - r1cs::{SpartanShape, SpartanWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, - }, + bellpepper::r1cs::SpartanWitness, digest::{DigestComputer, SimpleDigestible}, errors::SpartanError, + provider::ark_serde::Canonical, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{ math::Math, @@ -30,12 +28,13 @@ use crate::{ }, Commitment, CommitmentKey, CompressedCommitment, }; -use bellpepper_core::{Circuit, ConstraintSystem}; +use ark_ff::{AdditiveGroup, Field, PrimeField}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef}; use core::{cmp::max, marker::PhantomData}; -use ff::{Field, PrimeField}; use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; fn vec_to_arr(v: Vec) -> [T; N] { v.try_into() @@ -70,22 +69,32 @@ impl IdentityPolynomial { } /// A type that holds `R1CSShape` in a form amenable to memory checking +#[serde_as] #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct R1CSShapeSparkRepr { N: usize, // size of the vectors // dense representation + #[serde_as(as = "Canonical>")] row: Vec, + #[serde_as(as = "Canonical>")] col: Vec, + #[serde_as(as = "Canonical>")] val_A: Vec, + #[serde_as(as = "Canonical>")] val_B: Vec, + #[serde_as(as = "Canonical>")] val_C: Vec, // timestamp polynomials + #[serde_as(as = "Canonical>")] row_read_ts: Vec, + #[serde_as(as = "Canonical>")] row_audit_ts: Vec, + #[serde_as(as = "Canonical>")] col_read_ts: Vec, + #[serde_as(as = "Canonical>")] col_audit_ts: Vec, } @@ -658,6 +667,7 @@ impl SumcheckEngine for InnerSumcheckInstance { } /// A type that represents the prover's key +#[serde_as] #[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] pub struct ProverKey> { @@ -666,6 +676,7 @@ pub struct ProverKey> { S: R1CSShape, S_repr: R1CSShapeSparkRepr, S_comm: R1CSShapeSparkCommitment, + #[serde_as(as = "Canonical")] vk_digest: G::Scalar, // digest of verifier's key } @@ -686,6 +697,7 @@ impl> SimpleDigestible for VerifierKey> { @@ -702,44 +714,72 @@ pub struct RelaxedR1CSSNARK> { comm_E_col: CompressedCommitment, // initial claims + #[serde_as(as = "Canonical")] eval_Az_at_tau: G::Scalar, + #[serde_as(as = "Canonical")] eval_Bz_at_tau: G::Scalar, + #[serde_as(as = "Canonical")] eval_Cz_at_tau: G::Scalar, comm_output_arr: [CompressedCommitment; 8], + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] claims_product_arr: [G::Scalar; 8], // satisfiability sum-check sc_sat: SumcheckProof, // claims from the end of the sum-check + #[serde_as(as = "Canonical")] eval_Az: G::Scalar, + #[serde_as(as = "Canonical")] eval_Bz: G::Scalar, + #[serde_as(as = "Canonical")] eval_Cz: G::Scalar, + #[serde_as(as = "Canonical")] eval_E: G::Scalar, + #[serde_as(as = "Canonical")] eval_E_row: G::Scalar, + #[serde_as(as = "Canonical")] eval_E_col: G::Scalar, + #[serde_as(as = "Canonical")] eval_val_A: G::Scalar, + #[serde_as(as = "Canonical")] eval_val_B: G::Scalar, + #[serde_as(as = "Canonical")] eval_val_C: G::Scalar, + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] eval_left_arr: [G::Scalar; 8], + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] eval_right_arr: [G::Scalar; 8], + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] eval_output_arr: [G::Scalar; 8], + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] eval_input_arr: [G::Scalar; 8], + #[serde_as(as = "Canonical<[G::Scalar; 8]>")] eval_output2_arr: [G::Scalar; 8], + #[serde_as(as = "Canonical")] eval_row: G::Scalar, + #[serde_as(as = "Canonical")] eval_row_read_ts: G::Scalar, + #[serde_as(as = "Canonical")] eval_E_row_at_r_prod: G::Scalar, + #[serde_as(as = "Canonical")] eval_row_audit_ts: G::Scalar, + #[serde_as(as = "Canonical")] eval_col: G::Scalar, + #[serde_as(as = "Canonical")] eval_col_read_ts: G::Scalar, + #[serde_as(as = "Canonical")] eval_E_col_at_r_prod: G::Scalar, + #[serde_as(as = "Canonical")] eval_col_audit_ts: G::Scalar, + #[serde_as(as = "Canonical")] eval_W: G::Scalar, // batch openings of all multilinear polynomials sc_proof_batch: SumcheckProof, + #[serde_as(as = "Canonical<[G::Scalar; 7]>")] evals_batch_arr: [G::Scalar; 7], eval_arg: EE::EvaluationArgument, } @@ -885,37 +925,51 @@ impl> RelaxedR1CSSNARKTrait for Relaxe type ProverKey = ProverKey; type VerifierKey = VerifierKey; - fn setup>( + fn setup>( circuit: C, ) -> Result<(Self::ProverKey, Self::VerifierKey), SpartanError> { - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit.synthesize(&mut cs); - let (S, ck) = cs.r1cs_shape(); - + // Create an Arkworks-based constraint system and convert it to Spartan2 representation + let cs = ConstraintSystem::::new_ref(); + circuit + .generate_constraints(cs.clone()) + .expect("TODO: Handle error"); + let r1cs_cm = cs + .to_matrices() + .expect("Failed to convert constraint system to R1CS"); + let r1cs_shape = R1CSShape::from(&r1cs_cm); + let ck = R1CS::commitment_key(&r1cs_shape); let (pk_ee, vk_ee) = EE::setup(&ck); + let shape_repr = R1CSShapeSparkRepr::new(&r1cs_shape); + let shape_comm = shape_repr.commit(&ck); - let S_repr = R1CSShapeSparkRepr::new(&S); - let S_comm = S_repr.commit(&ck); - - let vk = VerifierKey::new(S.num_cons, S.num_vars, S_comm.clone(), vk_ee); - + let vk = VerifierKey::new( + r1cs_shape.num_cons, + r1cs_shape.num_vars, + shape_comm.clone(), + vk_ee, + ); let pk = ProverKey { ck, pk_ee, - S, - S_repr, - S_comm, + S: r1cs_shape, + S_repr: shape_repr, + S_comm: shape_comm, vk_digest: vk.digest(), }; Ok((pk, vk)) } - /// produces a succinct proof of satisfiability of a `RelaxedR1CS` instance - fn prove>(pk: &Self::ProverKey, circuit: C) -> Result { - let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); - let _ = circuit.synthesize(&mut cs); - + /// Produces a succinct proof of satisfiability of a `RelaxedR1CS` instance. + fn prove>( + pk: &Self::ProverKey, + circuit: C, + ) -> Result { + let cs = ConstraintSystem::::new(); + let cs_ref = ConstraintSystemRef::new(cs.clone()); + circuit + .generate_constraints(cs_ref) + .expect("TODO: Handle error"); let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| SpartanError::UnSat)?; diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 8520650..5ab920f 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -4,14 +4,12 @@ //! description of R1CS matrices. This is essentially optimal for the verifier when using //! an IPA-based polynomial commitment scheme. +use crate::r1cs::R1CS; use crate::{ - bellpepper::{ - r1cs::{SpartanShape, SpartanWitness}, - shape_cs::ShapeCS, - solver::SatisfyingAssignment, - }, + bellpepper::r1cs::SpartanWitness, digest::{DigestComputer, SimpleDigestible}, errors::SpartanError, + provider::ark_serde::Canonical, r1cs::{R1CSShape, RelaxedR1CSInstance, RelaxedR1CSWitness}, spartan::{ polys::{eq::EqPolynomial, multilinear::MultilinearPolynomial, multilinear::SparsePolynomial}, @@ -25,19 +23,24 @@ use crate::{ }, Commitment, CommitmentKey, CompressedCommitment, }; -use bellpepper_core::{Circuit, ConstraintSystem}; -use ff::Field; +use ark_ff::{AdditiveGroup, Field}; +use ark_relations::r1cs::{ + ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, LinearCombination, +}; use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; +use serde_with::serde_as; /// A type that represents the prover's key +#[serde_as] #[derive(Serialize, Deserialize)] #[serde(bound = "")] pub struct ProverKey> { ck: CommitmentKey, pk_ee: EE::ProverKey, S: R1CSShape, + #[serde_as(as = "Canonical")] vk_digest: G::Scalar, // digest of the verifier's key } @@ -78,16 +81,21 @@ impl> VerifierKey { /// A succinct proof of knowledge of a witness to a relaxed R1CS instance /// The proof is produced using Spartan's combination of the sum-check and /// the commitment to a vector viewed as a polynomial commitment +#[serde_as] #[derive(Serialize, Deserialize)] #[serde(bound = "")] pub struct RelaxedR1CSSNARK> { comm_W: CompressedCommitment, sc_proof_outer: SumcheckProof, + #[serde_as(as = "Canonical<(G::Scalar, G::Scalar, G::Scalar)>")] claims_outer: (G::Scalar, G::Scalar, G::Scalar), + #[serde_as(as = "Canonical")] eval_E: G::Scalar, sc_proof_inner: SumcheckProof, + #[serde_as(as = "Canonical")] eval_W: G::Scalar, sc_proof_batch: SumcheckProof, + #[serde_as(as = "Canonical>")] evals_batch: Vec, eval_arg: EE::EvaluationArgument, } @@ -96,32 +104,43 @@ impl> RelaxedR1CSSNARKTrait for Relaxe type ProverKey = ProverKey; type VerifierKey = VerifierKey; - fn setup>( + fn setup>( circuit: C, ) -> Result<(Self::ProverKey, Self::VerifierKey), SpartanError> { - let mut cs: ShapeCS = ShapeCS::new(); - let _ = circuit.synthesize(&mut cs); - + let cs = ConstraintSystem::::new_ref(); + circuit + .generate_constraints(cs.clone()) + .expect("TODO: Handle error"); + let r1cs_cm = cs + .to_matrices() + .expect("Failed to convert constraint system to R1CS"); + + // TODO: Do we still need that padding even if we don't use ShapeCS anymore? // Padding the ShapeCS: constraints (rows) and variables (columns) - let num_constraints = cs.num_constraints(); - + let num_constraints = r1cs_cm.num_constraints; (num_constraints..num_constraints.next_power_of_two()).for_each(|i| { - cs.enforce( - || format!("padding_constraint_{i}"), - |lc| lc, - |lc| lc, - |lc| lc, + cs.enforce_constraint( + LinearCombination::zero(), + LinearCombination::zero(), + LinearCombination::zero(), ) + .expect(&format!("Failed to enforce padding constraint {i}")); }); - let num_vars = cs.num_aux(); - + let num_vars = r1cs_cm.num_instance_variables; (num_vars..num_vars.next_power_of_two()).for_each(|i| { - cs.alloc(|| format!("padding_var_{i}"), || Ok(G::Scalar::ZERO)) - .unwrap(); + cs.enforce_constraint( + LinearCombination::zero(), + LinearCombination::zero(), + LinearCombination::zero(), + ) + .expect(&format!("Failed to enforce padding variable {i}")); }); - let (S, ck) = cs.r1cs_shape(); + // TODO: Generating a R1CS shape, commitment key, etc. from ark-relations R1CS seems to be + // a common operation. Consider bundling it into a utility (like the old r1cs_shape method). + let S = R1CSShape::from(&r1cs_cm); + let ck = R1CS::commitment_key(&S); let (pk_ee, vk_ee) = EE::setup(&ck); @@ -136,17 +155,27 @@ impl> RelaxedR1CSSNARKTrait for Relaxe Ok((pk, vk)) } - /// produces a succinct proof of satisfiability of a `RelaxedR1CS` instance - fn prove>(pk: &Self::ProverKey, circuit: C) -> Result { - let mut cs: SatisfyingAssignment = SatisfyingAssignment::new(); - let _ = circuit.synthesize(&mut cs); + /// Produces a succinct proof of satisfiability of a `RelaxedR1CS` instance. + fn prove>( + pk: &Self::ProverKey, + circuit: C, + ) -> Result { + let cs = ConstraintSystem::::new(); + let cs_ref = ConstraintSystemRef::new(cs.clone()); + circuit + .generate_constraints(cs_ref.clone()) + .expect("TODO: Handle error"); // Padding variables - let num_vars = cs.aux_slice().len(); - + let num_vars = cs_ref.num_instance_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { - cs.alloc(|| format!("padding_var_{i}"), || Ok(G::Scalar::ZERO)) - .unwrap(); + cs_ref + .enforce_constraint( + LinearCombination::zero(), + LinearCombination::zero(), + LinearCombination::zero(), + ) + .expect(&format!("Failed to enforce padding constraint {i}")); }); let (u, w) = cs diff --git a/src/spartan/sumcheck.rs b/src/spartan/sumcheck.rs index a547b0d..f32c9ff 100644 --- a/src/spartan/sumcheck.rs +++ b/src/spartan/sumcheck.rs @@ -1,12 +1,13 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] + use crate::errors::SpartanError; use crate::spartan::polys::{ multilinear::MultilinearPolynomial, univariate::{CompressedUniPoly, UniPoly}, }; use crate::traits::{Group, TranscriptEngineTrait}; -use ff::Field; +use ark_ff::AdditiveGroup; use rayon::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/src/traits/mod.rs b/src/traits/mod.rs index c892ea7..fc840b5 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -4,14 +4,18 @@ use core::{ fmt::Debug, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, }; -use ff::{PrimeField, PrimeFieldBits}; use num_bigint::BigInt; -use serde::{Deserialize, Serialize}; + +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; pub mod commitment; use commitment::CommitmentEngineTrait; +/// TODO: Replaces ff::PrimeFieldBits +// pub trait PrimeFieldBits: CanonicalSerialize + CanonicalDeserialize{}; + /// Represents an element of a group /// This is currently tailored for an elliptic curve group pub trait Group: @@ -26,39 +30,41 @@ pub trait Group: + ScalarMulOwned<::Scalar> + Send + Sync - + Serialize - + for<'de> Deserialize<'de> + + CanonicalSerialize + + CanonicalDeserialize // + Serialize +// + for<'de> Deserialize<'de> { /// A type representing an element of the base field of the group type Base: PrimeField - + PrimeFieldBits - + TranscriptReprTrait - + Serialize - + for<'de> Deserialize<'de>; + // + PrimeFieldBits + + TranscriptReprTrait; /// A type representing an element of the scalar field of the group type Scalar: PrimeField - + PrimeFieldBits + // + PrimeFieldBits + PrimeFieldExt + Send + Sync + TranscriptReprTrait - + Serialize - + for<'de> Deserialize<'de>; + + CanonicalSerialize + + CanonicalSerialize; /// A type representing the compressed version of the group element - type CompressedGroupElement: CompressedGroup - + Serialize - + for<'de> Deserialize<'de>; + type CompressedGroupElement: CompressedGroup; /// A type representing preprocessed group element - type PreprocessedGroupElement: Clone + Debug + Send + Sync + Serialize + for<'de> Deserialize<'de>; + type PreprocessedGroupElement: Clone + + Debug + + Send + + Sync + + CanonicalSerialize + + CanonicalDeserialize; /// A type that provides a generic Fiat-Shamir transcript to be used when externalizing proofs type TE: TranscriptEngineTrait; /// A type that defines a commitment engine over scalars in the group - type CE: CommitmentEngineTrait + Serialize + for<'de> Deserialize<'de>; + type CE: CommitmentEngineTrait; /// A method to compute a multiexponentation fn vartime_multiscalar_mul( @@ -98,12 +104,14 @@ pub trait CompressedGroup: + Send + Sync + TranscriptReprTrait - + Serialize - + for<'de> Deserialize<'de> + + CanonicalSerialize + + CanonicalDeserialize + // + Serialize + // + for<'de> Deserialize<'de> + 'static { /// A type that holds the decompressed version of the compressed group element - type GroupElement: Group + Serialize + for<'de> Deserialize<'de>; + type GroupElement: Group + CanonicalSerialize + CanonicalDeserialize; // Serialize + for<'de> Deserialize<'de>; /// Decompresses the compressed group element fn decompress(&self) -> Option; diff --git a/src/traits/snark.rs b/src/traits/snark.rs index d79996a..db9a61b 100644 --- a/src/traits/snark.rs +++ b/src/traits/snark.rs @@ -1,6 +1,7 @@ //! This module defines a collection of traits that define the behavior of a zkSNARK for RelaxedR1CS + use crate::{errors::SpartanError, traits::Group}; -use bellpepper_core::Circuit; +use ark_relations::r1cs::ConstraintSynthesizer; use serde::{Deserialize, Serialize}; /// A trait that defines the behavior of a zkSNARK @@ -14,12 +15,15 @@ pub trait RelaxedR1CSSNARKTrait: type VerifierKey: Send + Sync + Serialize + for<'de> Deserialize<'de>; /// Produces the keys for the prover and the verifier - fn setup>( + fn setup>( circuit: C, ) -> Result<(Self::ProverKey, Self::VerifierKey), SpartanError>; /// Produces a new SNARK for a relaxed R1CS - fn prove>(pk: &Self::ProverKey, circuit: C) -> Result; + fn prove>( + pk: &Self::ProverKey, + circuit: C, + ) -> Result; /// Verifies a SNARK for a relaxed R1CS fn verify(&self, vk: &Self::VerifierKey, io: &[G::Scalar]) -> Result<(), SpartanError>; From 62218d24aecad169654224a2e8c366e149e14ce3 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 31 Dec 2024 12:46:09 +0100 Subject: [PATCH 2/9] fixing failing tests --- benches/bench.rs | 10 +++++----- examples/less_than.rs | 8 ++++---- src/bellpepper/mod.rs | 25 ++++++++----------------- src/lib.rs | 16 ++++++++-------- src/provider/ark.rs | 4 ++-- src/r1cs.rs | 10 ++++++---- src/spartan/snark.rs | 28 ++++++++-------------------- 7 files changed, 41 insertions(+), 60 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index eab2dcd..9781684 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,20 +1,20 @@ extern crate core; +use ark_bls12_381::Fr; +use ark_ff::UniformRand; use criterion::{criterion_group, criterion_main, Criterion}; -use ff::Field; -use pasta_curves::Fp; use spartan2::spartan::polys::eq::EqPolynomial; fn benchmarks_evaluate_incremental(c: &mut Criterion) { let mut group = c.benchmark_group("evaluate_incremental"); (1..=20).step_by(2).for_each(|i| { - let random_point: Vec = (0..2usize.pow(i)) - .map(|_| Fp::random(&mut rand::thread_rng())) + let random_point: Vec = (0..2usize.pow(i)) + .map(|_| Fr::rand(&mut rand::thread_rng())) .collect(); let random_polynomial = EqPolynomial::new( (0..2usize.pow(i)) - .map(|_| Fp::random(&mut rand::thread_rng())) + .map(|_| Fr::rand(&mut rand::thread_rng())) .collect(), ); group.bench_with_input(format!("2^{}", i), &i, |b, &_i| { diff --git a/examples/less_than.rs b/examples/less_than.rs index bca5392..363a1b5 100644 --- a/examples/less_than.rs +++ b/examples/less_than.rs @@ -194,10 +194,10 @@ fn verify_circuit_unsafe>( let circuit = LessThanCircuitUnsafe::new(bound, input, num_bits); // produce keys - let (pk, vk) = SNARK::>::setup(circuit.clone()).unwrap(); + let (pk, vk) = SNARK::>::setup(circuit.clone())?; // produce a SNARK - let snark = SNARK::prove(&pk, circuit).unwrap(); + let snark = SNARK::prove(&pk, circuit)?; // verify the SNARK snark.verify(&vk, &[]) @@ -211,10 +211,10 @@ fn verify_circuit_safe>( let circuit = LessThanCircuitSafe::new(bound, input, num_bits); // produce keys - let (pk, vk) = SNARK::>::setup(circuit.clone()).unwrap(); + let (pk, vk) = SNARK::>::setup(circuit.clone())?; // produce a SNARK - let snark = SNARK::prove(&pk, circuit).unwrap(); + let snark = SNARK::prove(&pk, circuit)?; // verify the SNARK snark.verify(&vk, &[]) diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index 71ed2cd..7af8d09 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -15,28 +15,21 @@ mod tests { use crate::r1cs::{R1CSShape, R1CS}; use ark_ff::Field; + use ark_relations::lc; use ark_relations::r1cs::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; fn synthesize_alloc_bit(cs: &mut ConstraintSystem) -> Result<(), SynthesisError> { // Allocate 'a' as a public input - let a_var = cs.new_input_variable(|| Ok(F::ONE))?; + let a_var = cs.new_witness_variable(|| Ok(F::ONE))?; - // Enforce: a * (1 - a) = 0 (this ensures that this is an 0 or 1) - cs.enforce_constraint( - LinearCombination::from(a_var), // Left: a - LinearCombination::from(Variable::One) - a_var, // Right: 1 - a - LinearCombination::zero(), // Output: 0 - )?; + // Enforce: a * (1 - a) = 0 (this ensures that 'a' is an 0 or 1) + cs.enforce_constraint(lc!() + a_var, lc!() + Variable::One - a_var, lc!())?; // Allocate 'b' as a public input - let b_var = cs.new_input_variable(|| Ok(F::ONE))?; + let b_var = cs.new_witness_variable(|| Ok(F::ONE))?; - // Enforce: b * (1 - b) = 0 (this ensures that b is 0 or 1) - cs.enforce_constraint( - LinearCombination::from(b_var), // Left: b - LinearCombination::from(Variable::One) - b_var, // Right: 1 - b - LinearCombination::zero(), // Output: 0 - )?; + // Enforce: b * (1 - b) = 0 (this ensures that 'b' is 0 or 1) + cs.enforce_constraint(lc!() + b_var, lc!() + Variable::One - b_var, lc!())?; Ok(()) } @@ -48,9 +41,7 @@ mod tests { // First create the shape let mut cs: ConstraintSystem = ConstraintSystem::new(); let _ = synthesize_alloc_bit(&mut cs); - let r1cs_cm = cs - .to_matrices() - .expect("Failed to convert constraint system to R1CS"); + let r1cs_cm = cs.to_matrices().expect("Failed to convert to R1CS"); let shape: R1CSShape = R1CSShape::from(&r1cs_cm); let ck = R1CS::commitment_key(&shape); diff --git a/src/lib.rs b/src/lib.rs index 7e0d8e9..28c8563 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -111,13 +111,13 @@ mod tests { use super::*; use ark_ff::PrimeField; use ark_relations::lc; - use ark_relations::r1cs::{ConstraintSystemRef, LinearCombination, SynthesisError, Variable}; + use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError, Variable}; #[derive(Clone, Debug, Default)] struct CubicCircuit {} impl ConstraintSynthesizer for CubicCircuit { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - // Fixed toy values for testing + // Fixed values for testing let x = F::from(2u64); // Example: x = 2 let y = F::from(15u64); // Example: y = 15 (2^3 + 2 + 5 = 15) @@ -142,9 +142,8 @@ mod tests { )?; // 3. Add the constraint: x^3 + x + 5 = y - let constant_five = F::from(5u64); cs.enforce_constraint( - lc!() + x_cubed_var + (x, Variable::One) + (constant_five, Variable::One), + lc!() + x_cubed_var + (x, Variable::One) + (F::from(5u64), Variable::One), lc!() + Variable::One, // Identity multiplier for y lc!() + (y, Variable::One), )?; @@ -159,9 +158,9 @@ mod tests { #[test] fn test_snark() { type G = ark_bls12_381::G1Projective; - type EE = crate::provider::ipa_pc::EvaluationEngine; - type S = crate::spartan::snark::RelaxedR1CSSNARK; - type Spp = crate::spartan::ppsnark::RelaxedR1CSSNARK; + type EE = provider::ipa_pc::EvaluationEngine; + type S = spartan::snark::RelaxedR1CSSNARK; + type Spp = spartan::ppsnark::RelaxedR1CSSNARK; // test_snark_with::(); // TODO // test_snark_with::(); // TODO @@ -200,6 +199,7 @@ mod tests { // verify the SNARK let res = snark.verify(&vk, &[::Scalar::from(15u64)]); - assert!(res.is_ok()); + // assert!(res.is_ok()); + res.unwrap(); } } diff --git a/src/provider/ark.rs b/src/provider/ark.rs index 6bbc8fe..b8dff7e 100644 --- a/src/provider/ark.rs +++ b/src/provider/ark.rs @@ -10,7 +10,7 @@ use ark_ec::{ }; use ark_ec::{AffineRepr, CurveGroup, PrimeGroup, VariableBaseMSM}; use ark_ff::field_hashers::DefaultFieldHasher; -use ark_ff::PrimeField; +use ark_ff::{AdditiveGroup, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use derive_more::Into; use num_bigint::BigInt; @@ -155,7 +155,7 @@ impl Group for G1Projective { } fn zero() -> Self { - todo!() + G1Projective::ZERO } fn get_generator() -> Self { G1Projective::generator() diff --git a/src/r1cs.rs b/src/r1cs.rs index 196d3b9..a9346ea 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -39,9 +39,11 @@ pub struct R1CSShape { impl From<&ConstraintMatrices> for R1CSShape { fn from(r1cs_cm: &ConstraintMatrices) -> Self { Self { - num_cons: r1cs_cm.num_constraints, - num_vars: r1cs_cm.num_instance_variables, - num_io: r1cs_cm.num_witness_variables, // TODO: Is this correct? + // TODO: Is this mapping correct? + // TODO: Revisit implementation of SpartanShape + num_cons: r1cs_cm.num_constraints.next_power_of_two(), + num_vars: r1cs_cm.num_witness_variables.next_power_of_two(), + num_io: r1cs_cm.num_instance_variables - 1, // TODO: Always has one variable by default in ark-relations? A: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), B: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), C: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.c), @@ -411,7 +413,7 @@ impl R1CSWitness { } impl R1CSInstance { - /// A method to create an instance object using consitituent elements + /// A method to create an instance object using constituent elements pub fn new( S: &R1CSShape, comm_W: &Commitment, diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 5ab920f..db51a4d 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -24,9 +24,8 @@ use crate::{ Commitment, CommitmentKey, CompressedCommitment, }; use ark_ff::{AdditiveGroup, Field}; -use ark_relations::r1cs::{ - ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef, LinearCombination, -}; +use ark_relations::lc; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef}; use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -119,22 +118,14 @@ impl> RelaxedR1CSSNARKTrait for Relaxe // Padding the ShapeCS: constraints (rows) and variables (columns) let num_constraints = r1cs_cm.num_constraints; (num_constraints..num_constraints.next_power_of_two()).for_each(|i| { - cs.enforce_constraint( - LinearCombination::zero(), - LinearCombination::zero(), - LinearCombination::zero(), - ) - .expect(&format!("Failed to enforce padding constraint {i}")); + cs.enforce_constraint(lc!(), lc!(), lc!()) + .expect(&format!("Failed to enforce padding constraint {i}")); }); let num_vars = r1cs_cm.num_instance_variables; (num_vars..num_vars.next_power_of_two()).for_each(|i| { - cs.enforce_constraint( - LinearCombination::zero(), - LinearCombination::zero(), - LinearCombination::zero(), - ) - .expect(&format!("Failed to enforce padding variable {i}")); + cs.enforce_constraint(lc!(), lc!(), lc!()) + .expect(&format!("Failed to enforce padding variable {i}")); }); // TODO: Generating a R1CS shape, commitment key, etc. from ark-relations R1CS seems to be @@ -170,14 +161,11 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let num_vars = cs_ref.num_instance_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { cs_ref - .enforce_constraint( - LinearCombination::zero(), - LinearCombination::zero(), - LinearCombination::zero(), - ) + .enforce_constraint(lc!(), lc!(), lc!()) .expect(&format!("Failed to enforce padding constraint {i}")); }); + let cs = cs_ref.borrow().unwrap(); let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| SpartanError::UnSat)?; From 215afc7ee58e5c868506637ddd1357f8c105d36e Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Tue, 31 Dec 2024 13:54:09 +0100 Subject: [PATCH 3/9] clean up a bit --- Cargo.toml | 19 +++---- examples/less_than.rs | 33 +++++------ src/bellpepper/mod.rs | 6 +- src/bellpepper/r1cs.rs | 13 +---- src/lib.rs | 8 +-- src/provider/ark.rs | 1 - src/provider/keccak.rs | 1 - src/provider/mod.rs | 97 -------------------------------- src/r1cs.rs | 3 +- src/spartan/polys/multilinear.rs | 1 - src/spartan/polys/univariate.rs | 28 ++++----- src/spartan/snark.rs | 2 - src/traits/mod.rs | 17 ++---- 13 files changed, 52 insertions(+), 177 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 850b415..bed0460 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,21 +28,16 @@ bincode = "1.3" bitvec = "1.0" byteorder = "1.4.3" thiserror = "1.0" -halo2curves = { version = "0.4.0", features = ["derive_serde"] } -group = "0.13.0" once_cell = "1.18.0" -derive_more = { version = "^1.0.0", features = ["full"] } # Arkworks -ark-ec = { version = "^0.5.0", default-features = false } -ark-ff = { version = "^0.5.0", default-features = false } -ark-std = { version = "^0.5.0", default-features = false } -ark-serialize = { version = "^0.5.0", default-features = false, features = ["derive"] } -ark-snark = { version = "^0.5.1", default-features = false } -ark-bls12-381 = { version = "^0.5.0", default-features = false, features = ["curve"] } -ark-relations = { version = "^0.5.1", default-features = false } -zeroize = { version = "^1.8.1", default-features = false } -delegate = "0.13.1" +ark-ec = { version = "0.5.0", default-features = false } +ark-ff = { version = "0.5.0", default-features = false } +ark-std = { version = "0.5.0", default-features = false } +ark-serialize = { version = "0.5.0", default-features = false, features = ["derive"] } +ark-bls12-381 = { version = "0.5.0", default-features = false, features = ["curve"] } +ark-relations = { version = "0.5.1", default-features = false } +zeroize = { version = "1.8.1", default-features = false } serde_with = "3.12.0" sha2 = "0.10.7" ark-r1cs-std = "0.5.0" diff --git a/examples/less_than.rs b/examples/less_than.rs index 363a1b5..9a457c9 100644 --- a/examples/less_than.rs +++ b/examples/less_than.rs @@ -7,6 +7,7 @@ use ark_relations::r1cs::{ ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, Variable, }; +use ark_relations::{lc, ns}; use num_traits::One; use spartan2::{ errors::SpartanError, @@ -31,16 +32,17 @@ fn num_to_bits_le_bounded( // Add one witness per input bit in little-endian bit order let bits_circuit = opt_bits.into_iter() .enumerate() - // Boolean enforces the value to be 0 or 1 at the constraint level - .map(|(i, b)| { + // AllocatedBool enforces the value to be 0 or 1 at the constraint level + .map(|(_i, b)| { // TODO: Why do I need namespaces here? - let namespaced_cs = Namespace::from(cs.clone()); + // TODO: Namespace can't use string ids, only const ids + // let namespaced_cs = Namespace::from(cs.clone()); // TODO: Is it a "new_input" or a different type of a variable? - AllocatedBool::::new_input(namespaced_cs, || b.ok_or(SynthesisError::AssignmentMissing)) + AllocatedBool::::new_input(cs.clone(), || b.ok_or(SynthesisError::AssignmentMissing)) }) .collect::>, SynthesisError>>()?; - let mut weighted_sum_lc = LinearCombination::zero(); + let mut weighted_sum_lc = lc!(); let mut pow2 = F::ONE; for bit in bits_circuit.iter() { @@ -52,9 +54,8 @@ fn num_to_bits_le_bounded( let constraint_lc = weighted_sum_lc - n.variable; // Enforce constraint_lc == 0 - let one_lc = LinearCombination::from((One::one(), Variable::One)); - cs.enforce_constraint(constraint_lc, one_lc, LinearCombination::zero()) - .expect("Failed to enforce the linear combination constraint"); + let one_lc = lc!() + Variable::One; + cs.enforce_constraint(constraint_lc, one_lc, lc!())?; Ok(bits_circuit) } @@ -66,7 +67,7 @@ fn get_msb_index(n: F) -> u8 { .enumerate() .rev() .find(|(_, b)| *b) - .unwrap() + .expect("Index not found") .0 as u8 } @@ -99,13 +100,10 @@ impl ConstraintSynthesizer for LessThanCircuitUnsafe { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { assert!(F::MODULUS_BIT_SIZE > self.num_bits as u32 + 1); - // TODO: It is possible to create Namespace with an numerical id, tracing::Id. - // Would that be useful? - // TODO: Use ns!() macro instead - let input_ns = Namespace::from(cs.clone()); + let input_ns = ns!(cs.clone(), "input"); let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; - let shifted_ns = Namespace::from(cs.clone()); + let shifted_ns = ns!(cs.clone(), "shifted_diff"); // TODO: Is this an input or a variable? let shifted_diff = AllocatedFp::::new_input(shifted_ns, || { Ok(self.input + F::from(1 << self.num_bits) - self.bound) @@ -113,13 +111,13 @@ impl ConstraintSynthesizer for LessThanCircuitUnsafe { let shifted_diff_lc = LinearCombination::from(input.variable) + LinearCombination::from((F::from(1 << self.num_bits) - self.bound, Variable::One)) - - shifted_diff.variable; + - LinearCombination::from(shifted_diff.variable); // Enforce the linear combination (shifted_diff_lc == 0) cs.enforce_constraint( shifted_diff_lc, LinearCombination::from((F::ONE, Variable::One)), - LinearCombination::zero(), + lc!(), )?; let shifted_diff_bits = @@ -127,13 +125,12 @@ impl ConstraintSynthesizer for LessThanCircuitUnsafe { // Check that the last (i.e. most significant) bit is 0 let msb_var = shifted_diff_bits[self.num_bits as usize].variable(); - let zero_lc = LinearCombination::zero(); // Enforce the constraint that the most significant bit is 0 cs.enforce_constraint( LinearCombination::from((F::ONE, msb_var)), LinearCombination::from((F::ONE, Variable::One)), - zero_lc, + lc!(), )?; Ok(()) diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index 7af8d09..5e4a852 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -3,9 +3,9 @@ //! [Bellperson]: https://github.com/filecoin-project/bellperson pub mod r1cs; -// pub mod solver; -// In Arkworks, shape of R1CS is derived from ark_relations::r1cs::ConstraintSystem +// Superseded by ark-relations +// pub mod solver; // pub mod shape_cs; // pub mod test_shape_cs; @@ -16,7 +16,7 @@ mod tests { use crate::r1cs::{R1CSShape, R1CS}; use ark_ff::Field; use ark_relations::lc; - use ark_relations::r1cs::{ConstraintSystem, LinearCombination, SynthesisError, Variable}; + use ark_relations::r1cs::{ConstraintSystem, SynthesisError, Variable}; fn synthesize_alloc_bit(cs: &mut ConstraintSystem) -> Result<(), SynthesisError> { // Allocate 'a' as a public input diff --git a/src/bellpepper/r1cs.rs b/src/bellpepper/r1cs.rs index 2283394..014a093 100644 --- a/src/bellpepper/r1cs.rs +++ b/src/bellpepper/r1cs.rs @@ -8,6 +8,7 @@ use crate::{ traits::Group, CommitmentKey, }; +use ark_ff::PrimeField; use ark_relations::r1cs::ConstraintSystem; /// `SpartanWitness` provide a method for acquiring an `R1CSInstance` and `R1CSWitness` from implementers. @@ -20,17 +21,9 @@ pub trait SpartanWitness { ) -> Result<(R1CSInstance, R1CSWitness), SpartanError>; } -// TODO: Currently not used, move some helper methods here? Or remove it? -/// `SpartanShape` provides methods for acquiring `R1CSShape` and `CommitmentKey` from implementers. -pub trait SpartanShape { - /// Return an appropriate `R1CSShape` and `CommitmentKey` structs. - fn r1cs_shape(&self) -> (R1CSShape, CommitmentKey); -} - impl SpartanWitness for ConstraintSystem -// TODO: Not sure I need that -// where -// G::Scalar: PrimeField, +where + G::Scalar: PrimeField, { fn r1cs_instance_and_witness( &self, diff --git a/src/lib.rs b/src/lib.rs index 28c8563..3e84ff4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,10 +157,10 @@ mod tests { #[test] fn test_snark() { - type G = ark_bls12_381::G1Projective; - type EE = provider::ipa_pc::EvaluationEngine; - type S = spartan::snark::RelaxedR1CSSNARK; - type Spp = spartan::ppsnark::RelaxedR1CSSNARK; + // type G = ark_bls12_381::G1Projective; + // type EE = provider::ipa_pc::EvaluationEngine; + // type S = spartan::snark::RelaxedR1CSSNARK; + // type Spp = spartan::ppsnark::RelaxedR1CSSNARK; // test_snark_with::(); // TODO // test_snark_with::(); // TODO diff --git a/src/provider/ark.rs b/src/provider/ark.rs index b8dff7e..788a149 100644 --- a/src/provider/ark.rs +++ b/src/provider/ark.rs @@ -12,7 +12,6 @@ use ark_ec::{AffineRepr, CurveGroup, PrimeGroup, VariableBaseMSM}; use ark_ff::field_hashers::DefaultFieldHasher; use ark_ff::{AdditiveGroup, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use derive_more::Into; use num_bigint::BigInt; use num_traits::{Num, Zero}; use serde::ser::SerializeStruct; diff --git a/src/provider/keccak.rs b/src/provider/keccak.rs index 6858564..e041afc 100644 --- a/src/provider/keccak.rs +++ b/src/provider/keccak.rs @@ -103,7 +103,6 @@ mod tests { traits::{Group, PrimeFieldExt, TranscriptEngineTrait, TranscriptReprTrait}, }; use ark_serialize::CanonicalSerialize; - use bincode::Options; use rand::Rng; use sha3::{Digest, Keccak256}; diff --git a/src/provider/mod.rs b/src/provider/mod.rs index a8936c2..44ae8e0 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -13,103 +13,6 @@ pub mod ark_serde; pub mod pedersen; // pub mod secp_secq; // Replaced by ark-bls12-381, TODO: Fix later? -use ark_ec::AffineRepr; -use ark_ff::{AdditiveGroup, PrimeField}; -use num_traits::Zero; - -// TODO: Replaced by VariableMSM from Arkworks, remove? -/// Native implementation of fast multiexp -/// Adapted from zcash/halo2 -fn cpu_multiexp_serial(coeffs: &[C::ScalarField], bases: &[C], acc: &mut C::Group) -where - C: AffineRepr, - C::ScalarField: PrimeField, -{ - let coeffs: Vec<_> = coeffs.iter().map(|a| *a).collect(); - - let c = if bases.len() < 4 { - 1 - } else if bases.len() < 32 { - 3 - } else { - f64::from(bases.len() as u32).ln().ceil() as usize - }; - - /// Returns the `c`-bit integer value at the specified segment from the given bytes. - fn get_at(segment: usize, c: usize, bytes: &F::BigInt) -> usize { - // Calculate the bit position and check bounds - let skip_bits = segment * c; - if skip_bits / 8 >= bytes.as_ref().len() { - return 0; - } - // Process up to 8 bytes and extract relevant bits - let mut tmp = 0u64; - for (i, &byte) in bytes.as_ref()[skip_bits / 8..].iter().take(8).enumerate() { - tmp |= (byte as u64) << (i * 8); - } - (tmp >> (skip_bits % 8)) as usize & ((1 << c) - 1) - } - - let segments = (256 / c) + 1; - - for current_segment in (0..segments).rev() { - for _ in 0..c { - *acc = acc.double(); - } - - #[derive(Clone, Copy)] - enum Bucket { - None, - Affine(C), - Projective(C::Group), - } - - impl Bucket { - fn add_assign(&mut self, other: &C) { - *self = match *self { - Bucket::None => Bucket::Affine(*other), - Bucket::Affine(a) => Bucket::Projective(a + *other), - Bucket::Projective(mut a) => { - a += *other; - Bucket::Projective(a) - } - } - } - - fn add(self, mut other: C::Group) -> C::Group { - match self { - Bucket::None => other, - Bucket::Affine(a) => { - other += a; - other - } - Bucket::Projective(a) => other + a, - } - } - } - - let mut buckets: Vec> = vec![Bucket::None; (1 << c) - 1]; - - for (coeff, base) in coeffs.iter().zip(bases.iter()) { - let coeff_bigint = coeff.into_bigint(); - let coeff = get_at::(current_segment, c, &coeff_bigint); - if coeff != 0 { - buckets[coeff - 1].add_assign(base); - } - } - - // Summation by parts - // e.g. 3a + 2b + 1c = a + - // (a) + b + - // ((a) + b) + c - let mut running_sum = C::Group::zero(); - for exp in buckets.into_iter().rev() { - running_sum = exp.add(running_sum); - *acc += running_sum; - } - } -} - /// Curve ops #[macro_export] macro_rules! impl_traits { diff --git a/src/r1cs.rs b/src/r1cs.rs index a9346ea..9e8eb8f 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -40,7 +40,8 @@ impl From<&ConstraintMatrices> for R1CSShape { fn from(r1cs_cm: &ConstraintMatrices) -> Self { Self { // TODO: Is this mapping correct? - // TODO: Revisit implementation of SpartanShape + // TODO: These values are expected to be power of two. + // Remove `next_power_of_two()` to reproduce errors num_cons: r1cs_cm.num_constraints.next_power_of_two(), num_vars: r1cs_cm.num_witness_variables.next_power_of_two(), num_io: r1cs_cm.num_instance_variables - 1, // TODO: Always has one variable by default in ark-relations? diff --git a/src/spartan/polys/multilinear.rs b/src/spartan/polys/multilinear.rs index c398a3c..9d5532d 100644 --- a/src/spartan/polys/multilinear.rs +++ b/src/spartan/polys/multilinear.rs @@ -200,7 +200,6 @@ mod tests { use super::*; use ark_bls12_381::Fr; - use ark_ff::Fp; fn make_mlp(len: usize, value: F) -> MultilinearPolynomial { MultilinearPolynomial { diff --git a/src/spartan/polys/univariate.rs b/src/spartan/polys/univariate.rs index 0b67e9d..4133188 100644 --- a/src/spartan/polys/univariate.rs +++ b/src/spartan/polys/univariate.rs @@ -145,13 +145,13 @@ mod tests { assert_eq!(poly.evaluate(&F::from(3)), e3); } - // TODO: ark_ff::PrimeField is not implemented for these Scalars - // #[test] - // fn test_from_evals_quad() { - // test_from_evals_quad_with::(); - // test_from_evals_quad_with::(); - // test_from_evals_quad_with::(); - // } + #[test] + fn test_from_evals_quad() { + // test_from_evals_quad_with::(); + // test_from_evals_quad_with::(); + // test_from_evals_quad_with::(); + test_from_evals_quad_with::(); + } fn test_from_evals_cubic_with() { // polynomial is x^3 + 2x^2 + 3x + 1 @@ -181,11 +181,11 @@ mod tests { assert_eq!(poly.evaluate(&F::from(4)), e4); } - // TODO: ark_ff::PrimeField is not implemented for these Scalars - // #[test] - // fn test_from_evals_cubic() { - // test_from_evals_cubic_with::(); - // test_from_evals_cubic_with::(); - // test_from_evals_cubic_with::() - // } + #[test] + fn test_from_evals_cubic() { + // test_from_evals_cubic_with::(); + // test_from_evals_cubic_with::(); + // test_from_evals_cubic_with::(); + test_from_evals_cubic_with::(); + } } diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index db51a4d..331bab7 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -128,8 +128,6 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .expect(&format!("Failed to enforce padding variable {i}")); }); - // TODO: Generating a R1CS shape, commitment key, etc. from ark-relations R1CS seems to be - // a common operation. Consider bundling it into a utility (like the old r1cs_shape method). let S = R1CSShape::from(&r1cs_cm); let ck = R1CS::commitment_key(&S); diff --git a/src/traits/mod.rs b/src/traits/mod.rs index fc840b5..15de1f6 100644 --- a/src/traits/mod.rs +++ b/src/traits/mod.rs @@ -13,9 +13,6 @@ pub mod commitment; use commitment::CommitmentEngineTrait; -/// TODO: Replaces ff::PrimeFieldBits -// pub trait PrimeFieldBits: CanonicalSerialize + CanonicalDeserialize{}; - /// Represents an element of a group /// This is currently tailored for an elliptic curve group pub trait Group: @@ -31,17 +28,13 @@ pub trait Group: + Send + Sync + CanonicalSerialize - + CanonicalDeserialize // + Serialize -// + for<'de> Deserialize<'de> + + CanonicalDeserialize { /// A type representing an element of the base field of the group - type Base: PrimeField - // + PrimeFieldBits - + TranscriptReprTrait; + type Base: PrimeField + TranscriptReprTrait; /// A type representing an element of the scalar field of the group type Scalar: PrimeField - // + PrimeFieldBits + PrimeFieldExt + Send + Sync @@ -105,13 +98,11 @@ pub trait CompressedGroup: + Sync + TranscriptReprTrait + CanonicalSerialize - + CanonicalDeserialize - // + Serialize - // + for<'de> Deserialize<'de> + + CanonicalDeserialize + 'static { /// A type that holds the decompressed version of the compressed group element - type GroupElement: Group + CanonicalSerialize + CanonicalDeserialize; // Serialize + for<'de> Deserialize<'de>; + type GroupElement: Group + CanonicalSerialize + CanonicalDeserialize; /// Decompresses the compressed group element fn decompress(&self) -> Option; From d1957d45a38e9c542ed87d26ac2761c9840088a9 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 2 Jan 2025 17:45:36 +0100 Subject: [PATCH 4/9] fixed r1cs conversion --- examples/less_than.rs | 26 ++++++++++---------------- src/bellpepper/mod.rs | 4 ++-- src/r1cs.rs | 25 ++++++++++++------------- src/spartan/snark.rs | 13 ++++++++++--- 4 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/less_than.rs b/examples/less_than.rs index 9a457c9..5dcb434 100644 --- a/examples/less_than.rs +++ b/examples/less_than.rs @@ -4,8 +4,7 @@ use ark_r1cs_std::alloc::AllocVar; use ark_r1cs_std::boolean::AllocatedBool; use ark_r1cs_std::fields::fp::AllocatedFp; use ark_relations::r1cs::{ - ConstraintSynthesizer, ConstraintSystemRef, LinearCombination, Namespace, SynthesisError, - Variable, + ConstraintSynthesizer, ConstraintSystemRef, Namespace, SynthesisError, Variable, }; use ark_relations::{lc, ns}; use num_traits::One; @@ -104,32 +103,27 @@ impl ConstraintSynthesizer for LessThanCircuitUnsafe { let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; let shifted_ns = ns!(cs.clone(), "shifted_diff"); - // TODO: Is this an input or a variable? - let shifted_diff = AllocatedFp::::new_input(shifted_ns, || { + let shifted_diff = AllocatedFp::::new_witness(shifted_ns, || { Ok(self.input + F::from(1 << self.num_bits) - self.bound) })?; - let shifted_diff_lc = LinearCombination::from(input.variable) - + LinearCombination::from((F::from(1 << self.num_bits) - self.bound, Variable::One)) - - LinearCombination::from(shifted_diff.variable); + let shifted_diff_lc = + lc!() + (F::ONE, input.variable) + (F::from(1 << self.num_bits) - self.bound, Variable::One) + - (F::ONE, shifted_diff.variable); - // Enforce the linear combination (shifted_diff_lc == 0) - cs.enforce_constraint( - shifted_diff_lc, - LinearCombination::from((F::ONE, Variable::One)), - lc!(), - )?; + // Enforce shifted_diff_lc == 0 + cs.enforce_constraint(shifted_diff_lc, lc!() + (F::ONE, Variable::One), lc!())?; let shifted_diff_bits = num_to_bits_le_bounded::(cs.clone(), shifted_diff, self.num_bits + 1)?; - // Check that the last (i.e. most significant) bit is 0 + // Check that the most significant bit is 0 let msb_var = shifted_diff_bits[self.num_bits as usize].variable(); // Enforce the constraint that the most significant bit is 0 cs.enforce_constraint( - LinearCombination::from((F::ONE, msb_var)), - LinearCombination::from((F::ONE, Variable::One)), + lc!() + (F::ONE, msb_var), + lc!() + (F::ONE, Variable::One), lc!(), )?; diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index 5e4a852..8a5ddd7 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -20,13 +20,13 @@ mod tests { fn synthesize_alloc_bit(cs: &mut ConstraintSystem) -> Result<(), SynthesisError> { // Allocate 'a' as a public input - let a_var = cs.new_witness_variable(|| Ok(F::ONE))?; + let a_var = cs.new_input_variable(|| Ok(F::ONE))?; // Enforce: a * (1 - a) = 0 (this ensures that 'a' is an 0 or 1) cs.enforce_constraint(lc!() + a_var, lc!() + Variable::One - a_var, lc!())?; // Allocate 'b' as a public input - let b_var = cs.new_witness_variable(|| Ok(F::ONE))?; + let b_var = cs.new_input_variable(|| Ok(F::ONE))?; // Enforce: b * (1 - b) = 0 (this ensures that 'b' is 0 or 1) cs.enforce_constraint(lc!() + b_var, lc!() + Variable::One - b_var, lc!())?; diff --git a/src/r1cs.rs b/src/r1cs.rs index 9e8eb8f..3dcd53c 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -38,17 +38,16 @@ pub struct R1CSShape { impl From<&ConstraintMatrices> for R1CSShape { fn from(r1cs_cm: &ConstraintMatrices) -> Self { - Self { - // TODO: Is this mapping correct? - // TODO: These values are expected to be power of two. - // Remove `next_power_of_two()` to reproduce errors - num_cons: r1cs_cm.num_constraints.next_power_of_two(), - num_vars: r1cs_cm.num_witness_variables.next_power_of_two(), - num_io: r1cs_cm.num_instance_variables - 1, // TODO: Always has one variable by default in ark-relations? - A: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), - B: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), - C: R1CSShape::::flatten_r1cs_cm(&r1cs_cm.c), - } + R1CSShape::new( + r1cs_cm.num_constraints, + r1cs_cm.num_witness_variables, + // Arkworks creates one instance variable by default, need to it adjust here: + r1cs_cm.num_instance_variables - 1, + &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), + &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), + &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.c), + ) + .expect("Invalid R1CSShape") } } @@ -168,8 +167,8 @@ impl R1CSShape { }) } - // Checks regularity conditions on the R1CSShape, required in Spartan-class SNARKs - // Panics if num_cons, num_vars, or num_io are not powers of two, or if num_io > num_vars + /// Checks regularity conditions on the R1CSShape, required in Spartan-class SNARKs + /// Panics if num_cons, num_vars, or num_io are not powers of two, or if num_io > num_vars #[inline] pub(crate) fn check_regular_shape(&self) { assert_eq!(self.num_cons.next_power_of_two(), self.num_cons); diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 331bab7..bf07eca 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -128,6 +128,10 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .expect(&format!("Failed to enforce padding variable {i}")); }); + // Update before commiting shape to the ProverKey + let r1cs_cm = cs + .to_matrices() + .expect("Failed to convert constraint system to R1CS"); let S = R1CSShape::from(&r1cs_cm); let ck = R1CS::commitment_key(&S); @@ -155,15 +159,18 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .generate_constraints(cs_ref.clone()) .expect("TODO: Handle error"); - // Padding variables - let num_vars = cs_ref.num_instance_variables(); + let cs = cs_ref.borrow().unwrap(); + let r1cs_cm = cs.to_matrices().unwrap(); + let shape = R1CSShape::::from(&r1cs_cm); + + // Padding the variables + let num_vars = shape.num_vars; (num_vars..num_vars.next_power_of_two()).for_each(|i| { cs_ref .enforce_constraint(lc!(), lc!(), lc!()) .expect(&format!("Failed to enforce padding constraint {i}")); }); - let cs = cs_ref.borrow().unwrap(); let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| SpartanError::UnSat)?; From d1905a77f8141ab22c1f60ea15edf1037b3ca44c Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Thu, 2 Jan 2025 18:21:52 +0100 Subject: [PATCH 5/9] deduplicate into a R1CSShape helper --- src/bellpepper/mod.rs | 3 +-- src/r1cs.rs | 25 +++++++++++++++++++++---- src/spartan/ppsnark.rs | 5 +---- src/spartan/snark.rs | 11 +++-------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index 8a5ddd7..c4ab182 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -41,8 +41,7 @@ mod tests { // First create the shape let mut cs: ConstraintSystem = ConstraintSystem::new(); let _ = synthesize_alloc_bit(&mut cs); - let r1cs_cm = cs.to_matrices().expect("Failed to convert to R1CS"); - let shape: R1CSShape = R1CSShape::from(&r1cs_cm); + let shape: R1CSShape = R1CSShape::from(&cs); let ck = R1CS::commitment_key(&shape); // Now get the assignment diff --git a/src/r1cs.rs b/src/r1cs.rs index 3dcd53c..9347421 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -7,7 +7,7 @@ use crate::{ Commitment, CommitmentKey, CE, }; use ark_ff::{AdditiveGroup, Field}; -use ark_relations::r1cs::ConstraintMatrices; +use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystem, ConstraintSystemRef}; use core::{cmp::max, marker::PhantomData}; use itertools::concat; use rayon::prelude::*; @@ -36,12 +36,17 @@ pub struct R1CSShape { pub(crate) C: Vec<(usize, usize, G::Scalar)>, } -impl From<&ConstraintMatrices> for R1CSShape { - fn from(r1cs_cm: &ConstraintMatrices) -> Self { +impl R1CSShape { + /// Helper function to create an `R1CSShape` from any type that implements `to_matrices`. + fn from_matrices(to_matrices: F) -> Self + where + F: Fn() -> Option>, + { + let r1cs_cm = to_matrices().expect("Failed to convert constraint system to R1CS"); R1CSShape::new( r1cs_cm.num_constraints, r1cs_cm.num_witness_variables, - // Arkworks creates one instance variable by default, need to it adjust here: + // Arkworks creates one instance variable by default, need to adjust here: r1cs_cm.num_instance_variables - 1, &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), @@ -51,6 +56,18 @@ impl From<&ConstraintMatrices> for R1CSShape { } } +impl From<&ConstraintSystem> for R1CSShape { + fn from(cm: &ConstraintSystem) -> Self { + R1CSShape::from_matrices(|| cm.to_matrices()) + } +} + +impl From<&ConstraintSystemRef> for R1CSShape { + fn from(cm: &ConstraintSystemRef) -> Self { + R1CSShape::from_matrices(|| cm.to_matrices()) + } +} + impl R1CSShape { /// Helper method to flatten and index a nested R1CS matrix with tuples pub fn flatten_r1cs_cm( diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 7ee62c4..934032a 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -933,10 +933,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe circuit .generate_constraints(cs.clone()) .expect("TODO: Handle error"); - let r1cs_cm = cs - .to_matrices() - .expect("Failed to convert constraint system to R1CS"); - let r1cs_shape = R1CSShape::from(&r1cs_cm); + let r1cs_shape = R1CSShape::from(&cs); let ck = R1CS::commitment_key(&r1cs_shape); let (pk_ee, vk_ee) = EE::setup(&ck); let shape_repr = R1CSShapeSparkRepr::new(&r1cs_shape); diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index bf07eca..2de3c00 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -128,11 +128,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .expect(&format!("Failed to enforce padding variable {i}")); }); - // Update before commiting shape to the ProverKey - let r1cs_cm = cs - .to_matrices() - .expect("Failed to convert constraint system to R1CS"); - let S = R1CSShape::from(&r1cs_cm); + let S = R1CSShape::from(&cs); let ck = R1CS::commitment_key(&S); let (pk_ee, vk_ee) = EE::setup(&ck); @@ -160,8 +156,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .expect("TODO: Handle error"); let cs = cs_ref.borrow().unwrap(); - let r1cs_cm = cs.to_matrices().unwrap(); - let shape = R1CSShape::::from(&r1cs_cm); + let shape = R1CSShape::::from(&cs_ref); // Padding the variables let num_vars = shape.num_vars; @@ -439,7 +434,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let (num_rounds_x, num_rounds_y) = ( usize::try_from(vk.S.num_cons.ilog2()).unwrap(), - (usize::try_from(vk.S.num_vars.ilog2()).unwrap() + 1), + usize::try_from(vk.S.num_vars.ilog2()).unwrap() + 1, ); // outer sum-check From e74752715d9c44e92a2c8eb2bfb70223093b532f Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sat, 4 Jan 2025 10:03:13 +0100 Subject: [PATCH 6/9] fiddling with the failing test_snark --- src/bellpepper/mod.rs | 3 ++- src/lib.rs | 43 +++++++++++++++++++++------------------ src/r1cs.rs | 46 ++++++++++++++++++++++-------------------- src/spartan/ppsnark.rs | 20 +++++++----------- src/spartan/snark.rs | 23 +++++++++------------ 5 files changed, 65 insertions(+), 70 deletions(-) diff --git a/src/bellpepper/mod.rs b/src/bellpepper/mod.rs index c4ab182..9f15980 100644 --- a/src/bellpepper/mod.rs +++ b/src/bellpepper/mod.rs @@ -41,7 +41,8 @@ mod tests { // First create the shape let mut cs: ConstraintSystem = ConstraintSystem::new(); let _ = synthesize_alloc_bit(&mut cs); - let shape: R1CSShape = R1CSShape::from(&cs); + let shape: R1CSShape = + R1CSShape::from(&cs.to_matrices().expect("Failed to convert to R1CS")); let ck = R1CS::commitment_key(&shape); // Now get the assignment diff --git a/src/lib.rs b/src/lib.rs index 3e84ff4..80dcc60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,36 +121,39 @@ mod tests { let x = F::from(2u64); // Example: x = 2 let y = F::from(15u64); // Example: y = 15 (2^3 + 2 + 5 = 15) - // 1. Allocate x^2 as an intermediate variable (witness) + // Step 1: Allocate `x` as a private witness variable + let x_var = cs.new_witness_variable(|| Ok(x))?; + + // Step 2: Compute `x²` and enforce `x² = x * x` let x_squared_var = cs.new_witness_variable(|| Ok(x * x))?; + cs.enforce_constraint(lc!() + x_var, lc!() + x_var, lc!() + x_squared_var)?; - // Enforce x^2 = x * x + // Step 3: Compute `x³` and enforce `x³ = x² * x` + let x_cubed_var = cs.new_witness_variable(|| Ok(x * x * x))?; cs.enforce_constraint( - lc!() + (x, Variable::One), - lc!() + (x, Variable::One), - lc!() + x_squared_var, + lc!() + x_squared_var, // Left-hand side: `x²` + lc!() + x_var, // Right-hand side: `x` + lc!() + x_cubed_var, // Result: `x³` )?; - // 2. Allocate x^3 as another intermediate variable (witness) - let x_cubed_var = cs.new_witness_variable(|| Ok(x * x * x))?; - - // Enforce x^3 = x^2 * x + // Step 4: Compute `y` and enforce `y = x³ + x + 5` + let y_var = cs.new_input_variable(|| Ok(y))?; cs.enforce_constraint( - lc!() + x_squared_var, - lc!() + (x, Variable::One), - lc!() + x_cubed_var, + lc!() + x_cubed_var // `x³` + + x_var // `x` + + (F::from(5u64), Variable::One), // `+ 5` + lc!() + Variable::One, // Identity multiplier + lc!() + y_var, // Public `y` )?; - // 3. Add the constraint: x^3 + x + 5 = y + // Step 5: Expose `y` explicitly as public input + // This adds one more constraint to ensure `y_var` matches the public input declared for the circuit. cs.enforce_constraint( - lc!() + x_cubed_var + (x, Variable::One) + (F::from(5u64), Variable::One), - lc!() + Variable::One, // Identity multiplier for y - lc!() + (y, Variable::One), + lc!() + y_var, + lc!() + Variable::One, + lc!() + (y, Variable::One), // Ensure that `y_var` matches the public `y` )?; - // 4. Expose y as a public input - cs.new_input_variable(|| Ok(y))?; - Ok(()) } } @@ -194,7 +197,7 @@ mod tests { // produce a SNARK let res = SNARK::prove(&pk, circuit); - assert!(res.is_ok()); + // assert!(res.is_ok()); let snark = res.unwrap(); // verify the SNARK diff --git a/src/r1cs.rs b/src/r1cs.rs index 9347421..9c0205f 100644 --- a/src/r1cs.rs +++ b/src/r1cs.rs @@ -7,7 +7,7 @@ use crate::{ Commitment, CommitmentKey, CE, }; use ark_ff::{AdditiveGroup, Field}; -use ark_relations::r1cs::{ConstraintMatrices, ConstraintSystem, ConstraintSystemRef}; +use ark_relations::r1cs::ConstraintMatrices; use core::{cmp::max, marker::PhantomData}; use itertools::concat; use rayon::prelude::*; @@ -38,33 +38,24 @@ pub struct R1CSShape { impl R1CSShape { /// Helper function to create an `R1CSShape` from any type that implements `to_matrices`. - fn from_matrices(to_matrices: F) -> Self - where - F: Fn() -> Option>, - { - let r1cs_cm = to_matrices().expect("Failed to convert constraint system to R1CS"); + fn from_matrices(cm: ConstraintMatrices) -> Self { R1CSShape::new( - r1cs_cm.num_constraints, - r1cs_cm.num_witness_variables, - // Arkworks creates one instance variable by default, need to adjust here: - r1cs_cm.num_instance_variables - 1, - &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.a), - &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.b), - &*R1CSShape::::flatten_r1cs_cm(&r1cs_cm.c), + cm.num_constraints, + cm.num_witness_variables, + // Arkworks creates one instance variable by default + // Don't count it as an input for shape's purposes. + cm.num_instance_variables - 1, + &*R1CSShape::::flatten_r1cs_cm(&cm.a), + &*R1CSShape::::flatten_r1cs_cm(&cm.b), + &*R1CSShape::::flatten_r1cs_cm(&cm.c), ) .expect("Invalid R1CSShape") } } -impl From<&ConstraintSystem> for R1CSShape { - fn from(cm: &ConstraintSystem) -> Self { - R1CSShape::from_matrices(|| cm.to_matrices()) - } -} - -impl From<&ConstraintSystemRef> for R1CSShape { - fn from(cm: &ConstraintSystemRef) -> Self { - R1CSShape::from_matrices(|| cm.to_matrices()) +impl From<&ConstraintMatrices> for R1CSShape { + fn from(cm: &ConstraintMatrices) -> Self { + R1CSShape::from_matrices::(cm.clone()) } } @@ -79,6 +70,17 @@ impl R1CSShape { .flat_map(|(row, cols)| cols.iter().map(move |(value, col)| (row, *col, *value))) .collect() } + + pub fn padded(&self) -> Self { + Self { + num_cons: self.num_cons.next_power_of_two(), + num_vars: self.num_vars.next_power_of_two(), + num_io: self.num_io, + A: self.A.clone(), + B: self.B.clone(), + C: self.C.clone(), + } + } } /// A type that holds a witness for a given R1CS instance diff --git a/src/spartan/ppsnark.rs b/src/spartan/ppsnark.rs index 934032a..fc82026 100644 --- a/src/spartan/ppsnark.rs +++ b/src/spartan/ppsnark.rs @@ -928,27 +928,21 @@ impl> RelaxedR1CSSNARKTrait for Relaxe fn setup>( circuit: C, ) -> Result<(Self::ProverKey, Self::VerifierKey), SpartanError> { - // Create an Arkworks-based constraint system and convert it to Spartan2 representation - let cs = ConstraintSystem::::new_ref(); + let cs_ref = ConstraintSystem::::new_ref(); circuit - .generate_constraints(cs.clone()) + .generate_constraints(cs_ref.clone()) .expect("TODO: Handle error"); - let r1cs_shape = R1CSShape::from(&cs); - let ck = R1CS::commitment_key(&r1cs_shape); + let shape = R1CSShape::from(&cs_ref.to_matrices().expect("Failed to convert to R1CS")); + let ck = R1CS::commitment_key(&shape); let (pk_ee, vk_ee) = EE::setup(&ck); - let shape_repr = R1CSShapeSparkRepr::new(&r1cs_shape); + let shape_repr = R1CSShapeSparkRepr::new(&shape); let shape_comm = shape_repr.commit(&ck); - let vk = VerifierKey::new( - r1cs_shape.num_cons, - r1cs_shape.num_vars, - shape_comm.clone(), - vk_ee, - ); + let vk = VerifierKey::new(shape.num_cons, shape.num_vars, shape_comm.clone(), vk_ee); let pk = ProverKey { ck, pk_ee, - S: r1cs_shape, + S: shape, S_repr: shape_repr, S_comm: shape_comm, vk_digest: vk.digest(), diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 2de3c00..28f791f 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -110,25 +110,22 @@ impl> RelaxedR1CSSNARKTrait for Relaxe circuit .generate_constraints(cs.clone()) .expect("TODO: Handle error"); - let r1cs_cm = cs - .to_matrices() - .expect("Failed to convert constraint system to R1CS"); - - // TODO: Do we still need that padding even if we don't use ShapeCS anymore? - // Padding the ShapeCS: constraints (rows) and variables (columns) - let num_constraints = r1cs_cm.num_constraints; - (num_constraints..num_constraints.next_power_of_two()).for_each(|i| { + + // Padding the shape: constraints (rows) and variables (columns) + let num_cons = cs.num_constraints(); + (num_cons..num_cons.next_power_of_two()).for_each(|i| { cs.enforce_constraint(lc!(), lc!(), lc!()) .expect(&format!("Failed to enforce padding constraint {i}")); }); - let num_vars = r1cs_cm.num_instance_variables; + let num_vars = cs.num_witness_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { cs.enforce_constraint(lc!(), lc!(), lc!()) .expect(&format!("Failed to enforce padding variable {i}")); }); - let S = R1CSShape::from(&cs); + // Update shape after padding + let S = R1CSShape::from(&cs.to_matrices().expect("Failed to convert to R1CS")); let ck = R1CS::commitment_key(&S); let (pk_ee, vk_ee) = EE::setup(&ck); @@ -155,17 +152,15 @@ impl> RelaxedR1CSSNARKTrait for Relaxe .generate_constraints(cs_ref.clone()) .expect("TODO: Handle error"); - let cs = cs_ref.borrow().unwrap(); - let shape = R1CSShape::::from(&cs_ref); - // Padding the variables - let num_vars = shape.num_vars; + let num_vars = cs_ref.num_instance_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { cs_ref .enforce_constraint(lc!(), lc!(), lc!()) .expect(&format!("Failed to enforce padding constraint {i}")); }); + let cs = cs_ref.borrow().unwrap(); let (u, w) = cs .r1cs_instance_and_witness(&pk.S, &pk.ck) .map_err(|_e| SpartanError::UnSat)?; From 393ac465a16478c98ffdc41fbb7aadb3f783435b Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sat, 4 Jan 2025 10:12:01 +0100 Subject: [PATCH 7/9] cubic circuit has correct nr of witness vars now --- src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 80dcc60..d053660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -136,8 +136,8 @@ mod tests { lc!() + x_cubed_var, // Result: `x³` )?; - // Step 4: Compute `y` and enforce `y = x³ + x + 5` - let y_var = cs.new_input_variable(|| Ok(y))?; + // Step 4: Allocate `y` as a witness variable and enforce `y = x³ + x + 5` + let y_var = cs.new_witness_variable(|| Ok(y))?; cs.enforce_constraint( lc!() + x_cubed_var // `x³` + x_var // `x` @@ -146,8 +146,7 @@ mod tests { lc!() + y_var, // Public `y` )?; - // Step 5: Expose `y` explicitly as public input - // This adds one more constraint to ensure `y_var` matches the public input declared for the circuit. + // Step 5: Expose `y` as a public input cs.enforce_constraint( lc!() + y_var, lc!() + Variable::One, From 065e53b48e4361e9ee334a70972c1e196c19f6a9 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sat, 4 Jan 2025 10:35:52 +0100 Subject: [PATCH 8/9] update from_label --- src/provider/ark.rs | 71 +++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/src/provider/ark.rs b/src/provider/ark.rs index 788a149..18ddd4b 100644 --- a/src/provider/ark.rs +++ b/src/provider/ark.rs @@ -3,20 +3,24 @@ use crate::provider::pedersen::CommitmentEngine; use crate::traits::{CompressedGroup, Group, PrimeFieldExt, TranscriptReprTrait}; use ark_bls12_381::g1::Config as G1Config; use ark_bls12_381::{Fq, Fr, G1Affine, G1Projective}; -use ark_ec::short_weierstrass::SWCurveConfig; -use ark_ec::{ - hashing::{curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve}, - short_weierstrass::Projective, +use ark_ec::hashing::{ + curve_maps::wb::WBMap, map_to_curve_hasher::MapToCurveBasedHasher, HashToCurve, }; +use ark_ec::short_weierstrass::SWCurveConfig; use ark_ec::{AffineRepr, CurveGroup, PrimeGroup, VariableBaseMSM}; use ark_ff::field_hashers::DefaultFieldHasher; use ark_ff::{AdditiveGroup, PrimeField}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use num_bigint::BigInt; -use num_traits::{Num, Zero}; +use num_traits::Zero; +use rayon::prelude::*; use serde::ser::SerializeStruct; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use sha2::Sha256; +use sha3::{ + digest::{ExtendableOutput, Update}, + Shake256, +}; +use std::io::Read; /// Compressed representation of BLS12-381 group elements #[derive(Clone, Copy, Debug, Eq, PartialEq, CanonicalDeserialize, CanonicalSerialize)] @@ -68,7 +72,7 @@ impl TranscriptReprTrait for BLS12CompressedElement { } } -impl TranscriptReprTrait for ark_bls12_381::Fq { +impl TranscriptReprTrait for Fq { fn to_transcript_bytes(&self) -> Vec { let mut serialized_data = Vec::new(); self @@ -78,7 +82,7 @@ impl TranscriptReprTrait for ark_bls12_381::Fq { } } -impl TranscriptReprTrait for ark_bls12_381::Fr { +impl TranscriptReprTrait for Fr { fn to_transcript_bytes(&self) -> Vec { let mut serialized_data = Vec::new(); self @@ -121,24 +125,39 @@ impl Group for G1Projective { self.into_affine() } - // TODO: This is not actually a label "from_uniform_bytes", fix it fn from_label(label: &[u8], n: usize) -> Vec { - let domain_separator = b"from_uniform_bytes"; - // TODO: Doesn't work with sha3::Shake256, which was originally used here, what do? + let mut shake = Shake256::default(); + shake.update(label); + + let mut reader = shake.finalize_xof(); + + let mut uniform_bytes_vec = Vec::new(); + for _ in 0..n { + let mut uniform_bytes = [0u8; 32]; + reader + .read_exact(&mut uniform_bytes) + .expect("Failed to read bytes from XOF"); + uniform_bytes_vec.push(uniform_bytes); + } + let hasher = MapToCurveBasedHasher::< - Projective, - DefaultFieldHasher, + G1Projective, + DefaultFieldHasher, WBMap, - >::new(domain_separator) + >::new(b"from_uniform_bytes") .expect("Failed to create MapToCurveBasedHasher"); - // Generate `n` curve points from the label - (0..n) - .map(|i| { - let input = [label, &i.to_be_bytes()].concat(); - hasher.hash(&input).expect("Failed to hash to curve") - }) - .collect() + let ck_proj = uniform_bytes_vec + .into_par_iter() + .map(|bytes| hasher.hash(&bytes).expect("Hashing to curve failed")) + .collect::>(); + + let mut ck_affine = vec![G1Affine::identity(); n]; + for (proj, affine) in ck_proj.iter().zip(ck_affine.iter_mut()) { + *affine = (*proj).into(); + } + + ck_affine } fn to_coordinates(&self) -> (Self::Base, Self::Base, bool) { @@ -161,13 +180,9 @@ impl Group for G1Projective { } fn get_curve_params() -> (Self::Base, Self::Base, BigInt) { - let a = ark_bls12_381::g1::Config::COEFF_A; - let b = ark_bls12_381::g1::Config::COEFF_B; - let order = BigInt::from_str_radix( - "52435875175126190479447740508185965837690552500527637822603658699938581184512", - 10, - ) - .unwrap(); + let a = G1Config::COEFF_A; + let b = G1Config::COEFF_B; + let order = Fr::MODULUS.into(); (a, b, order) } } From 9cf2892b58f1481889e7e21b853c874e24752995 Mon Sep 17 00:00:00 2001 From: Piotr Roslaniec Date: Sat, 4 Jan 2025 21:16:01 +0100 Subject: [PATCH 9/9] fix not padding witness vars --- examples/less_than.rs | 7 +++---- src/spartan/snark.rs | 15 +++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/less_than.rs b/examples/less_than.rs index 5dcb434..abc9c32 100644 --- a/examples/less_than.rs +++ b/examples/less_than.rs @@ -36,8 +36,7 @@ fn num_to_bits_le_bounded( // TODO: Why do I need namespaces here? // TODO: Namespace can't use string ids, only const ids // let namespaced_cs = Namespace::from(cs.clone()); - // TODO: Is it a "new_input" or a different type of a variable? - AllocatedBool::::new_input(cs.clone(), || b.ok_or(SynthesisError::AssignmentMissing)) + AllocatedBool::::new_witness(cs.clone(), || b.ok_or(SynthesisError::AssignmentMissing)) }) .collect::>, SynthesisError>>()?; @@ -100,7 +99,7 @@ impl ConstraintSynthesizer for LessThanCircuitUnsafe { assert!(F::MODULUS_BIT_SIZE > self.num_bits as u32 + 1); let input_ns = ns!(cs.clone(), "input"); - let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; + let input = AllocatedFp::::new_witness(input_ns, || Ok(self.input))?; let shifted_ns = ns!(cs.clone(), "shifted_diff"); let shifted_diff = AllocatedFp::::new_witness(shifted_ns, || { @@ -158,7 +157,7 @@ impl ConstraintSynthesizer for LessThanCircuitSafe { fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { // TODO: Do we need to use a namespace here? let input_ns = Namespace::from(cs.clone()); - let input = AllocatedFp::::new_input(input_ns, || Ok(self.input))?; + let input = AllocatedFp::::new_witness(input_ns, || Ok(self.input))?; // Perform the input bit decomposition check num_to_bits_le_bounded::(cs.clone(), input, self.num_bits)?; diff --git a/src/spartan/snark.rs b/src/spartan/snark.rs index 28f791f..7333583 100644 --- a/src/spartan/snark.rs +++ b/src/spartan/snark.rs @@ -25,7 +25,7 @@ use crate::{ }; use ark_ff::{AdditiveGroup, Field}; use ark_relations::lc; -use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystem}; use once_cell::sync::OnceCell; use rayon::prelude::*; use serde::{Deserialize, Serialize}; @@ -120,7 +120,7 @@ impl> RelaxedR1CSSNARKTrait for Relaxe let num_vars = cs.num_witness_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { - cs.enforce_constraint(lc!(), lc!(), lc!()) + cs.new_witness_variable(|| Ok(G::Scalar::ZERO)) .expect(&format!("Failed to enforce padding variable {i}")); }); @@ -146,18 +146,17 @@ impl> RelaxedR1CSSNARKTrait for Relaxe pk: &Self::ProverKey, circuit: C, ) -> Result { - let cs = ConstraintSystem::::new(); - let cs_ref = ConstraintSystemRef::new(cs.clone()); + let cs_ref = ConstraintSystem::new_ref(); circuit .generate_constraints(cs_ref.clone()) .expect("TODO: Handle error"); - // Padding the variables - let num_vars = cs_ref.num_instance_variables(); + // Padding the witness variables + let num_vars = cs_ref.num_witness_variables(); (num_vars..num_vars.next_power_of_two()).for_each(|i| { cs_ref - .enforce_constraint(lc!(), lc!(), lc!()) - .expect(&format!("Failed to enforce padding constraint {i}")); + .new_witness_variable(|| Ok(G::Scalar::ZERO)) + .expect(&format!("Failed to enforce padding variable {i}")); }); let cs = cs_ref.borrow().unwrap();