From 216d1b986cd2160d9fe390d523738c56464b5cbf Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 16:45:20 +0800 Subject: [PATCH 1/2] Implement recording and replaying transcripts so we don't need to return challenges explicitly --- crates/fs/src/definitions/algorithms.rs | 2 +- crates/fs/src/definitions/circuits.rs | 2 +- crates/fs/src/lib.rs | 6 +- crates/fs/src/nova/algorithms/prover.rs | 8 +- crates/fs/src/nova/circuits/verifier.rs | 110 ++++++++---------- .../src/compilers/cyclefold/adapters/nova.rs | 30 +++-- .../ivc/src/compilers/cyclefold/circuits.rs | 21 ++-- crates/ivc/src/compilers/cyclefold/mod.rs | 31 ++--- .../src/transcripts/griffin/sponge.rs | 64 +++------- crates/primitives/src/transcripts/mod.rs | 56 ++++++--- .../src/transcripts/poseidon/sponge.rs | 57 ++++----- .../src/transcripts/recording/mod.rs | 69 +++++++++++ .../primitives/src/transcripts/replay/mod.rs | 90 ++++++++++++++ 13 files changed, 350 insertions(+), 196 deletions(-) create mode 100644 crates/primitives/src/transcripts/recording/mod.rs create mode 100644 crates/primitives/src/transcripts/replay/mod.rs diff --git a/crates/fs/src/definitions/algorithms.rs b/crates/fs/src/definitions/algorithms.rs index a4238a038..d5f9c37a4 100644 --- a/crates/fs/src/definitions/algorithms.rs +++ b/crates/fs/src/definitions/algorithms.rs @@ -56,7 +56,7 @@ pub trait FoldingSchemeProver: FoldingSchemeDef ws: &[impl Borrow; N], us: &[impl Borrow; N], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof, Self::Challenge), Error>; + ) -> Result<(Self::RW, Self::RU, Self::Proof), Error>; } /// [`FoldingSchemeVerifier`] is the trait for folding scheme verifier. diff --git a/crates/fs/src/definitions/circuits.rs b/crates/fs/src/definitions/circuits.rs index 68d1be5f5..63aac1e13 100644 --- a/crates/fs/src/definitions/circuits.rs +++ b/crates/fs/src/definitions/circuits.rs @@ -32,7 +32,7 @@ pub trait FoldingSchemePartialVerifierGadget: Us: [&Self::RU; M], us: [&Self::IU; N], proof: &Self::Proof, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError>; + ) -> Result; } /// [`FoldingSchemeFullVerifierGadget`] is the full in-circuit verifier. diff --git a/crates/fs/src/lib.rs b/crates/fs/src/lib.rs index cf528aa75..2ee180acd 100644 --- a/crates/fs/src/lib.rs +++ b/crates/fs/src/lib.rs @@ -94,8 +94,8 @@ mod tests { let config = Arc::new(GriffinParams::new(16, 5, 9)); - let mut transcript_p = GriffinSponge::new(&config); - let mut transcript_v = GriffinSponge::new(&config); + let mut transcript_p = GriffinSponge::new(config.clone()); + let mut transcript_v = GriffinSponge::new(config); for assignments in assignments_vec { let mut ws = vec![]; @@ -113,7 +113,7 @@ mod tests { let ws = ws.try_into().unwrap(); let us = us.try_into().unwrap(); - let (WW, UU, pi, _) = FS::prove(pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; + let (WW, UU, pi) = FS::prove(pk, &mut transcript_p, &Ws, &Us, &ws, &us, &mut rng)?; FS::decide_running(&dk, &WW, &UU)?; assert_eq!(FS::verify(vk, &mut transcript_v, &Us, &us, &pi)?, UU); diff --git a/crates/fs/src/nova/algorithms/prover.rs b/crates/fs/src/nova/algorithms/prover.rs index eda3416ca..c5e4daca9 100644 --- a/crates/fs/src/nova/algorithms/prover.rs +++ b/crates/fs/src/nova/algorithms/prover.rs @@ -61,7 +61,7 @@ impl FoldingSchemePro ws: &[impl Borrow; 1], us: &[impl Borrow; 1], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>, Self::Challenge), Error> { + ) -> Result<(Self::RW, Self::RU, Self::Proof<1, 1>), Error> { let (W, U) = (Ws[0].borrow(), Us[0].borrow()); let (w, u) = (ws[0].borrow(), us[0].borrow()); @@ -94,7 +94,7 @@ impl FoldingSchemePro .map(|(a, b)| rho * b + a) .collect(), }; - Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) + Ok((WW, UU, cm_t)) } } @@ -110,7 +110,7 @@ impl FoldingSchemePro _: &[impl Borrow; 0], _: &[impl Borrow; 0], rng: impl RngCore, - ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>, Self::Challenge), Error> { + ) -> Result<(Self::RW, Self::RU, Self::Proof<2, 0>), Error> { let (W1, U1) = (W1.borrow(), U1.borrow()); let (W2, U2) = (W2.borrow(), U2.borrow()); @@ -146,6 +146,6 @@ impl FoldingSchemePro .map(|(a, b)| rho * b + a) .collect(), }; - Ok((WW, UU, cm_t, rho_bits.try_into().unwrap())) + Ok((WW, UU, cm_t)) } } diff --git a/crates/fs/src/nova/circuits/verifier.rs b/crates/fs/src/nova/circuits/verifier.rs index 54127a51c..d60dc6a81 100644 --- a/crates/fs/src/nova/circuits/verifier.rs +++ b/crates/fs/src/nova/circuits/verifier.rs @@ -23,7 +23,7 @@ where [U]: [&Self::RU; 1], [u]: [&Self::IU; 1], proof: &Self::Proof<1, 1>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + ) -> Result { let rho_bits = transcript.add(&U)?.add(&u)?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; @@ -31,34 +31,25 @@ where return Err(SynthesisError::Unsatisfiable); } - Ok(( - Self::RU { - u: (U.u.clone() + &rho) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm_e: CM::CommitmentVar::new_witness( - U.cm_e.cs().or(proof.cs()).or(rho.cs()), - || { - Ok(U.cm_e.value().unwrap_or_default() - + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - cm_w: CM::CommitmentVar::new_witness( - U.cm_w.cs().or(u.cm_w.cs()).or(rho.cs()), - || { - Ok(U.cm_w.value().unwrap_or_default() - + u.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - x: U.x - .iter() - .zip(&u.x) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }, - rho_bits.try_into().unwrap(), - )) + Ok(Self::RU { + u: (U.u.clone() + &rho) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness(U.cm_e.cs().or(proof.cs()).or(rho.cs()), || { + Ok(U.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho.value().unwrap_or_default()) + })?, + cm_w: CM::CommitmentVar::new_witness(U.cm_w.cs().or(u.cm_w.cs()).or(rho.cs()), || { + Ok(U.cm_w.value().unwrap_or_default() + + u.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + })?, + x: U.x + .iter() + .zip(&u.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) } } @@ -73,7 +64,7 @@ where [U1, U2]: [&Self::RU; 2], _: [&Self::IU; 0], proof: &Self::Proof<2, 0>, - ) -> Result<(Self::RU, Self::Challenge), SynthesisError> { + ) -> Result { let rho_bits = transcript.add(&(U1, U2))?.add(proof)?.challenge_bits(B)?; let rho = CM::ScalarVar::from_bits_le(&rho_bits)?; @@ -81,37 +72,34 @@ where return Err(SynthesisError::Unsatisfiable); } - Ok(( - Self::RU { - u: (U2.u.clone() * &rho + &U1.u) - .try_into() - .map_err(|_| SynthesisError::Unsatisfiable)?, - cm_e: CM::CommitmentVar::new_witness( - U1.cm_e.cs().or(U2.cm_e.cs()).or(proof.cs()).or(rho.cs()), - || { - let rho = rho.value().unwrap_or_default(); - Ok(U1.cm_e.value().unwrap_or_default() - + proof.value().unwrap_or_default() * rho - + U2.cm_e.value().unwrap_or_default() * rho * rho) - }, - )?, - cm_w: CM::CommitmentVar::new_witness( - U1.cm_w.cs().or(U2.cm_w.cs()).or(rho.cs()), - || { - Ok(U1.cm_w.value().unwrap_or_default() - + U2.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) - }, - )?, - x: U1 - .x - .iter() - .zip(&U2.x) - .map(|(a, b)| (b.clone() * &rho + a).try_into()) - .collect::>() - .map_err(|_| SynthesisError::Unsatisfiable)?, - }, - rho_bits.try_into().unwrap(), - )) + Ok(Self::RU { + u: (U2.u.clone() * &rho + &U1.u) + .try_into() + .map_err(|_| SynthesisError::Unsatisfiable)?, + cm_e: CM::CommitmentVar::new_witness( + U1.cm_e.cs().or(U2.cm_e.cs()).or(proof.cs()).or(rho.cs()), + || { + let rho = rho.value().unwrap_or_default(); + Ok(U1.cm_e.value().unwrap_or_default() + + proof.value().unwrap_or_default() * rho + + U2.cm_e.value().unwrap_or_default() * rho * rho) + }, + )?, + cm_w: CM::CommitmentVar::new_witness( + U1.cm_w.cs().or(U2.cm_w.cs()).or(rho.cs()), + || { + Ok(U1.cm_w.value().unwrap_or_default() + + U2.cm_w.value().unwrap_or_default() * rho.value().unwrap_or_default()) + }, + )?, + x: U1 + .x + .iter() + .zip(&U2.x) + .map(|(a, b)| (b.clone() * &rho + a).try_into()) + .collect::>() + .map_err(|_| SynthesisError::Unsatisfiable)?, + }) } } diff --git a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs index 83b42f932..75525325f 100644 --- a/crates/ivc/src/compilers/cyclefold/adapters/nova.rs +++ b/crates/ivc/src/compilers/cyclefold/adapters/nova.rs @@ -18,7 +18,11 @@ use sonobe_primitives::{ }, circuits::WitnessToPublic, commitments::GroupBasedCommitment, - traits::{CF2, SonobeCurve}, + traits::{CF1, CF2, SonobeCurve}, + transcripts::{ + Transcript, TranscriptGadget, + replay::{ReplayTranscript, ReplayTranscriptVar}, + }, }; use crate::compilers::cyclefold::{ @@ -68,15 +72,16 @@ impl FoldingSchemeCycleFo [U]: &[impl Borrow; 1], [u]: &[impl Borrow; 1], proof: &Self::Proof<1, 1>, - rho: Self::Challenge, + mut transcript: ReplayTranscript>, ) -> Vec { + let rho = transcript.challenge_bits(CHALLENGE_BITS); vec![ NovaCycleFoldCircuit { - r: rho.into(), + r: rho.clone(), points: vec![U.borrow().cm_e, *proof], }, NovaCycleFoldCircuit { - r: rho.into(), + r: rho, points: vec![U.borrow().cm_w, u.borrow().cm_w], }, ] @@ -88,9 +93,9 @@ impl FoldingSchemeCycleFo [u]: [::IU; 1], UU: ::RU, proof: ::Proof<1, 1>, - rho: ::Challenge, + mut transcript: ReplayTranscriptVar>, ) -> Result>>>, SynthesisError> { - let mut rho = rho.to_vec(); + let mut rho = transcript.challenge_bits(CHALLENGE_BITS)?; rho.resize( CF2::::MODULUS_BIT_SIZE as usize, Boolean::FALSE, @@ -130,20 +135,21 @@ impl FoldingSchemeCycleFo [U1, U2]: &[impl Borrow; 2], _: &[impl Borrow; 0], proof: &Self::Proof<2, 0>, - rho_bits: Self::Challenge, + mut transcript: ReplayTranscript>, ) -> Vec { + let rho_bits = transcript.challenge_bits(CHALLENGE_BITS); let rho = CM::Scalar::from_bits_le(&rho_bits); vec![ NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits.clone(), points: vec![*proof, U2.borrow().cm_e], }, NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits.clone(), points: vec![U1.borrow().cm_e, U2.borrow().cm_e * rho + proof], }, NovaCycleFoldCircuit { - r: rho_bits.into(), + r: rho_bits, points: vec![U1.borrow().cm_w, U2.borrow().cm_w], }, ] @@ -155,9 +161,9 @@ impl FoldingSchemeCycleFo _: [::IU; 0], UU: ::RU, proof: ::Proof<2, 0>, - rho_bits: ::Challenge, + mut transcript: ReplayTranscriptVar>, ) -> Result>>>, SynthesisError> { - let mut rho_bits = rho_bits.to_vec(); + let mut rho_bits = transcript.challenge_bits(CHALLENGE_BITS)?; rho_bits.resize( CF2::::MODULUS_BIT_SIZE as usize, Boolean::FALSE, diff --git a/crates/ivc/src/compilers/cyclefold/circuits.rs b/crates/ivc/src/compilers/cyclefold/circuits.rs index 6667639aa..7f989fe0a 100644 --- a/crates/ivc/src/compilers/cyclefold/circuits.rs +++ b/crates/ivc/src/compilers/cyclefold/circuits.rs @@ -19,7 +19,7 @@ use sonobe_primitives::{ circuits::{FCircuit, WitnessToPublic}, commitments::CommitmentDef, traits::{Dummy, SonobeCurve}, - transcripts::{Transcript, TranscriptGadget}, + transcripts::{TranscriptGadget, recording::RecordingTranscriptVar}, }; use crate::compilers::cyclefold::FoldingSchemeCycleFoldExt; @@ -31,7 +31,7 @@ pub struct AugmentedCircuit< FS1: GroupBasedFoldingSchemePrimary<1, 1>, FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, - T: Transcript, + T: TranscriptGadget, > { _fs: PhantomData<(FS1, FS2)>, hash_config: &'a T::Config, @@ -45,7 +45,7 @@ where FS1: GroupBasedFoldingSchemePrimary<1, 1>, FS2: GroupBasedFoldingSchemeSecondary<1, 1>, FC: FCircuit, - T: Transcript, + T: TranscriptGadget, { /// [`AugmentedCircuit::new`] creates an instance of the augmented circuit /// for the given step circuit. @@ -84,7 +84,7 @@ where >, >, FC: FCircuit::Scalar>, - T: Transcript, + T: TranscriptGadget, { /// [`AugmentedCircuit::compute_next_state`] invokes the step circuit on the /// current state and external inputs to compute the next state and external @@ -105,12 +105,13 @@ where cf_us: Vec, cf_proofs: Vec>, ) -> Result<(FC::State, FC::ExternalOutputs), SynthesisError> { - let hash = T::Gadget::new_with_pp_hash( - self.hash_config, + let hash = T::new_with_pp_hash( + self.hash_config.clone(), &FpVar::new_witness(cs.clone(), || Ok(pp_hash))?, )?; let sponge = hash.separate_domain("sponge".as_ref())?; - let mut transcript = hash.separate_domain("transcript".as_ref())?; + let mut transcript = + RecordingTranscriptVar::new(hash.separate_domain("transcript".as_ref())?); let i = FpVar::new_witness(cs.clone(), || Ok(FC::Field::from(i as u64)))?; let ii = &i + FpVar::one(); @@ -152,7 +153,7 @@ where // 1.c. Fold the primary running instance `U` and incoming instance `u` // using the provided proof to obtain the next running instance // `UU`. - let (UU, rho) = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; + let UU = FS1::Gadget::verify_hinted(&(), &mut transcript, [&U], [&u], &proof)?; // 1.d. If this is the base case (`i = 0`), then we should instead use // the dummy running instance as the next running instance. let actual_UU = is_basecase.select(&U_dummy, &UU)?; @@ -161,7 +162,7 @@ where // 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)?; + let cf_u_xs = FS1::to_cyclefold_inputs([U], [u], UU, proof, transcript.clone().into())?; if [cf_us.len(), cf_u_xs.len(), cf_proofs.len()] != [FS1::N_CYCLEFOLDS; 3] { return Err(SynthesisError::Unsatisfiable); } @@ -224,7 +225,7 @@ where >, >, FC: FCircuit::Scalar>, - T: Transcript, + T: TranscriptGadget, { fn generate_constraints( self, diff --git a/crates/ivc/src/compilers/cyclefold/mod.rs b/crates/ivc/src/compilers/cyclefold/mod.rs index 184153926..2cb4b12e6 100644 --- a/crates/ivc/src/compilers/cyclefold/mod.rs +++ b/crates/ivc/src/compilers/cyclefold/mod.rs @@ -32,7 +32,11 @@ use sonobe_primitives::{ commitments::CommitmentDef, relations::WitnessInstanceSampler, traits::{CF1, CF2, Dummy, SonobeCurve}, - transcripts::Transcript, + transcripts::{ + Transcript, TranscriptGadget, + recording::RecordingTranscript, + replay::{ReplayTranscript, ReplayTranscriptVar}, + }, }; use crate::{ @@ -63,7 +67,7 @@ pub trait FoldingSchemeCycleFoldExt: Us: &[impl Borrow; M], us: &[impl Borrow; N], proof: &Self::Proof, - rho: Self::Challenge, + transcript: ReplayTranscript::Commitment>>, ) -> Vec; /// [`FoldingSchemeCycleFoldExt::to_cyclefold_inputs`] computes the inputs @@ -76,7 +80,7 @@ pub trait FoldingSchemeCycleFoldExt: us: [::IU; N], UU: ::RU, proof: ::Proof, - rho: ::Challenge, + transcript: ReplayTranscriptVar::Commitment>>, ) -> Result< Vec< Vec< @@ -160,7 +164,8 @@ where Commitment: SonobeCurve::Scalar>, >, >, - T: Transcript::Commitment>>, + T: Transcript::Commitment>, Config: CanonicalSerialize>, + T::Gadget: TranscriptGadget::Commitment>, Config = T::Config>, { type Field = ::Scalar; @@ -215,7 +220,7 @@ where loop { let new_arith1 = { let cs = ArithExtractor::new(); - cs.execute_synthesizer(AugmentedCircuit::::new( + cs.execute_synthesizer(AugmentedCircuit::::new( &hash_config, &arith1_config, arith2_config, @@ -274,8 +279,8 @@ where Proof(W, U, w, u, cf_W, cf_U): &Self::Proof, mut rng: impl RngCore, ) -> Result<(FC::State, FC::ExternalOutputs, Self::Proof), Error> { - let hash = T::new_with_pp_hash(hash_config, *pp_hash); - let mut transcript = hash.separate_domain("transcript".as_ref()); + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); + let mut transcript = RecordingTranscript::new(hash.separate_domain("transcript".as_ref())); let arith1_config = &dk1.to_arith_config(); let arith2_config = &dk2.to_arith_config(); @@ -287,8 +292,7 @@ where let (mut cf_UU, mut cf_WW) = (Dummy::dummy(arith2_config), Dummy::dummy(arith2_config)); if i != 0 { - let challenge; - (WW, UU, proof, challenge) = FS1::prove( + (WW, UU, proof) = FS1::prove( dk1.to_pk(), &mut transcript, &[W], @@ -298,14 +302,15 @@ where &mut rng, )?; - let cf_circuits = FS1::to_cyclefold_circuits(&[U], &[u], &proof, challenge); + let cf_circuits = + FS1::to_cyclefold_circuits(&[U], &[u], &proof, transcript.clone().into()); for (i, cf_circuit) in cf_circuits.into_iter().enumerate() { let cs = AssignmentsExtractor::new(); cs.execute_fn(|cs| cf_circuit.verify_point_rlc(cs))?; let (cf_w, cf_u) = dk2.sample(cs.assignments()?, &mut rng)?; - (cf_WW, cf_UU, cf_proofs[i], _) = FS2::prove( + (cf_WW, cf_UU, cf_proofs[i]) = FS2::prove( dk2.to_pk(), &mut transcript, &[if i == 0 { cf_W } else { &cf_WW }], @@ -320,7 +325,7 @@ where let cs = AssignmentsExtractor::new(); let (next_state, external_outputs) = cs.execute_fn(|cs| { - let augmented_circuit = AugmentedCircuit::::new( + let augmented_circuit = AugmentedCircuit::::new( hash_config, arith1_config, arith2_config, @@ -369,7 +374,7 @@ where return Err(Error::IVCVerificationFail); } - let hash = T::new_with_pp_hash(hash_config, *pp_hash); + let hash = T::new_with_pp_hash(hash_config.clone(), *pp_hash); let mut sponge = hash.separate_domain("sponge".as_ref()); let u_x = sponge diff --git a/crates/primitives/src/transcripts/griffin/sponge.rs b/crates/primitives/src/transcripts/griffin/sponge.rs index 70cfa71ee..e644f6cd2 100644 --- a/crates/primitives/src/transcripts/griffin/sponge.rs +++ b/crates/primitives/src/transcripts/griffin/sponge.rs @@ -1,11 +1,8 @@ //! Implementation of transcript traits for Griffin sponge. use ark_crypto_primitives::sponge::DuplexSpongeMode; -use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{ - fields::{FieldVar, fp::FpVar}, - prelude::{Boolean, ToBitsGadget}, -}; +use ark_ff::PrimeField; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::SynthesisError; use ark_std::sync::Arc; @@ -187,7 +184,7 @@ impl Transcript for GriffinSponge { type Config = Arc>; type Gadget = GriffinSpongeVar; - fn new(parameters: &Arc>) -> Self { + fn new(parameters: Arc>) -> Self { let state = vec![F::zero(); parameters.rate + parameters.capacity]; let mode = DuplexSpongeMode::Absorbing { next_absorb_index: 0, @@ -223,22 +220,6 @@ impl Transcript for GriffinSponge { self } - fn get_bits(&mut self, num_bits: usize) -> Vec { - let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; - - let num_elements = num_bits.div_ceil(usable_bits); - let src_elements = self.get_field_elements(num_elements); - - let mut bits: Vec = Vec::with_capacity(usable_bits * num_elements); - for elem in &src_elements { - let elem_bits = elem.into_bigint().to_bits_le(); - bits.extend_from_slice(&elem_bits[..usable_bits]); - } - - bits.truncate(num_bits); - bits - } - fn get_field_elements(&mut self, num_elements: usize) -> Vec { let mut squeezed_elems = vec![F::zero(); num_elements]; match self.mode { @@ -263,9 +244,10 @@ impl Transcript for GriffinSponge { } impl TranscriptGadget for GriffinSpongeVar { + type Config = Arc>; type Widget = GriffinSponge; - fn new(parameters: &Arc>) -> Self + fn new(parameters: Arc>) -> Self where Self: Sized, { @@ -282,10 +264,7 @@ impl TranscriptGadget for GriffinSpongeVar { } } - fn add + ?Sized>( - &mut self, - input: &A, - ) -> Result<&mut Self, SynthesisError> { + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { let input = { let mut result = Vec::new(); input.absorb_into(&mut result)?; @@ -315,21 +294,6 @@ impl TranscriptGadget for GriffinSpongeVar { Ok(self) } - fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { - let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; - - let num_elements = num_bits.div_ceil(usable_bits); - let src_elements = self.get_field_elements(num_elements)?; - - let mut bits: Vec> = Vec::with_capacity(usable_bits * num_elements); - for elem in &src_elements { - bits.extend_from_slice(&elem.to_bits_le()?[..usable_bits]); - } - - bits.truncate(num_bits); - Ok(bits) - } - fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { let zero = FpVar::zero(); let mut squeezed_elems = vec![zero; num_elements]; @@ -374,13 +338,13 @@ mod tests { fn test_challenge_field_element() -> Result<(), Box> { // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + let mut tr = GriffinSponge::::new(config.clone()); tr.add(&Fr::from(42_u32)); let c = tr.challenge_field_element(); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + let mut tr_var = GriffinSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_field_element()?; @@ -397,13 +361,13 @@ mod tests { // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + let mut tr = GriffinSponge::::new(config.clone()); tr.add(&Fq::from(42_u32)); let c = tr.challenge_bits(nbits); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + let mut tr_var = GriffinSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_bits(nbits)?; @@ -418,7 +382,7 @@ mod tests { fn test_absorb_canonical_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + let mut tr = GriffinSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -427,7 +391,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + let mut tr_var = GriffinSpongeVar::::new(config); let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; @@ -442,7 +406,7 @@ mod tests { fn test_absorb_emulated_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = Arc::new(GriffinParams::::new(3, 5, 12)); - let mut tr = GriffinSponge::::new(&config); + let mut tr = GriffinSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -451,7 +415,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = GriffinSpongeVar::::new(&config); + let mut tr_var = GriffinSpongeVar::::new(config); let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; diff --git a/crates/primitives/src/transcripts/mod.rs b/crates/primitives/src/transcripts/mod.rs index 10e94db7a..e2e55b24d 100644 --- a/crates/primitives/src/transcripts/mod.rs +++ b/crates/primitives/src/transcripts/mod.rs @@ -8,15 +8,16 @@ //! sub-modules. use ark_ff::{BigInteger, PrimeField}; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_r1cs_std::{boolean::Boolean, convert::ToBitsGadget, fields::fp::FpVar}; use ark_relations::gr1cs::SynthesisError; -use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; pub use self::absorbable::{Absorbable, AbsorbableVar}; pub mod absorbable; pub mod griffin; pub mod poseidon; +pub mod recording; +pub mod replay; /// [`Transcript`] is the out-of-circuit widget for transcripts and sponges. /// @@ -25,7 +26,7 @@ pub mod poseidon; pub trait Transcript: Clone { /// [`Transcript::Config`] is the configuration for the underlying hash /// function of the transcript. - type Config: Clone + CanonicalSerialize + CanonicalDeserialize; + type Config: Clone; /// [`Transcript::Gadget`] is the in-circuit gadget corresponding to this /// widget. @@ -33,12 +34,12 @@ pub trait Transcript: Clone { /// [`Transcript::new`] creates a new transcript / sponge under the given /// configuration `config`. - fn new(config: &Self::Config) -> Self; + fn new(config: Self::Config) -> Self; /// [`Transcript::new_with_pp_hash`] is a convenience method for creating a /// new transcript / sponge under the given configuration `config` and /// additionally absorbing a hash of the public parameters `pp_hash`. - fn new_with_pp_hash(config: &Self::Config, pp_hash: F) -> Self { + fn new_with_pp_hash(config: Self::Config, pp_hash: F) -> Self { let mut sponge = Self::new(config); sponge.add_field_elements(&[pp_hash]); sponge @@ -59,7 +60,21 @@ pub trait Transcript: Clone { /// [`Transcript::get_bits`] squeezes `num_bits` bits from the transcript / /// sponge. - fn get_bits(&mut self, num_bits: usize) -> Vec; + fn get_bits(&mut self, num_bits: usize) -> Vec { + let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; + + let num_elements = num_bits.div_ceil(usable_bits); + let src_elements = self.get_field_elements(num_elements); + + let mut bits: Vec = Vec::with_capacity(usable_bits * num_elements); + for elem in &src_elements { + let elem_bits = elem.into_bigint().to_bits_le(); + bits.extend_from_slice(&elem_bits[..usable_bits]); + } + + bits.truncate(num_bits); + bits + } /// [`Transcript::get_field_element`] squeezes a single field element from /// the transcript / sponge. @@ -132,22 +147,23 @@ pub trait Transcript: Clone { /// [`TranscriptGadget`] is the in-circuit gadget for transcripts and sponges. pub trait TranscriptGadget: Clone { + /// [`TranscriptGadget::Config`] is the configuration for the underlying + /// hash function of the transcript gadget. + type Config: Clone; + /// [`TranscriptGadget::Widget`] points to the out-of-circuit widget for /// this transcript gadget. type Widget: Transcript; /// [`TranscriptGadget::new`] creates a new transcript / sponge variable /// under the given configuration `config`. - fn new(config: &>::Config) -> Self; + fn new(config: Self::Config) -> Self; /// [`TranscriptGadget::new_with_pp_hash`] is a convenience method for /// creating a new transcript / sponge variable under the given /// configuration `config` and additionally absorbing a hash of the public /// parameters `pp_hash`. - fn new_with_pp_hash( - config: &>::Config, - pp_hash: &FpVar, - ) -> Result { + fn new_with_pp_hash(config: Self::Config, pp_hash: &FpVar) -> Result { let mut sponge = Self::new(config); sponge.add(&pp_hash)?; Ok(sponge) @@ -156,12 +172,24 @@ pub trait TranscriptGadget: Clone { /// [`TranscriptGadget::add`] absorbs a message `input` that can be any type /// implementing the [`AbsorbableGadget`] trait into the transcript / sponge /// variable. - fn add + ?Sized>(&mut self, input: &A) - -> Result<&mut Self, SynthesisError>; + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError>; /// [`TranscriptGadget::get_bits`] squeezes `num_bits` bit variables from /// the transcript / sponge variable. - fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError>; + fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { + let usable_bits = (F::MODULUS_BIT_SIZE - 1) as usize; + + let num_elements = num_bits.div_ceil(usable_bits); + let src_elements = self.get_field_elements(num_elements)?; + + let mut bits: Vec> = Vec::with_capacity(usable_bits * num_elements); + for elem in &src_elements { + bits.extend_from_slice(&elem.to_bits_le()?[..usable_bits]); + } + + bits.truncate(num_bits); + Ok(bits) + } /// [`TranscriptGadget::get_field_element`] squeezes a single field element /// variable from the transcript / sponge variable. diff --git a/crates/primitives/src/transcripts/poseidon/sponge.rs b/crates/primitives/src/transcripts/poseidon/sponge.rs index 49cd82250..eb827a19f 100644 --- a/crates/primitives/src/transcripts/poseidon/sponge.rs +++ b/crates/primitives/src/transcripts/poseidon/sponge.rs @@ -1,12 +1,12 @@ //! Implementation of transcript traits for arkworks' Poseidon sponge. use ark_crypto_primitives::sponge::{ - Absorb, CryptographicSponge, FieldBasedCryptographicSponge, + Absorb, CryptographicSponge, DuplexSpongeMode, FieldBasedCryptographicSponge, constraints::CryptographicSpongeVar, poseidon::{PoseidonConfig, PoseidonSponge, constraints::PoseidonSpongeVar}, }; use ark_ff::PrimeField; -use ark_r1cs_std::{boolean::Boolean, fields::fp::FpVar}; +use ark_r1cs_std::fields::{FieldVar, fp::FpVar}; use ark_relations::gr1cs::{ConstraintSystemRef, SynthesisError}; use ark_std::mem::transmute_copy; @@ -16,8 +16,14 @@ impl Transcript for PoseidonSponge { type Config = PoseidonConfig; type Gadget = PoseidonSpongeVar; - fn new(config: &Self::Config) -> Self { - CryptographicSponge::new(config) + fn new(config: Self::Config) -> Self { + Self { + state: vec![F::zero(); config.rate + config.capacity], + parameters: config, + mode: DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }, + } } fn add_field_elements(&mut self, input: &[F]) -> &mut Self { @@ -40,29 +46,30 @@ impl Transcript for PoseidonSponge { self } - fn get_bits(&mut self, num_bits: usize) -> Vec { - CryptographicSponge::squeeze_bits(self, num_bits) - } - fn get_field_elements(&mut self, num_elements: usize) -> Vec { self.squeeze_native_field_elements(num_elements) } } impl TranscriptGadget for PoseidonSpongeVar { + type Config = PoseidonConfig; type Widget = PoseidonSponge; - fn new(config: &PoseidonConfig) -> Self + fn new(config: PoseidonConfig) -> Self where Self: Sized, { - CryptographicSpongeVar::new(ConstraintSystemRef::None, config) + Self { + cs: ConstraintSystemRef::None, + state: vec![FpVar::::zero(); config.rate + config.capacity], + parameters: config, + mode: DuplexSpongeMode::Absorbing { + next_absorb_index: 0, + }, + } } - fn add + ?Sized>( - &mut self, - input: &A, - ) -> Result<&mut Self, SynthesisError> { + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { let mut result = Vec::new(); input.absorb_into(&mut result)?; @@ -70,10 +77,6 @@ impl TranscriptGadget for PoseidonSpongeVar { Ok(self) } - fn get_bits(&mut self, num_bits: usize) -> Result>, SynthesisError> { - self.squeeze_bits(num_bits) - } - fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { self.squeeze_field_elements(num_elements) } @@ -102,7 +105,7 @@ mod tests { #[test] fn check_against_circom_poseidon() -> Result<(), Box> { let config = poseidon_circom_config::(); - let mut poseidon_sponge = PoseidonSponge::new(&config); + let mut poseidon_sponge = PoseidonSponge::new(config); let v = vec![1, 2, 3, 4] .into_iter() .map(Fr::from) @@ -123,13 +126,13 @@ mod tests { fn test_challenge_field_element() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); tr.add(&Fr::from(42_u32)); let c = tr.challenge_field_element(); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + let mut tr_var = PoseidonSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fr::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_field_element()?; @@ -146,13 +149,13 @@ mod tests { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); tr.add(&Fq::from(42_u32)); let c = tr.challenge_bits(nbits); // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + let mut tr_var = PoseidonSpongeVar::::new(config); let v = FpVar::::new_witness(cs.clone(), || Ok(Fq::from(42_u32)))?; tr_var.add(&v)?; let c_var = tr_var.challenge_bits(nbits)?; @@ -167,7 +170,7 @@ mod tests { fn test_absorb_canonical_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -176,7 +179,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + let mut tr_var = PoseidonSpongeVar::::new(config); let p_var = ProjectiveVar::>::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; @@ -191,7 +194,7 @@ mod tests { fn test_absorb_emulated_point() -> Result<(), Box> { // Create a transcript outside of the circuit let config = poseidon_circom_config::(); - let mut tr = PoseidonSponge::::new(&config); + let mut tr = PoseidonSponge::::new(config.clone()); let rng = &mut thread_rng(); let p = G1::rand(rng); @@ -200,7 +203,7 @@ mod tests { // Create a transcript inside of the circuit let cs = ConstraintSystem::::new_ref(); - let mut tr_var = PoseidonSpongeVar::::new(&config); + let mut tr_var = PoseidonSpongeVar::::new(config); let p_var = EmulatedAffineVar::new_witness(cs, || Ok(p))?; tr_var.add(&p_var)?; let c_var = tr_var.challenge_field_element()?; diff --git a/crates/primitives/src/transcripts/recording/mod.rs b/crates/primitives/src/transcripts/recording/mod.rs new file mode 100644 index 000000000..d91e5d9a1 --- /dev/null +++ b/crates/primitives/src/transcripts/recording/mod.rs @@ -0,0 +1,69 @@ +//! Implementation of transcripts that can automatically record generated +//! challenges, eliminating the need to pass challenges thoughout protocols. + +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; + +use super::{AbsorbableVar, Transcript, TranscriptGadget}; + +/// [`RecordingTranscript`] wraps a regular transcript to record all challenges +/// it produces. +#[derive(Clone)] +pub struct RecordingTranscript> { + inner: T, + pub(super) cached_challenges: Vec, +} + +impl> Transcript for RecordingTranscript { + type Config = T; + type Gadget = RecordingTranscriptVar; + + fn new(inner: Self::Config) -> Self { + Self { + inner, + cached_challenges: vec![], + } + } + + fn add_field_elements(&mut self, input: &[F]) -> &mut Self { + self.inner.add_field_elements(input); + self + } + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + let v = self.inner.get_field_elements(num_elements); + self.cached_challenges.extend_from_slice(&v); + v + } +} + +/// [`RecordingTranscriptVar`] is the in-circuit variable of [`RecordingTranscript`]. +#[derive(Clone)] +pub struct RecordingTranscriptVar> { + inner: T, + pub(super) cached_challenges: Vec>, +} + +impl> TranscriptGadget for RecordingTranscriptVar { + type Config = T; + type Widget = RecordingTranscript; + + fn new(inner: Self::Config) -> Self { + Self { + inner, + cached_challenges: vec![], + } + } + + fn add>(&mut self, input: &A) -> Result<&mut Self, SynthesisError> { + self.inner.add(input)?; + Ok(self) + } + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + let v = self.inner.get_field_elements(num_elements)?; + self.cached_challenges.extend_from_slice(&v); + Ok(v) + } +} diff --git a/crates/primitives/src/transcripts/replay/mod.rs b/crates/primitives/src/transcripts/replay/mod.rs new file mode 100644 index 000000000..9c4e8197e --- /dev/null +++ b/crates/primitives/src/transcripts/replay/mod.rs @@ -0,0 +1,90 @@ +//! Implementation of transcripts that always produces designated challenge +//! values. + +use ark_ff::PrimeField; +use ark_r1cs_std::fields::fp::FpVar; +use ark_relations::gr1cs::SynthesisError; + +use super::{AbsorbableVar, Transcript, TranscriptGadget}; +use crate::transcripts::recording::{RecordingTranscript, RecordingTranscriptVar}; + +/// [`ReplayTranscript`] is a convenience struct that generates specific values +/// as challenges without running the actual hash function. +/// +/// WARNING: This struct itself is insecure. The caller is responsible for +/// checking the validity of the designated challenge values. +#[derive(Clone)] +pub struct ReplayTranscript { + cached_challenges: Vec, +} + +impl> From> for ReplayTranscript { + fn from(value: RecordingTranscript) -> Self { + Self { + cached_challenges: value.cached_challenges, + } + } +} + +impl Transcript for ReplayTranscript { + type Config = Vec; + type Gadget = ReplayTranscriptVar; + + fn new(mut cached_challenges: Self::Config) -> Self { + cached_challenges.reverse(); + Self { cached_challenges } + } + + fn add_field_elements(&mut self, _: &[F]) -> &mut Self { + self + } + + fn get_field_elements(&mut self, num_elements: usize) -> Vec { + let mut result = vec![]; + for _ in 0..num_elements { + result.push(self.cached_challenges.pop().unwrap()) + } + result + } +} + +/// [`ReplayTranscriptVar`] is the in-circuit variable of [`ReplayTranscript`]. +#[derive(Clone)] +pub struct ReplayTranscriptVar { + cached_challenges: Vec>, +} + +impl> From> + for ReplayTranscriptVar +{ + fn from(value: RecordingTranscriptVar) -> Self { + Self { + cached_challenges: value.cached_challenges, + } + } +} + +impl TranscriptGadget for ReplayTranscriptVar { + type Config = Vec>; + type Widget = ReplayTranscript; + + fn new(mut cached_challenges: Vec>) -> Self + where + Self: Sized, + { + cached_challenges.reverse(); + Self { cached_challenges } + } + + fn add + ?Sized>(&mut self, _: &A) -> Result<&mut Self, SynthesisError> { + Ok(self) + } + + fn get_field_elements(&mut self, num_elements: usize) -> Result>, SynthesisError> { + let mut result = vec![]; + for _ in 0..num_elements { + result.push(self.cached_challenges.pop().unwrap()) + } + Ok(result) + } +} From b93395536a713501911c9619e5b081977de15987 Mon Sep 17 00:00:00 2001 From: winderica Date: Tue, 26 May 2026 16:48:43 +0800 Subject: [PATCH 2/2] Fix typos --- crates/primitives/src/transcripts/recording/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/primitives/src/transcripts/recording/mod.rs b/crates/primitives/src/transcripts/recording/mod.rs index d91e5d9a1..bf2de8899 100644 --- a/crates/primitives/src/transcripts/recording/mod.rs +++ b/crates/primitives/src/transcripts/recording/mod.rs @@ -1,5 +1,5 @@ //! Implementation of transcripts that can automatically record generated -//! challenges, eliminating the need to pass challenges thoughout protocols. +//! challenges, eliminating the need to pass challenges throughout protocols. use ark_ff::PrimeField; use ark_r1cs_std::fields::fp::FpVar;