diff --git a/Cargo.lock b/Cargo.lock index 89ee22be3..62f596db3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1086,6 +1086,7 @@ dependencies = [ "ark-serialize", "ark-std", "getrandom", + "itertools", "num-bigint", "rayon", "sonobe-primitives", @@ -1130,6 +1131,7 @@ dependencies = [ "ark-serialize", "ark-std", "getrandom", + "itertools", "num-bigint", "num-integer", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index 3cd726c60..e47297e84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/privacy-scaling-explorations/sonobe/" rust-version = "1.85.1" [workspace.dependencies] +itertools = { version = "0.14.0" } num-bigint = { version = "0.4.3" } num-integer = { version = "0.1" } num-traits = { version = "0.2" } diff --git a/crates/fs/Cargo.toml b/crates/fs/Cargo.toml index 772f4e47d..b955cc775 100644 --- a/crates/fs/Cargo.toml +++ b/crates/fs/Cargo.toml @@ -14,6 +14,7 @@ ark-r1cs-std = { workspace = true } ark-relations = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } ark-serialize = { workspace = true } +itertools = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } thiserror = { workspace = true } rayon = { workspace = true } diff --git a/crates/fs/src/definitions/instances.rs b/crates/fs/src/definitions/instances.rs index cd431c448..9c2edcfc1 100644 --- a/crates/fs/src/definitions/instances.rs +++ b/crates/fs/src/definitions/instances.rs @@ -14,7 +14,9 @@ use super::utils::TaggedVec; /// [`FoldingInstance`] defines the operations that a folding scheme's instance /// should support. -pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + Absorbable { +pub trait FoldingInstance: + Clone + Debug + PartialEq + Eq + Absorbable + for<'a> Dummy<&'a ArithConfig> +{ /// [`FoldingInstance::N_COMMITMENTS`] defines the number of commitments /// contained in the instance. const N_COMMITMENTS: usize; @@ -44,9 +46,9 @@ pub trait FoldingInstance: Clone + Debug + PartialEq + Eq + A /// `'u'` for it. pub type PlainInstance = TaggedVec; -impl Dummy<&A> for PlainInstance { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_public_inputs()].into() +impl Dummy<&ArithConfig> for PlainInstance { + fn dummy(cfg: &ArithConfig) -> Self { + vec![V::default(); cfg.n_public_inputs].into() } } diff --git a/crates/fs/src/definitions/keys.rs b/crates/fs/src/definitions/keys.rs index fc843568e..796d574d8 100644 --- a/crates/fs/src/definitions/keys.rs +++ b/crates/fs/src/definitions/keys.rs @@ -12,15 +12,12 @@ pub trait DeciderKey: CanonicalSerialize + CanonicalDeserialize { /// [`DeciderKey::VerifierKey`] is the type of the verifier key contained in /// the decider key. type VerifierKey; - /// [`DeciderKey::ArithConfig`] is the constraint system configuration - /// associated with the folding scheme. - type ArithConfig: ArithConfig; /// [`DeciderKey::to_pk`] returns the reference to the prover key. fn to_pk(&self) -> &Self::ProverKey; /// [`DeciderKey::to_vk`] returns the reference to the verifier key. fn to_vk(&self) -> &Self::VerifierKey; - /// [`DeciderKey::to_arith_config`] returns the reference to the constraint - /// system configuration. - fn to_arith_config(&self) -> &Self::ArithConfig; + /// [`DeciderKey::to_arith_config`] returns the constraint system + /// configuration. + fn to_arith_config(&self) -> ArithConfig; } diff --git a/crates/fs/src/definitions/mod.rs b/crates/fs/src/definitions/mod.rs index 38a737027..547a326c6 100644 --- a/crates/fs/src/definitions/mod.rs +++ b/crates/fs/src/definitions/mod.rs @@ -12,7 +12,7 @@ pub mod witnesses; use ark_r1cs_std::{GR1CSVar, alloc::AllocVar}; use sonobe_primitives::{ - arithmetizations::Arith, + arithmetizations::{Arith, ArithConfig}, circuits::AssignmentsOwned, commitments::{CommitmentDef, CommitmentDefGadget}, relations::{Relation, WitnessInstanceSampler}, @@ -54,19 +54,19 @@ pub trait FoldingSchemeDef { /// scheme. type CM: CommitmentDef; /// [`FoldingSchemeDef::RW`] is the type of running witness. - type RW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type RW: FoldingWitness; /// [`FoldingSchemeDef::RU`] is the type of running instance. - type RU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type RU: FoldingInstance; /// [`FoldingSchemeDef::IW`] is the type of incoming witness. - type IW: FoldingWitness + for<'a> Dummy<&'a ::Config>; + type IW: FoldingWitness; /// [`FoldingSchemeDef::IU`] is the type of incoming instance. - type IU: FoldingInstance + for<'a> Dummy<&'a ::Config>; + type IU: FoldingInstance; /// [`FoldingSchemeDef::TranscriptField`] is the field type used in the /// transcript of the folding scheme. type TranscriptField: SonobeField; /// [`FoldingSchemeDef::Arith`] is the constraint system supported by the /// folding scheme. - type Arith: Arith::ArithConfig>; + type Arith: Arith; /// [`FoldingSchemeDef::Config`] is the type of configuration required to /// generate the public parameters of the folding scheme. type Config; @@ -92,8 +92,7 @@ pub trait FoldingSchemeDef { type Challenge; /// [`FoldingSchemeDef::Proof`] is the type of proof generated by the /// folding prover. - type Proof: Clone - + for<'a> Dummy<&'a ::Config>; + type Proof: Clone + for<'a> Dummy<&'a ArithConfig>; } /// [`FoldingSchemeDefGadget`] specifies the in-circuit associated types for a diff --git a/crates/fs/src/definitions/witnesses.rs b/crates/fs/src/definitions/witnesses.rs index 95da4b78b..913c2c492 100644 --- a/crates/fs/src/definitions/witnesses.rs +++ b/crates/fs/src/definitions/witnesses.rs @@ -12,7 +12,7 @@ use super::utils::TaggedVec; /// [`FoldingWitness`] defines the operations that a folding scheme's witness /// should support. -pub trait FoldingWitness: Debug { +pub trait FoldingWitness: Debug + for<'a> Dummy<&'a ArithConfig> { /// [`FoldingWitness::N_OPENINGS`] defines the number of openings contained /// in the witness. const N_OPENINGS: usize; @@ -33,9 +33,9 @@ pub trait FoldingWitness: Debug { /// `'w'` for it. pub type PlainWitness = TaggedVec; -impl Dummy<&A> for PlainWitness { - fn dummy(cfg: &A) -> Self { - vec![V::default(); cfg.n_witnesses()].into() +impl Dummy<&ArithConfig> for PlainWitness { + fn dummy(cfg: &ArithConfig) -> Self { + vec![V::default(); cfg.n_witnesses].into() } } diff --git a/crates/fs/src/nova/algorithms/key_generator.rs b/crates/fs/src/nova/algorithms/key_generator.rs index 38e41ba82..c107cc75c 100644 --- a/crates/fs/src/nova/algorithms/key_generator.rs +++ b/crates/fs/src/nova/algorithms/key_generator.rs @@ -2,7 +2,7 @@ use ark_std::sync::Arc; use sonobe_primitives::{ - arithmetizations::{Arith, ArithConfig}, + arithmetizations::Arith, commitments::{CommitmentKey, GroupBasedCommitment}, traits::SonobeField, }; @@ -16,7 +16,7 @@ impl FoldingSchemeKey let ck = Arc::new(ck); let r1cs = Arc::new(r1cs); let cfg = r1cs.config(); - if ck.max_scalars_len() < cfg.n_constraints().max(cfg.n_witnesses()) { + if ck.max_scalars_len() < cfg.n_constraints.max(cfg.n_witnesses) { return Err(Error::InvalidPublicParameters( "The commitment key is too short for the R1CS instance".into(), )); diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index 48b8657cc..eda3416ca 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -2,6 +2,8 @@ use ark_ff::{Field, One}; use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, ops::Mul, rand::RngCore}; +#[cfg(not(feature = "parallel"))] +use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ @@ -30,19 +32,19 @@ fn cross_term<'a, F: Field>( // Compute the cross term `T` by following the optimized approach in // [Mova](https://eprint.iacr.org/2024/1220.pdf)'s section 5.2. - let v = arith.evaluate_at(AssignmentsOwned::from(( + let v = arith.evaluate_r1cs(AssignmentsOwned::from(( z1.constant + z2.constant, cfg_iter!(z1.public) - .zip(z2.public) + .zip_eq(z2.public) .map(|(a, b)| *a + b) .collect(), cfg_iter!(z1.private) - .zip(z2.private) + .zip_eq(z2.private) .map(|(a, b)| *a + b) .collect(), )))?; Ok(cfg_into_iter!(v) - .zip(e) + .zip_eq(e) .map(|(a, b)| a - b.borrow()) .collect()) } @@ -72,16 +74,25 @@ impl FoldingSchemePro let rho = CM::Scalar::from_bits_le(&rho_bits); let WW = Self::RW { - e: cfg_iter!(W.e).zip(&t).map(|(a, b)| rho * b + a).collect(), + e: cfg_iter!(W.e) + .zip_eq(&t) + .map(|(a, b)| rho * b + a) + .collect(), r_e: W.r_e + r_t * rho, - w: cfg_iter!(W.w).zip(&w.w).map(|(a, b)| rho * b + a).collect(), + w: cfg_iter!(W.w) + .zip_eq(&w.w) + .map(|(a, b)| rho * b + a) + .collect(), r_w: W.r_w + w.r_w * rho, }; let UU = Self::RU { cm_e: U.cm_e + cm_t.mul(rho), u: U.u + rho, cm_w: U.cm_w + u.cm_w.mul(rho), - x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + x: cfg_iter!(U.x) + .zip_eq(&u.x) + .map(|(a, b)| rho * b + a) + .collect(), }; Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) } @@ -104,7 +115,7 @@ impl FoldingSchemePro let (W2, U2) = (W2.borrow(), U2.borrow()); let (z1, z2) = ((U1.u, &U1.x[..], &W1.w[..]), (U2.u, &U2.x[..], &W2.w[..])); - let e = cfg_iter!(W1.e).zip(&W2.e).map(|(a, b)| *a + b); + let e = cfg_iter!(W1.e).zip_eq(&W2.e).map(|(a, b)| *a + b); let t = cross_term(&pk.arith, z1, z2, e)?; let (cm_t, r_t) = CM::commit(&pk.ck, &t, rng)?; @@ -115,13 +126,13 @@ impl FoldingSchemePro let WW = Self::RW { e: cfg_iter!(W1.e) - .zip(&t) - .zip(&W2.e) + .zip_eq(&t) + .zip_eq(&W2.e) .map(|((a, b), c)| rho_squared * c + rho * b + a) .collect(), r_e: W1.r_e + r_t * rho + W2.r_e * rho_squared, w: cfg_iter!(W1.w) - .zip(&W2.w) + .zip_eq(&W2.w) .map(|(a, b)| rho * b + a) .collect(), r_w: W1.r_w + W2.r_w * rho, @@ -131,7 +142,7 @@ impl FoldingSchemePro u: U1.u + rho * U2.u, cm_w: U1.cm_w + U2.cm_w.mul(rho), x: cfg_iter!(U1.x) - .zip(&U2.x) + .zip_eq(&U2.x) .map(|(a, b)| rho * b + a) .collect(), }; diff --git a/crates/fs/src/nova/algorithms/verifier.rs b/crates/fs/src/nova/algorithms/verifier.rs index 5892d01d2..8bbda14e4 100644 --- a/crates/fs/src/nova/algorithms/verifier.rs +++ b/crates/fs/src/nova/algorithms/verifier.rs @@ -1,6 +1,8 @@ //! Proof verification for Nova. use ark_std::{borrow::Borrow, cfg_iter, ops::Mul}; +#[cfg(not(feature = "parallel"))] +use itertools::Itertools; #[cfg(feature = "parallel")] use rayon::prelude::*; use sonobe_primitives::{ @@ -30,7 +32,10 @@ impl FoldingSchemeVer cm_e: U.cm_e + cm_t.mul(rho), u: U.u + rho, cm_w: U.cm_w + u.cm_w.mul(rho), - x: cfg_iter!(U.x).zip(&u.x).map(|(a, b)| rho * b + a).collect(), + x: cfg_iter!(U.x) + .zip_eq(&u.x) + .map(|(a, b)| rho * b + a) + .collect(), }) } } @@ -57,7 +62,7 @@ impl FoldingSchemeVer u: U1.u + rho * U2.u, cm_w: U1.cm_w + U2.cm_w.mul(rho), x: cfg_iter!(U1.x) - .zip(&U2.x) + .zip_eq(&U2.x) .map(|(a, b)| rho * b + a) .collect(), }) diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index 000af39a8..54127a51c 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -27,6 +27,10 @@ where let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U.x.len() != u.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(( Self::RU { u: (U.u.clone() + &rho) @@ -73,6 +77,10 @@ where let rho_bits = transcript.add(&(U1, U2))?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U1.x.len() != U2.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(( Self::RU { u: (U2.u.clone() * &rho + &U1.u) @@ -123,6 +131,10 @@ where let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; + if U.x.len() != u.x.len() { + return Err(SynthesisError::Unsatisfiable); + } + Ok(Self::RU { u: (U.u.clone() + &rho) .try_into() diff --git a/crates/fs/src/nova/instances/mod.rs b/crates/fs/src/nova/instances/mod.rs index a2408bafb..d4958997c 100644 --- a/crates/fs/src/nova/instances/mod.rs +++ b/crates/fs/src/nova/instances/mod.rs @@ -40,13 +40,13 @@ impl FoldingInstance for RunningInstance { } } -impl Dummy<&Cfg> for RunningInstance { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for RunningInstance { + fn dummy(cfg: &ArithConfig) -> Self { Self { cm_e: Default::default(), u: Default::default(), cm_w: Default::default(), - x: vec![Default::default(); cfg.n_public_inputs()], + x: vec![Default::default(); cfg.n_public_inputs], } } } @@ -85,11 +85,11 @@ impl FoldingInstance for IncomingInstance { } } -impl Dummy<&Cfg> for IncomingInstance { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for IncomingInstance { + fn dummy(cfg: &ArithConfig) -> Self { Self { cm_w: Default::default(), - x: vec![Default::default(); cfg.n_public_inputs()], + x: vec![Default::default(); cfg.n_public_inputs], } } } diff --git a/crates/fs/src/nova/keys/mod.rs b/crates/fs/src/nova/keys/mod.rs index ca70224d6..479539779 100644 --- a/crates/fs/src/nova/keys/mod.rs +++ b/crates/fs/src/nova/keys/mod.rs @@ -29,7 +29,6 @@ pub struct NovaKey { impl DeciderKey for NovaKey { type ProverKey = Self; type VerifierKey = (); - type ArithConfig = A::Config; fn to_pk(&self) -> &Self::ProverKey { self @@ -39,7 +38,7 @@ impl DeciderKey for NovaKey { &() } - fn to_arith_config(&self) -> &Self::ArithConfig { + fn to_arith_config(&self) -> ArithConfig { self.arith.config() } } @@ -131,10 +130,10 @@ where let cfg = self.arith.config(); let u = CM::Scalar::rand(&mut rng); - let x = (0..cfg.n_public_inputs()) + let x = (0..cfg.n_public_inputs) .map(|_| CM::Scalar::rand(&mut rng)) .collect::>(); - let w = (0..cfg.n_witnesses()) + let w = (0..cfg.n_witnesses) .map(|_| CM::Scalar::rand(&mut rng)) .collect::>(); let e = self.arith.eval_relation( diff --git a/crates/fs/src/nova/witnesses/mod.rs b/crates/fs/src/nova/witnesses/mod.rs index 5f144df36..dbb673c41 100644 --- a/crates/fs/src/nova/witnesses/mod.rs +++ b/crates/fs/src/nova/witnesses/mod.rs @@ -28,12 +28,12 @@ impl FoldingWitness for RunningWitness { } } -impl Dummy<&Cfg> for RunningWitness { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for RunningWitness { + fn dummy(cfg: &ArithConfig) -> Self { Self { - e: vec![Default::default(); cfg.n_constraints()], + e: vec![Default::default(); cfg.n_constraints], r_e: Default::default(), - w: vec![Default::default(); cfg.n_witnesses()], + w: vec![Default::default(); cfg.n_witnesses], r_w: Default::default(), } } @@ -56,10 +56,10 @@ impl FoldingWitness for IncomingWitness { } } -impl Dummy<&Cfg> for IncomingWitness { - fn dummy(cfg: &Cfg) -> Self { +impl Dummy<&ArithConfig> for IncomingWitness { + fn dummy(cfg: &ArithConfig) -> Self { Self { - w: vec![Default::default(); cfg.n_witnesses()], + w: vec![Default::default(); cfg.n_witnesses], r_w: Default::default(), } } diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 7593b72ec..925aea553 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -7,12 +7,13 @@ use ark_r1cs_std::{ fields::{FieldVar, fp::FpVar}, }; use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use ark_std::marker::PhantomData; use sonobe_fs::{ FoldingInstanceVar, FoldingSchemeFullVerifierGadget, FoldingSchemePartialVerifierGadget, GroupBasedFoldingSchemePrimary, GroupBasedFoldingSchemeSecondary, }; use sonobe_primitives::{ - arithmetizations::Arith, + arithmetizations::ArithConfig, circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, traits::{Dummy, SonobeCurve}, @@ -30,10 +31,36 @@ pub struct AugmentedCircuit< FC: FCircuit, T: Transcript, > { - pub(super) hash_config: T::Config, - pub(super) arith1_config: &'a ::Config, - pub(super) arith2_config: &'a ::Config, - pub(super) step_circuit: &'a FC, + _fs: PhantomData<(FS1, FS2)>, + hash_config: &'a T::Config, + arith1_config: &'a ArithConfig, + arith2_config: &'a ArithConfig, + step_circuit: &'a FC, +} + +impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> +where + FS1: GroupBasedFoldingSchemePrimary<1, 1>, + FS2: GroupBasedFoldingSchemeSecondary<1, 1>, + FC: FCircuit, + T: Transcript, +{ + /// [`AugmentedCircuit::new`] creates an instance of the augmented circuit + /// for the given step circuit. + pub fn new( + hash_config: &'a T::Config, + arith1_config: &'a ArithConfig, + arith2_config: &'a ArithConfig, + step_circuit: &'a FC, + ) -> Self { + Self { + _fs: PhantomData, + hash_config, + arith1_config, + arith2_config, + step_circuit, + } + } } impl<'a, FS1, FS2, FC, T> AugmentedCircuit<'a, FS1, FS2, FC, T> @@ -77,7 +104,7 @@ where cf_proofs: Vec>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { let hash = T::Gadget::new_with_pp_hash( - &self.hash_config, + self.hash_config, &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; let sponge = hash.separate_domain("sponge".as_ref())?; @@ -123,15 +150,15 @@ where let actual_UU = is_basecase.select(&U_dummy, &UU)?; // 2. Fold secondary instances. + // 2.a. Derive the public inputs to the secondary (CycleFold) + // circuits in the `i`-th step, which are obtained by calling + // the implementation of `FoldingSchemeCycleFoldExt`. + let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?; + if [cf_us.len(), cf_u_xs.len(), cf_proofs.len()] != [FS1::N_CYCLEFOLDS; 3] { + return Err(SynthesisError::Unsatisfiable); + } let mut cf_UU = cf_U; - for ((cf_u, cf_u_x), cf_proof) in cf_us - .iter() - // 2.a. Derive the public inputs to the secondary (CycleFold) - // circuits in the `i`-th step, which are obtained by calling - // the implementation of `FoldingSchemeCycleFoldExt`. - .zip(FS1::to_cyclefold_inputs([U], [u], UU, proof, rho)?) - .zip(&cf_proofs) - { + for ((cf_u, cf_u_x), cf_proof) in cf_us.iter().zip(cf_u_xs).zip(&cf_proofs) { // 2.b. Construct the incoming instance `cf_u` representing the // corresponding execution of secondary (CycleFold) circuit // with the derived public inputs. diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index bc1093295..00ad735bb 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -27,7 +27,7 @@ use sonobe_fs::{ }; use sonobe_primitives::{ algebra::field::emulated::EmulatedFieldVar, - arithmetizations::Arith, + arithmetizations::{Arith, ArithConfig}, circuits::{ArithExtractor, AssignmentsExtractor, FCircuit}, commitments::CommitmentDef, relations::WitnessInstanceSampler, @@ -109,8 +109,8 @@ pub struct Proof( impl Dummy<&Key> for Proof { fn dummy(pk: &Key) -> Self { - let cfg1 = pk.0.to_arith_config(); - let cfg2 = pk.1.to_arith_config(); + let cfg1 = &pk.0.to_arith_config(); + let cfg2 = &pk.1.to_arith_config(); Self( FS1::RW::dummy(cfg1), FS1::RU::dummy(cfg1), @@ -205,23 +205,30 @@ where // To break this circular dependency, we use a fixed-point iteration // where we start from a default arithmetization and repeatedly update // it until its configuration stabilizes. - let mut arith1 = FS1::Arith::default(); + let mut arith1_config = ArithConfig { + n_public_inputs: 1, + ..Default::default() + }; + let arith2_config = &arith2.config(); + let arith1; loop { let new_arith1 = { let cs = ArithExtractor::new(); - cs.execute_synthesizer(AugmentedCircuit:: { - hash_config: hash_config.clone(), - arith1_config: arith1.config(), - arith2_config: arith2.config(), + cs.execute_synthesizer(AugmentedCircuit::::new( + &hash_config, + &arith1_config, + arith2_config, step_circuit, - })?; + ))?; cs.arith::()? }; - if new_arith1.config() == arith1.config() { + let new_arith1_config = new_arith1.config(); + if new_arith1_config == arith1_config { + arith1 = new_arith1; break; } - arith1 = new_arith1; + arith1_config = new_arith1_config; } let dk1 = FS1::generate_keys(pp1, arith1)?; @@ -270,8 +277,8 @@ where let hash = T::new_with_pp_hash(hash_config, *pp_hash); let mut transcript = hash.separate_domain("transcript".as_ref()); - let arith1_config = dk1.to_arith_config(); - let arith2_config = dk2.to_arith_config(); + let arith1_config = &dk1.to_arith_config(); + let arith2_config = &dk2.to_arith_config(); let (mut WW, mut UU) = (Dummy::dummy(arith1_config), Dummy::dummy(arith1_config)); let mut proof = Dummy::dummy(arith1_config); @@ -313,12 +320,12 @@ where let cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { - let augmented_circuit = AugmentedCircuit:: { - hash_config: hash_config.clone(), + let augmented_circuit = AugmentedCircuit::::new( + hash_config, arith1_config, arith2_config, step_circuit, - }; + ); augmented_circuit.compute_next_state( cs, *pp_hash, diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 68458cbc0..711de1524 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -14,6 +14,7 @@ ark-relations = { workspace = true } ark-r1cs-std = { workspace = true } ark-serialize = { workspace = true } ark-std = { workspace = true, features = ["getrandom"] } +itertools = { workspace = true } num-bigint = { workspace = true, features = ["rand"] } num-integer = { workspace = true } num-traits = { workspace = true } diff --git a/crates/primitives/src/algebra/field/emulated.rs b/crates/primitives/src/algebra/field/emulated.rs index 48e73da93..6950e654f 100644 --- a/crates/primitives/src/algebra/field/emulated.rs +++ b/crates/primitives/src/algebra/field/emulated.rs @@ -1375,24 +1375,29 @@ mod tests { let rng = &mut thread_rng(); let size = 1024; - let mut lbs = vec![BigInt::zero()]; - let mut ubs: Vec = vec![(BigInt::one() << size) - BigInt::one()]; - lbs.push(-ubs[0].clone()); - ubs.push(BigInt::zero()); - lbs.push(-ubs[0].clone()); - ubs.push(ubs[0].clone()); - lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); - ubs.push(BigInt::zero()); - lbs.push(BigInt::zero()); - ubs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); - lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); - ubs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); - lbs.push(rng.gen_bigint_range(&-&ubs[0], &BigInt::zero())); - ubs.push(rng.gen_bigint_range(lbs.last().unwrap(), &BigInt::zero())); - lbs.push(rng.gen_bigint_range(&BigInt::zero(), &ubs[0])); - ubs.push(rng.gen_bigint_range(lbs.last().unwrap(), &ubs[0])); - - for (lb, ub) in lbs.into_iter().zip(ubs) { + let zero = BigInt::zero(); + let max: BigInt = (BigInt::one() << size) - BigInt::one(); + + let mut bounds = vec![(zero.clone(), max.clone())]; + + bounds.push((-&max, zero.clone())); + bounds.push((-&max, max.clone())); + bounds.push((rng.gen_bigint_range(&-&max, &zero), zero.clone())); + bounds.push((zero.clone(), rng.gen_bigint_range(&zero, &max))); + bounds.push(( + rng.gen_bigint_range(&-&max, &zero), + rng.gen_bigint_range(&zero, &max), + )); + bounds.push({ + let lb = rng.gen_bigint_range(&-&max, &zero); + (lb.clone(), rng.gen_bigint_range(&lb, &zero)) + }); + bounds.push({ + let lb = rng.gen_bigint_range(&zero, &max); + (lb.clone(), rng.gen_bigint_range(&lb, &max)) + }); + + for (lb, ub) in bounds { let mut v = vec![ lb.clone(), ub.clone(), @@ -1641,17 +1646,18 @@ mod tests { let rng = &mut thread_rng(); let a = (0..len).map(|_| Fq::rand(rng)).collect::>(); let b = (0..len).map(|_| Fq::rand(rng)).collect::>(); - let c = a.iter().zip(b.iter()).map(|(a, b)| a * b).sum::(); - let a_var = Vec::>::new_witness(cs.clone(), || Ok(a))?; - let b_var = Vec::>::new_witness(cs.clone(), || Ok(b))?; - let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; + let a_var = Vec::>::new_witness(cs.clone(), || Ok(&a[..]))?; + let b_var = Vec::>::new_witness(cs.clone(), || Ok(&b[..]))?; + let mut c = Fq::zero(); let mut r_var: LimbedVar = EmulatedFieldVar::constant(BigUint::zero().into()).into(); - for (a, b) in a_var.into_iter().zip(b_var) { - r_var = r_var.add_unaligned(&a.mul_unaligned(&b)?)?; + for i in 0..len { + c += a[i] * b[i]; + r_var = r_var.add_unaligned(&a_var[i].mul_unaligned(&b_var[i])?)?; } + let c_var = EmulatedFieldVar::new_witness(cs.clone(), || Ok(c))?; r_var.enforce_congruent(&c_var)?; assert!(cs.is_satisfied()?); diff --git a/crates/primitives/src/algebra/ops/eq.rs b/crates/primitives/src/algebra/ops/eq.rs index 294ba63aa..d6d0da9c3 100644 --- a/crates/primitives/src/algebra/ops/eq.rs +++ b/crates/primitives/src/algebra/ops/eq.rs @@ -25,6 +25,10 @@ impl EquivalenceGadget> for FpVar { impl> EquivalenceGadget<[T]> for [T] { fn enforce_equivalent(&self, other: &[T]) -> Result<(), SynthesisError> { + if self.len() != other.len() { + return Err(SynthesisError::Unsatisfiable); + } + self.iter() .zip(other) .try_for_each(|(a, b)| a.enforce_equivalent(b)) diff --git a/crates/primitives/src/algebra/ops/rlc.rs b/crates/primitives/src/algebra/ops/rlc.rs index b25a23d70..301aa2696 100644 --- a/crates/primitives/src/algebra/ops/rlc.rs +++ b/crates/primitives/src/algebra/ops/rlc.rs @@ -9,6 +9,7 @@ use ark_std::{ iter::Sum, ops::{Add, Mul}, }; +use itertools::Itertools; /// [`ScalarRLC`] computes the random linear combination for a sequence of /// scalars (i.e., each $v_i$ is a scalar). @@ -28,7 +29,7 @@ where type Value = I::Item; fn scalar_rlc(self, coeffs: &[Coeff]) -> Self::Value { - self.zip(coeffs).map(|(v, c)| v * c).sum::() + self.zip_eq(coeffs).map(|(v, c)| v * c).sum::() } } @@ -53,12 +54,12 @@ where fn slice_rlc(self, coeffs: &[Coeff]) -> Vec { let mut iter = self - .zip(coeffs) + .zip_eq(coeffs) .map(|(v, c)| v.iter().map(|x| x.clone() * c)); let first = iter.next().unwrap(); iter.fold(first.collect(), |acc, v| { - acc.into_iter().zip(v).map(|(a, b)| a + b).collect() + acc.into_iter().zip_eq(v).map(|(a, b)| a + b).collect() }) } } diff --git a/crates/primitives/src/arithmetizations/ccs/circuits.rs b/crates/primitives/src/arithmetizations/ccs/circuits.rs deleted file mode 100644 index fe9011ae7..000000000 --- a/crates/primitives/src/arithmetizations/ccs/circuits.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! This module implements in-circuit CCS variables. - -use ark_ff::PrimeField; -use ark_r1cs_std::{ - alloc::{AllocVar, AllocationMode}, - fields::fp::FpVar, -}; -use ark_relations::gr1cs::{Namespace, SynthesisError}; -use ark_std::borrow::Borrow; - -use super::{CCS, CCSVariant}; -use crate::algebra::ops::matrix::SparseMatrixVar; - -/// [`CCSMatricesVar`] is an in-circuit variable of a given CCS structure. -/// -/// Only the matrices are represented, while the remaining CCS parameters are -/// constants to the circuit. -#[allow(non_snake_case)] -#[derive(Debug, Clone)] -pub struct CCSMatricesVar { - #[allow(dead_code)] - M: Vec>>, -} - -impl AllocVar, F> for CCSMatricesVar { - fn new_variable>>( - cs: impl Into>, - f: impl FnOnce() -> Result, - _mode: AllocationMode, - ) -> Result { - f().and_then(|val| { - let cs = cs.into(); - Ok(Self { - M: val - .borrow() - .M - .iter() - .map(|m| SparseMatrixVar::new_constant(cs.clone(), m)) - .collect::>()?, - }) - }) - } -} - -// TODO: add relation check gadgets when needed. diff --git a/crates/primitives/src/arithmetizations/ccs/mod.rs b/crates/primitives/src/arithmetizations/ccs/mod.rs index c9f9a0d12..c71465f7c 100644 --- a/crates/primitives/src/arithmetizations/ccs/mod.rs +++ b/crates/primitives/src/arithmetizations/ccs/mod.rs @@ -22,126 +22,48 @@ use ark_ff::Field; use ark_poly::DenseMultilinearExtension; use ark_relations::gr1cs::{ConstraintSystem, Matrix}; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{borrow::Borrow, cfg_into_iter, cfg_iter, fmt::Debug, marker::PhantomData}; +use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{Arith, ArithRelation, Error, r1cs::R1CS}; -use crate::{ - algebra::ops::poly::MLEHelper, - arithmetizations::{ArithConfig, r1cs::R1CSConfig}, - circuits::Assignments, -}; +use super::{Arith, Error}; +use crate::{algebra::ops::poly::MLEHelper, circuits::Assignments}; -pub mod circuits; +/// [`CCS`] is an abstract trait that defines the behavior of all CCS variants, +/// including but not limited to R1CS. +pub trait CCS: + Arith + for<'a> From<&'a ConstraintSystem> + From> +{ + /// [`CCS::Field`] specifies the underlying field of a CCS instance + type Field: Field; -/// [`CCSVariant`] defines the methods that a CCS variant (e.g., R1CS) should -/// implement. -pub trait CCSVariant: Clone + Debug + PartialEq + Default + Sync + Send { - /// [`CCSVariant::n_matrices`] returns the number of matrices in the CCS - /// variant. - fn n_matrices() -> usize; + /// [`CCS::matrices`] returns the matrices contained in a concrete CCS + /// instance `self`. + fn matrices(&self) -> &[Matrix]; - /// [`CCSVariant::degree`] returns the degree of the CCS variant. - fn degree() -> usize; - - /// [`CCSVariant::multisets_vec`] returns the vector of multisets in the CCS - /// variant. - fn multisets_vec() -> Vec>; - - /// [`CCSVariant::coefficients_vec`] returns the vector of coefficients in - /// the CCS variant. - fn coefficients_vec() -> Vec; -} - -/// [`CCSConfig`] stores the shape parameters of a CCS structure. -#[allow(non_snake_case)] -#[derive(Clone, Debug, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCSConfig { - _v: PhantomData, - /// m: number of rows in M_i (such that M_i \in F^{m, n}) - m: usize, - /// n = |z|, number of cols in M_i - n: usize, - /// l = |io|, size of public input/output - l: usize, -} - -impl ArithConfig for CCSConfig { - #[inline] - fn degree(&self) -> usize { - V::degree() - } - - #[inline] - fn n_constraints(&self) -> usize { - self.m - } - - #[inline] - fn n_variables(&self) -> usize { - self.n - } - - #[inline] - fn n_public_inputs(&self) -> usize { - self.l - } - - #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 - } -} - -impl, V: CCSVariant> From for CCSConfig { - fn from(cfg: Cfg) -> Self { - let cfg = cfg.borrow(); - Self { - _v: PhantomData, - m: cfg.n_constraints(), - n: cfg.n_variables(), - l: cfg.n_public_inputs(), - } - } -} - -impl From<&ConstraintSystem> for CCSConfig { - fn from(cs: &ConstraintSystem) -> Self { - R1CSConfig::from(cs).into() - } -} - -/// [`CCS`] holds the CCS matrices `M` together with the configuration. -#[allow(non_snake_case)] -#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] -pub struct CCS { - cfg: CCSConfig, - - pub(super) M: Vec>, -} - -impl CCS { - /// [`CCS::evaluate_at`] evaluates the CCS relation at a given vector of - /// assignments `z`. - pub fn evaluate_at(&self, z: Assignments + Sync>) -> Result, Error> { - let cfg = &self.cfg; + /// [`CCS::evaluate_ccs`] evaluates the CCS relation at a given vector of + /// assignments, multisets, and coefficients. + fn evaluate_ccs( + &self, + z: Assignments + Sync>, + multisets: [Vec; Q], + coefficients: [Self::Field; Q], + ) -> Result, Error> { + let cfg = self.config(); + let matrices = self.matrices(); let public_len = z.public.as_ref().len(); let private_len = z.private.as_ref().len(); - if public_len != cfg.n_public_inputs() { + if public_len != cfg.n_public_inputs { return Err(Error::MalformedAssignments(format!( "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", - cfg.n_public_inputs(), - public_len + cfg.n_public_inputs, public_len ))); } - if private_len != cfg.n_witnesses() { + if private_len != cfg.n_witnesses { return Err(Error::MalformedAssignments(format!( "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", - cfg.n_witnesses(), - private_len + cfg.n_witnesses, private_len ))); } @@ -156,13 +78,13 @@ impl CCS { // We parallelize the outer loop over rows (when the `parallel` feature // is enabled), since the number of constraints in the CCS is typically // large in practice. - Ok(cfg_into_iter!(0..cfg.n_constraints()) + Ok(cfg_into_iter!(0..cfg.n_constraints) .map(|row| { // The `row`-th entry of the resulting vector is: // `Σ_{i ∈ {0, q-1}} (c_i · 〇_{j ∈ S_i} (M_j[row] · z))` - V::multisets_vec() - .into_iter() - .zip(V::coefficients_vec::()) + multisets + .iter() + .zip(coefficients) .map(|(s, c)| { // Each term in the sum is: // `c_i · 〇_{j ∈ S_i} (M_j[row] · z)` @@ -171,12 +93,12 @@ impl CCS { .map(|&i| { // Each factor in the product is `M_j[row] · z`, // i.e., the dot product of `M_j[row]` and `z`. - self.M[i][row] + matrices[i][row] .iter() .map(|(val, col)| z[*col] * val) - .sum::() + .sum::() }) - .product::() + .product::() }) .sum() }) @@ -185,14 +107,15 @@ impl CCS { /// [`CCS::mles`] returns the multilinear extensions of all CCS matrices /// `M_i` evaluated over the assignments `z`. - pub fn mles( + fn mles( &self, - z: Assignments + Sync>, - ) -> Vec> { - (0..V::n_matrices()) - .map(|i| { + z: Assignments + Sync>, + ) -> Vec> { + self.matrices() + .iter() + .map(|matrix| { DenseMultilinearExtension::from_evaluations( - &cfg_iter!(self.M[i]) + &cfg_iter!(matrix) .map(|row| row.iter().map(|(val, col)| z[*col] * val).sum()) .collect::>(), ) @@ -200,133 +123,3 @@ impl CCS { .collect() } } - -impl Default for CCS { - #[inline] - fn default() -> Self { - Self { - cfg: CCSConfig::default(), - M: vec![vec![]; V::n_matrices()], - } - } -} - -impl Arith for CCS { - type Config = CCSConfig; - - #[inline] - fn config(&self) -> &Self::Config { - &self.cfg - } - - #[inline] - fn config_mut(&mut self) -> &mut Self::Config { - &mut self.cfg - } -} - -impl, U: AsRef<[F]>, V: CCSVariant> ArithRelation for CCS { - type Evaluation = Vec; - - fn eval_relation(&self, w: &W, u: &U) -> Result { - self.evaluate_at((F::one(), u.as_ref(), w.as_ref()).into()) - } - - fn check_evaluation(_w: &W, _u: &U, e: Self::Evaluation) -> Result<(), Error> { - cfg_into_iter!(e) - .all(|i| i.is_zero()) - .then_some(()) - .ok_or(Error::UnsatisfiedAssignments( - "Evaluation contains non-zero values".into(), - )) - } -} - -impl From> for CCS { - fn from(r1cs: R1CS) -> Self { - Self { - cfg: r1cs.config().into(), - M: vec![r1cs.A, r1cs.B, r1cs.C], - } - } -} - -impl From<&ConstraintSystem> for CCS { - fn from(cs: &ConstraintSystem) -> Self { - R1CS::from(cs).into() - } -} - -impl From> for CCS { - fn from(cs: ConstraintSystem) -> Self { - Self::from(&cs) - } -} - -#[cfg(test)] -mod tests { - use ark_bn254::Fr; - use ark_ff::{One, UniformRand, Zero}; - use ark_std::{error::Error, rand::thread_rng}; - - use super::*; - use crate::{ - circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, - relations::Relation, - }; - - #[test] - fn test_eval() -> Result<(), Box> { - let mut rng = thread_rng(); - let ccs: CCS = constraints_for_test::().into(); - - assert!( - ccs.evaluate_at(satisfying_assignments_for_test(Fr::rand(&mut rng)))? - .into_iter() - .all(|e| e.is_zero()) - ); - assert!( - !ccs.evaluate_at(Assignments::from(( - Fr::one(), - vec![Fr::rand(&mut rng)], - vec![ - Fr::rand(&mut rng), - Fr::rand(&mut rng), - Fr::rand(&mut rng), - Fr::rand(&mut rng), - ], - )))? - .into_iter() - .all(|e| e.is_zero()) - ); - - Ok(()) - } - - #[test] - fn test_check() -> Result<(), Box> { - let mut rng = thread_rng(); - let ccs: CCS = constraints_for_test::().into(); - - let assignments = satisfying_assignments_for_test(Fr::rand(&mut rng)); - - assert!( - ccs.check_relation(&assignments.private, &assignments.public) - .is_ok() - ); - assert!( - ccs.check_relation( - &[ - Fr::rand(&mut rng), - Fr::rand(&mut rng), - Fr::rand(&mut rng), - Fr::rand(&mut rng), - ], - &[Fr::rand(&mut rng)] - ) - .is_err() - ); - - Ok(()) - } -} diff --git a/crates/primitives/src/arithmetizations/mod.rs b/crates/primitives/src/arithmetizations/mod.rs index c0e2457e1..f592561ac 100644 --- a/crates/primitives/src/arithmetizations/mod.rs +++ b/crates/primitives/src/arithmetizations/mod.rs @@ -34,31 +34,34 @@ pub enum Error { } /// [`ArithConfig`] describes the configuration of a constraint system. -pub trait ArithConfig: Clone + Debug + Default + PartialEq { - /// [`ArithConfig::degree`] returns the degree of the constraint system. - fn degree(&self) -> usize; +#[derive(Clone, Debug, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] +pub struct ArithConfig { + /// [`ArithConfig::degree`] specifies the degree of the constraint system. + pub degree: usize, - /// [`ArithConfig::n_constraints`] returns the number of constraints in the - /// constraint system. - fn n_constraints(&self) -> usize; - - /// [`ArithConfig::log_constraints`] returns the base-2 logarithm of the - /// number of constraints in the constraint system. - fn log_constraints(&self) -> usize { - log2(self.n_constraints()) as usize - } + /// [`ArithConfig::n_constraints`] specifies the number of constraints in + /// the constraint system. + pub n_constraints: usize, - /// [`ArithConfig::n_variables`] returns the number of variables in the + /// [`ArithConfig::n_variables`] specifies the number of variables in the /// constraint system. - fn n_variables(&self) -> usize; + pub n_variables: usize, - /// [`ArithConfig::n_public_inputs`] returns the number of public inputs in - /// the constraint system. - fn n_public_inputs(&self) -> usize; + /// [`ArithConfig::n_public_inputs`] specifies the number of public inputs + /// in the constraint system. + pub n_public_inputs: usize, - /// [`ArithConfig::n_witnesses`] returns the number of witnesses in the + /// [`ArithConfig::n_witnesses`] specifies the number of witnesses in the /// constraint system. - fn n_witnesses(&self) -> usize; + pub n_witnesses: usize, +} + +impl ArithConfig { + /// [`ArithConfig::log_constraints`] returns the base-2 logarithm of the + /// number of constraints in the constraint system. + pub fn log_constraints(&self) -> usize { + log2(self.n_constraints) as usize + } } /// [`Arith`] is a trait for constraint systems (R1CS, CCS, etc.), where we @@ -66,16 +69,8 @@ pub trait ArithConfig: Clone + Debug + Default + PartialEq { /// In addition to the configuration, the implementor of this trait may also /// store the actual constraints and other information. pub trait Arith: Clone + Default + Send + Sync + CanonicalSerialize + CanonicalDeserialize { - /// [`Arith::Config`] specifies the arithmetization's configuration. - type Config: ArithConfig; - - /// [`Arith::config`] returns a reference to the configuration of the - /// constraint system. - fn config(&self) -> &Self::Config; - - /// [`Arith::config_mut`] returns a mutable reference to the configuration - /// of the constraint system. - fn config_mut(&mut self) -> &mut Self::Config; + /// [`Arith::config`] returns the configuration of the constraint system. + fn config(&self) -> ArithConfig; } /// [`ArithRelation`] treats a constraint system as a relation between a witness diff --git a/crates/primitives/src/arithmetizations/r1cs/circuits.rs b/crates/primitives/src/arithmetizations/r1cs/circuits.rs index 0d983a4a8..594c30be3 100644 --- a/crates/primitives/src/arithmetizations/r1cs/circuits.rs +++ b/crates/primitives/src/arithmetizations/r1cs/circuits.rs @@ -44,9 +44,21 @@ impl> let val = val.borrow(); Ok(Self { - A: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.A), mode)?, - B: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.B), mode)?, - C: SparseMatrixVar::::new_variable(cs.clone(), || Ok(&val.C), mode)?, + A: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[0]), + mode, + )?, + B: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[1]), + mode, + )?, + C: SparseMatrixVar::::new_variable( + cs.clone(), + || Ok(&val.matrices[2]), + mode, + )?, }) }) } @@ -115,13 +127,13 @@ mod tests { let r1cs = constraints_for_test::(); assert!( - r1cs.evaluate_at(satisfying_assignments_for_test(Fr::rand(&mut rng)))? + r1cs.evaluate_r1cs(satisfying_assignments_for_test(Fr::rand(&mut rng)))? .into_iter() .all(|e| e.is_zero()) ); assert!( !r1cs - .evaluate_at(Assignments::from(( + .evaluate_r1cs(Assignments::from(( Fr::one(), vec![Fr::rand(&mut rng)], vec![ diff --git a/crates/primitives/src/arithmetizations/r1cs/mod.rs b/crates/primitives/src/arithmetizations/r1cs/mod.rs index 9e818e405..3cfb2106e 100644 --- a/crates/primitives/src/arithmetizations/r1cs/mod.rs +++ b/crates/primitives/src/arithmetizations/r1cs/mod.rs @@ -4,189 +4,74 @@ use ark_ff::Field; use ark_relations::gr1cs::{ConstraintSystem, Matrix, R1CS_PREDICATE_LABEL}; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; -use ark_std::{cfg_into_iter, cfg_iter, iterable::Iterable}; +use ark_std::{cfg_into_iter, cfg_iter}; #[cfg(feature = "parallel")] use rayon::prelude::*; -use super::{Arith, ArithRelation, Error, ccs::CCS}; -use crate::{ - arithmetizations::{ArithConfig, ccs::CCSVariant}, - circuits::Assignments, -}; +use super::{Arith, ArithConfig, ArithRelation, Error, ccs::CCS}; +use crate::circuits::Assignments; pub mod circuits; -/// [`R1CSConfig`] stores the shape parameters of an R1CS structure. +/// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the +/// configuration. #[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CSConfig { +pub struct R1CS { m: usize, // number of constraints n: usize, // number of variables l: usize, // io len + matrices: [Matrix; 3], } -impl R1CSConfig { - /// [`R1CSConfig::new`] creates a new R1CS configuration. - pub fn new(n_constraints: usize, n_variables: usize, n_public_inputs: usize) -> Self { - Self { - m: n_constraints, - n: n_variables, - l: n_public_inputs, - } - } -} - -impl ArithConfig for R1CSConfig { - #[inline] - fn degree(&self) -> usize { - 2 - } - - #[inline] - fn n_constraints(&self) -> usize { - self.m - } - - #[inline] - fn n_variables(&self) -> usize { - self.n - } - - #[inline] - fn n_public_inputs(&self) -> usize { - self.l - } - - #[inline] - fn n_witnesses(&self) -> usize { - self.n_variables() - self.n_public_inputs() - 1 - } -} - -impl From<&ConstraintSystem> for R1CSConfig { - fn from(cs: &ConstraintSystem) -> Self { - Self::new( - cs.num_constraints(), - cs.num_instance_variables + cs.num_witness_variables, - cs.num_instance_variables - 1, // -1 to subtract the first '1' - ) - } -} - -impl CCSVariant for R1CSConfig { - #[inline] - fn n_matrices() -> usize { - 3 - } - - #[inline] - fn degree() -> usize { - 2 - } - - #[inline] - fn multisets_vec() -> Vec> { - vec![vec![0, 1], vec![2]] - } - +impl Arith for R1CS { #[inline] - fn coefficients_vec() -> Vec { - vec![F::one(), -F::one()] - } -} - -/// [`R1CS`] holds the three sparse matrices `A`, `B`, `C` together with the -/// configuration. -#[allow(non_snake_case)] -#[derive(Debug, Clone, Default, PartialEq, CanonicalSerialize, CanonicalDeserialize)] -pub struct R1CS { - cfg: R1CSConfig, - pub(super) A: Matrix, - pub(super) B: Matrix, - pub(super) C: Matrix, -} - -type Row = Vec<(F, usize)>; - -impl R1CS { - /// [`R1CS::evaluate_rows`] evaluates the R1CS relation by applying the - /// provided function `f` to each triplet of rows `(A[i], B[i], C[i])`. - pub fn evaluate_rows( - &self, - f: impl Fn(((&Row, &Row), &Row)) -> Result + Send + Sync, - ) -> Result, Error> { - cfg_iter!(self.A).zip(&self.B).zip(&self.C).map(f).collect() - } - - /// [`R1CS::evaluate_at`] evaluates the R1CS relation at a given vector of - /// assignments `z`. - pub fn evaluate_at(&self, z: Assignments + Sync>) -> Result, Error> { - let cfg = &self.cfg; - - let public_len = z.public.as_ref().len(); - let private_len = z.private.as_ref().len(); - if public_len != cfg.n_public_inputs() { - return Err(Error::MalformedAssignments(format!( - "The number of public inputs in R1CS ({}) does not match the length of the provided public inputs ({}).", - cfg.n_public_inputs(), - public_len - ))); + fn config(&self) -> ArithConfig { + ArithConfig { + degree: 2, + n_constraints: self.m, + n_variables: self.n, + n_public_inputs: self.l, + n_witnesses: self.n - self.l - 1, } - if private_len != cfg.n_witnesses() { - return Err(Error::MalformedAssignments(format!( - "The number of witnesses in R1CS ({}) does not match the length of the provided witnesses ({}).", - cfg.n_witnesses(), - private_len - ))); - } - - self.evaluate_rows(|((a, b), c)| { - let az = a.iter().map(|(val, col)| z[*col] * val).sum::(); - let bz = b.iter().map(|(val, col)| z[*col] * val).sum::(); - let cz = c.iter().map(|(val, col)| z[*col] * val).sum::(); - // use `z[0]` here since the constant term at index 0 may not be 1 - // for relaxed instances - Ok(az * bz - z[0] * cz) - }) } } -impl Arith for R1CS { - type Config = R1CSConfig; +impl CCS for R1CS { + type Field = F; - #[inline] - fn config(&self) -> &Self::Config { - &self.cfg - } - - #[inline] - fn config_mut(&mut self) -> &mut Self::Config { - &mut self.cfg + fn matrices(&self) -> &[Matrix] { + &self.matrices[..] } } impl R1CS { /// [`R1CS::new`] creates a new R1CS structure from the given configuration /// and matrices. - #[allow(non_snake_case)] - pub fn new(cfg: R1CSConfig, [A, B, C]: [Matrix; 3]) -> Self { - Self { cfg, A, B, C } + pub fn new( + n_constraints: usize, + n_variables: usize, + n_public_inputs: usize, + matrices: [Matrix; 3], + ) -> Self { + Self { + m: n_constraints, + l: n_public_inputs, + n: n_variables, + matrices, + } } -} -impl TryFrom> for R1CS { - type Error = Error; - - fn try_from(ccs: CCS) -> Result { - let cfg = ccs.config(); - Ok(Self::new( - R1CSConfig::new( - cfg.n_constraints(), - cfg.n_variables(), - cfg.n_public_inputs(), - ), - // `unwrap` is safe here because the type parameter T = 3 - ccs.M.try_into().unwrap(), - )) + /// [`R1CS::evaluate_r1cs`] evaluates the R1CS relation at a given vector of + /// assignments `z`. + /// + /// This method is simply a wrapper of [`CCS::evaluate_ccs`] with fixed + /// coefficients and multisets. + pub fn evaluate_r1cs( + &self, + z: Assignments + Sync>, + ) -> Result, Error> { + let u = z[0]; + self.evaluate_ccs(z, [vec![0, 1], vec![2]], [F::one(), -u]) } } @@ -196,7 +81,12 @@ impl From<&ConstraintSystem> for R1CS { let r1cs_predicate = &cs.predicate_constraint_systems[R1CS_PREDICATE_LABEL]; let matrices = r1cs_predicate.to_matrices(cs); // `unwrap` is safe here because R1CS always has 3 matrices - R1CS::new(cs.into(), matrices.try_into().unwrap()) + R1CS::new( + cs.num_constraints(), + cs.num_instance_variables + cs.num_witness_variables, + cs.num_instance_variables - 1, // -1 to subtract the first '1' + matrices.try_into().unwrap(), + ) } } @@ -210,7 +100,7 @@ impl, U: AsRef<[F]>> ArithRelation for R1CS { type Evaluation = Vec; fn eval_relation(&self, w: &W, x: &U) -> Result { - self.evaluate_at((F::one(), x.as_ref(), w.as_ref()).into()) + self.evaluate_r1cs((F::one(), x.as_ref(), w.as_ref()).into()) } fn check_evaluation(_w: &W, _x: &U, e: Self::Evaluation) -> Result<(), Error> { @@ -251,7 +141,7 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 w: &RelaxedWitness<&[F]>, u: &RelaxedInstance<&[F]>, ) -> Result { - self.evaluate_at((*u.u, u.x, w.w).into()) + self.evaluate_r1cs((*u.u, u.x, w.w).into()) } fn check_evaluation( @@ -259,6 +149,14 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 _u: &RelaxedInstance<&[F]>, v: Self::Evaluation, ) -> Result<(), Error> { + if w.e.len() != v.len() { + return Err(Error::MalformedAssignments(format!( + "The number of constraints in R1CS ({}) does not match the length of the provided relaxed witness's error term ({}).", + v.len(), + w.e.len() + ))); + } + cfg_iter!(w.e) .zip(&v) .all(|(e, v)| e == v) @@ -273,51 +171,37 @@ impl ArithRelation, RelaxedInstance<&[F]>> for R1 mod tests { use ark_bn254::Fr; use ark_ff::UniformRand; - use ark_relations::gr1cs::ConstraintSynthesizer; use ark_std::{error::Error, rand::thread_rng}; - #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] - use wasm_bindgen_test::wasm_bindgen_test as test; - use super::*; - use crate::circuits::{ - ArithExtractor, AssignmentsExtractor, - utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, + use crate::{ + circuits::utils::{constraints_for_test, satisfying_assignments_for_test}, + relations::Relation, }; #[test] - fn test_satisfiability() -> Result<(), Box> { - let mut rng = thread_rng(); - let circuit = CircuitForTest:: { - x: Fr::rand(&mut rng), - }; - let cs = ConstraintSystem::new_ref(); - circuit.generate_constraints(cs.clone())?; - assert!(cs.is_satisfied()?); - - Ok(()) - } - - #[test] - fn test_constraint_extraction() -> Result<(), Box> { - let mut rng = thread_rng(); - let circuit = CircuitForTest:: { - x: Fr::rand(&mut rng), - }; - let cs = ArithExtractor::new(); - cs.execute_synthesizer(circuit)?; - assert_eq!(cs.arith::>()?, constraints_for_test()); - Ok(()) - } - - #[test] - fn test_witness_extraction() -> Result<(), Box> { + fn test_check() -> Result<(), Box> { let mut rng = thread_rng(); - let x = Fr::rand(&mut rng); - let circuit = CircuitForTest:: { x }; + let r1cs = constraints_for_test::(); + + let assignments = satisfying_assignments_for_test(Fr::rand(&mut rng)); + + assert!( + r1cs.check_relation(&assignments.private, &assignments.public) + .is_ok() + ); + assert!( + r1cs.check_relation( + &[ + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + Fr::rand(&mut rng), + ], + &[Fr::rand(&mut rng)] + ) + .is_err() + ); - let cs = AssignmentsExtractor::new(); - cs.execute_synthesizer(circuit)?; - assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); Ok(()) } } diff --git a/crates/primitives/src/circuits/mod.rs b/crates/primitives/src/circuits/mod.rs index a466876f6..1d14bf976 100644 --- a/crates/primitives/src/circuits/mod.rs +++ b/crates/primitives/src/circuits/mod.rs @@ -277,3 +277,56 @@ impl WitnessToPublic for [T] { self.iter().try_for_each(|x| x.mark_as_public()) } } + +#[cfg(test)] +mod tests { + use ark_bn254::Fr; + use ark_ff::UniformRand; + use ark_relations::gr1cs::ConstraintSynthesizer; + use ark_std::{error::Error, rand::thread_rng}; + #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] + use wasm_bindgen_test::wasm_bindgen_test as test; + + use super::{ + utils::{CircuitForTest, constraints_for_test, satisfying_assignments_for_test}, + *, + }; + use crate::arithmetizations::r1cs::R1CS; + + #[test] + fn test_satisfiability() -> Result<(), Box> { + let mut rng = thread_rng(); + let circuit = CircuitForTest:: { + x: Fr::rand(&mut rng), + }; + let cs = ConstraintSystem::new_ref(); + circuit.generate_constraints(cs.clone())?; + assert!(cs.is_satisfied()?); + + Ok(()) + } + + #[test] + fn test_constraint_extraction() -> Result<(), Box> { + let mut rng = thread_rng(); + let circuit = CircuitForTest:: { + x: Fr::rand(&mut rng), + }; + let cs = ArithExtractor::new(); + cs.execute_synthesizer(circuit)?; + assert_eq!(cs.arith::>()?, constraints_for_test()); + Ok(()) + } + + #[test] + fn test_witness_extraction() -> Result<(), Box> { + let mut rng = thread_rng(); + let x = Fr::rand(&mut rng); + let circuit = CircuitForTest:: { x }; + + let cs = AssignmentsExtractor::new(); + cs.execute_synthesizer(circuit)?; + assert_eq!(cs.assignments()?, satisfying_assignments_for_test(x)); + Ok(()) + } +} diff --git a/crates/primitives/src/circuits/utils.rs b/crates/primitives/src/circuits/utils.rs index 464d248d3..0917544f9 100644 --- a/crates/primitives/src/circuits/utils.rs +++ b/crates/primitives/src/circuits/utils.rs @@ -9,11 +9,7 @@ use ark_r1cs_std::{ use ark_relations::gr1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError, Variable}; use super::Assignments; -use crate::{ - arithmetizations::r1cs::{R1CS, R1CSConfig}, - circuits::FCircuit, - traits::SonobeField, -}; +use crate::{arithmetizations::r1cs::R1CS, circuits::FCircuit, traits::SonobeField}; /// [`CircuitForTest`] implements a simple test circuit computing /// `y = x^3 + x + 5` with 4 R1CS constraints. @@ -139,7 +135,7 @@ pub fn constraints_for_test() -> R1CS { vec![(F::one(), 2)], ]; - R1CS::::new(R1CSConfig::new(4, 6, 1), [A, B, C]) + R1CS::::new(4, 6, 1, [A, B, C]) } /// [`satisfying_assignments_for_test`] returns a satisfying assignment for the diff --git a/crates/primitives/src/transcripts/griffin/mod.rs b/crates/primitives/src/transcripts/griffin/mod.rs index d3f3dad9b..a97fd7f77 100644 --- a/crates/primitives/src/transcripts/griffin/mod.rs +++ b/crates/primitives/src/transcripts/griffin/mod.rs @@ -46,6 +46,7 @@ use ark_r1cs_std::{ }; use ark_relations::gr1cs::SynthesisError; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use itertools::Itertools; use num_bigint::BigUint; use sha3::{ Shake128, Shake128Reader, @@ -236,7 +237,10 @@ impl Griffin { input.iter().skip(1).for_each(|el| sum.add_assign(el)); if round < params.rounds - 1 { - for (el, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { + for (el, rc) in input + .iter_mut() + .zip_eq(params.round_constants[round].iter()) + { el.add_assign(&sum); el.add_assign(rc); // add round constant } @@ -277,7 +281,10 @@ impl Griffin { input[3] = t_4; if round < params.rounds - 1 { - for (i, rc) in input.iter_mut().zip(params.round_constants[round].iter()) { + for (i, rc) in input + .iter_mut() + .zip_eq(params.round_constants[round].iter()) + { i.add_assign(rc); } } @@ -468,7 +475,7 @@ impl GriffinGadget { current_state = params .mat .iter() - .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum()) .collect(); for r in 0..params.rounds { @@ -476,12 +483,12 @@ impl GriffinGadget { current_state = params .mat .iter() - .map(|row| current_state.iter().zip(row).map(|(a, b)| a * *b).sum()) + .map(|row| current_state.iter().zip_eq(row).map(|(a, b)| a * *b).sum()) .collect(); if r < params.rounds - 1 { current_state = current_state .iter() - .zip(¶ms.round_constants[r]) + .zip_eq(¶ms.round_constants[r]) .map(|(c, rc)| c + *rc) .collect(); }