use ark_crypto_primitives::sponge::{constraints::AbsorbGadget, Absorb}; use ark_ec::CurveGroup; use ark_ff::PrimeField; use ark_r1cs_std::{ eq::EqGadget, fields::{fp::FpVar, FieldVar}, uint8::UInt8, ToConstraintFieldGadget, }; use ark_relations::r1cs::SynthesisError; use ark_std::{cfg_into_iter, log2, One}; use rayon::prelude::*; use super::{ constants::RUNNING, utils::{pow_i, pow_i_var}, CommittedInstance, CommittedInstanceVar, Witness, WitnessVar, }; use crate::{ arith::{ r1cs::{circuits::R1CSMatricesVar, R1CS}, Arith, ArithGadget, }, folding::circuits::CF1, transcript::AbsorbNonNative, utils::vec::is_zero_vec, Error, }; // Implements the trait for absorbing ProtoGalaxy's CommittedInstance. impl Absorb for CommittedInstance where C::ScalarField: Absorb, { fn to_sponge_bytes(&self, dest: &mut Vec) { C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest); } fn to_sponge_field_elements(&self, dest: &mut Vec) { self.phi .to_native_sponge_field_elements_as_vec() .to_sponge_field_elements(dest); self.betas.to_sponge_field_elements(dest); self.e.to_sponge_field_elements(dest); self.x.to_sponge_field_elements(dest); } } // Implements the trait for absorbing ProtoGalaxy's CommittedInstanceVar in-circuit. impl AbsorbGadget for CommittedInstanceVar { fn to_sponge_bytes(&self) -> Result>, SynthesisError> { FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?) } fn to_sponge_field_elements(&self) -> Result>, SynthesisError> { Ok([ self.phi.to_constraint_field()?, self.betas.to_sponge_field_elements()?, self.e.to_sponge_field_elements()?, self.x.to_sponge_field_elements()?, ] .concat()) } } /// Implements `Arith` for R1CS, where the witness is of type [`Witness`], and /// the committed instance is of type [`CommittedInstance`]. /// /// Due to the error term `CommittedInstance.e`, R1CS here is considered as a /// relaxed R1CS. /// /// See `nova/traits.rs` for the rationale behind the design. impl Arith>, CommittedInstance> for R1CS> { type Evaluation = Vec>; fn eval_relation( &self, w: &Witness>, u: &CommittedInstance, ) -> Result { self.eval_at_z(&[&[C::ScalarField::one()][..], &u.x, &w.w].concat()) } fn check_evaluation( _w: &Witness, u: &CommittedInstance, e: Vec, ) -> Result<(), Error> { let ok = if TYPE == RUNNING { if u.betas.len() != log2(e.len()) as usize { return Err(Error::NotSameLength( "instance.betas.len()".to_string(), u.betas.len(), "log2(e.len())".to_string(), log2(e.len()) as usize, )); } u.e == cfg_into_iter!(e) .enumerate() .map(|(i, e_i)| pow_i(i, &u.betas) * e_i) .sum::>() } else { is_zero_vec(&e) }; ok.then_some(()).ok_or(Error::NotSatisfied) } } /// Unlike its native counterpart, we only need to support running instances in /// circuit, as the decider circuit only checks running instance satisfiability. impl ArithGadget>, CommittedInstanceVar> for R1CSMatricesVar, FpVar>> { type Evaluation = (Vec>>, Vec>>); fn eval_relation( &self, w: &WitnessVar>, u: &CommittedInstanceVar, ) -> Result { self.eval_at_z(&[&[FpVar::one()][..], &u.x, &w.W].concat()) } fn enforce_evaluation( _w: &WitnessVar, u: &CommittedInstanceVar, (AzBz, uCz): Self::Evaluation, ) -> Result<(), SynthesisError> { let mut e = vec![]; for (i, (l, r)) in AzBz.iter().zip(uCz).enumerate() { e.push(pow_i_var(i, &u.betas) * (l - r)); } // Call `sum` on a vector instead of computing the sum in the above loop // to avoid stack overflow (the cause of this is similar to issue #80 // https://github.com/privacy-scaling-explorations/sonobe/issues/80) e.iter().sum::>().enforce_equal(&u.e) } } #[cfg(test)] pub mod tests { use super::*; use ark_bn254::{Fr, G1Projective as Projective}; use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; use ark_relations::r1cs::ConstraintSystem; use ark_std::UniformRand; use rand::Rng; /// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements} /// vs the R1CS constraints version #[test] pub fn test_committed_instance_to_sponge_preimage() { let mut rng = ark_std::test_rng(); let t = rng.gen::() as usize; let io_len = rng.gen::() as usize; let ci = CommittedInstance:: { phi: Projective::rand(&mut rng), betas: (0..t).map(|_| Fr::rand(&mut rng)).collect(), e: Fr::rand(&mut rng), x: (0..io_len).map(|_| Fr::rand(&mut rng)).collect(), }; let bytes = ci.to_sponge_bytes_as_vec(); let field_elements = ci.to_sponge_field_elements_as_vec(); let cs = ConstraintSystem::::new_ref(); let ciVar = CommittedInstanceVar::::new_witness(cs.clone(), || Ok(ci.clone())) .unwrap(); let bytes_var = ciVar.to_sponge_bytes().unwrap(); let field_elements_var = ciVar.to_sponge_field_elements().unwrap(); assert!(cs.is_satisfied().unwrap()); // check that the natively computed and in-circuit computed hashes match assert_eq!(bytes_var.value().unwrap(), bytes); assert_eq!(field_elements_var.value().unwrap(), field_elements); } }