From 3e45ac54657da656f734085ad4fa80d7090045d4 Mon Sep 17 00:00:00 2001 From: Adrian Hamelink Date: Thu, 7 Mar 2024 20:25:59 +0100 Subject: [PATCH] Working merge --- src/parafold/circuit.rs | 91 +-- src/parafold/cycle_fold/circuit.rs | 28 +- src/parafold/cycle_fold/gadgets/ecc.rs | 13 +- src/parafold/cycle_fold/gadgets/emulated.rs | 21 +- .../gadgets/secondary_commitment.rs | 15 +- src/parafold/cycle_fold/nifs/circuit.rs | 161 ++--- src/parafold/cycle_fold/nifs/prover.rs | 147 ++--- src/parafold/cycle_fold/prover.rs | 30 +- src/parafold/mod.rs | 33 +- src/parafold/nifs/circuit.rs | 220 +++---- src/parafold/nifs/mod.rs | 12 +- src/parafold/nifs/prover.rs | 227 ++++--- src/parafold/nivc/circuit.rs | 623 ++++++++---------- src/parafold/nivc/mod.rs | 121 ++-- src/parafold/nivc/prover.rs | 371 +++++------ src/parafold/prover.rs | 282 +++++--- src/parafold/transcript/circuit.rs | 26 +- 17 files changed, 1121 insertions(+), 1300 deletions(-) diff --git a/src/parafold/circuit.rs b/src/parafold/circuit.rs index 9f211b983..abc3e2393 100644 --- a/src/parafold/circuit.rs +++ b/src/parafold/circuit.rs @@ -1,67 +1,68 @@ use bellpepper_core::{ConstraintSystem, SynthesisError}; use crate::parafold::nivc::circuit::AllocatedNIVCState; -use crate::parafold::nivc::{NIVCUpdateProof, NIVCIO}; +use crate::parafold::nivc::{NIVCCircuitInput, NIVCMergeProof, NIVCIO}; use crate::parafold::transcript::TranscriptConstants; use crate::supernova::StepCircuit; use crate::traits::CurveCycleEquipped; pub fn synthesize_step( mut cs: CS, - ro_consts: &TranscriptConstants, - proof: NIVCUpdateProof, + input: &NIVCCircuitInput, + ro_consts: TranscriptConstants, step_circuit: &SF, -) -> Result<(Option>, AllocatedNIVCState), SynthesisError> +) -> Result>, SynthesisError> where E: CurveCycleEquipped, CS: ConstraintSystem, SF: StepCircuit, { - // Fold proof for previous state - let (mut state, transcript_state) = - AllocatedNIVCState::from_proof(cs.namespace(|| "verify self"), ro_consts, proof)?; + let mut state = AllocatedNIVCState::init(cs.namespace(|| "alloc state"), ro_consts, input)?; let io = state.update_io(cs.namespace(|| "step"), step_circuit)?; state.inputize(cs.namespace(|| "inputize state"))?; - transcript_state.inputize(cs.namespace(|| "inputize transcript"))?; - Ok((io, state)) + Ok(io) } -// /// Circuit -// pub fn synthesize_merge( -// mut cs: CS, -// ro_consts: &TranscriptConstants, -// proof_L: NIVCUpdateProof, -// proof_R: NIVCUpdateProof, -// proof_merge: NIVCMergeProof, -// ) -> Result, SynthesisError> -// where -// E: CurveCycleEquipped, -// CS: ConstraintSystem, -// { -// // Verify L -// let (self_L, transcript_L) = -// AllocatedNIVCState::from_proof(cs.namespace(|| "verify proof_L"), ro_consts, proof_L)?; -// // Verify R -// let (self_R, transcript_R) = -// AllocatedNIVCState::from_proof(cs.namespace(|| "verify proof_R"), ro_consts, proof_R)?; -// // Merge transcripts -// let mut transcript = AllocatedTranscript::merge(transcript_L, transcript_R); -// -// // Merge states -// let (state, io_native) = AllocatedNIVCState::merge( -// cs.namespace(|| "merge"), -// self_L, -// self_R, -// ro_consts, -// proof_merge, -// &mut transcript, -// )?; -// -// transcript.inputize(cs.namespace(|| "inputize transcript"))?; -// state.inputize(cs.namespace(|| "inputize state"))?; -// -// Ok(io_native) -// } +///Circuit +#[allow(unused)] +pub fn synthesize_merge( + mut cs: CS, + input_L: &NIVCCircuitInput, + input_R: &NIVCCircuitInput, + merge_proof: NIVCMergeProof, + ro_consts: TranscriptConstants, +) -> Result>, SynthesisError> +where + E: CurveCycleEquipped, + CS: ConstraintSystem, +{ + // Verify L + let mut state_L = AllocatedNIVCState::init( + cs.namespace(|| "alloc state_L"), + ro_consts.clone(), + &input_L, + )?; + + // Verify L + let mut state_R = AllocatedNIVCState::init( + cs.namespace(|| "alloc state_R"), + ro_consts.clone(), + &input_R, + )?; + + // Merge states + let (state, io_native) = AllocatedNIVCState::merge( + cs.namespace(|| "merge"), + state_L, + state_R, + merge_proof, + ro_consts, + )?; + + state.inputize(cs.namespace(|| "inputize state"))?; + + Ok(io_native) +} diff --git a/src/parafold/cycle_fold/circuit.rs b/src/parafold/cycle_fold/circuit.rs index efb38c721..fd240d872 100644 --- a/src/parafold/cycle_fold/circuit.rs +++ b/src/parafold/cycle_fold/circuit.rs @@ -1,26 +1,28 @@ -use bellpepper_core::boolean::Boolean; use bellpepper_core::{ConstraintSystem, SynthesisError, Variable}; +use bellpepper_core::boolean::Boolean; use ff::PrimeField; +use crate::parafold::cycle_fold::AllocatedPrimaryCommitment; use crate::parafold::cycle_fold::gadgets::emulated::AllocatedBase; use crate::parafold::cycle_fold::nifs::circuit::AllocatedSecondaryRelaxedR1CSInstance; use crate::parafold::cycle_fold::nifs::NUM_IO_SECONDARY; -use crate::parafold::cycle_fold::AllocatedPrimaryCommitment; use crate::parafold::transcript::circuit::AllocatedTranscript; use crate::traits::CurveCycleEquipped; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct AllocatedScalarMulAccumulator { deferred: Vec>, - acc: AllocatedSecondaryRelaxedR1CSInstance, +} + +impl Drop for AllocatedScalarMulAccumulator { + fn drop(&mut self) { + assert!(self.deferred.is_empty(), "unproved scalar multiplications") + } } impl AllocatedScalarMulAccumulator { - pub fn new(acc: AllocatedSecondaryRelaxedR1CSInstance) -> Self { - Self { - deferred: vec![], - acc, - } + pub fn new() -> Self { + Self { deferred: vec![] } } /// Compute the result `C <- A + x * B` by folding a proof over the secondary curve. @@ -55,8 +57,9 @@ impl AllocatedScalarMulAccumulator { pub fn finalize( mut self, mut cs: CS, + acc_cf: &mut AllocatedSecondaryRelaxedR1CSInstance, transcript: &mut AllocatedTranscript, - ) -> Result, SynthesisError> + ) -> Result<(), SynthesisError> where CS: ConstraintSystem, { @@ -65,14 +68,13 @@ impl AllocatedScalarMulAccumulator { // TODO: In order to avoid computing unnecessary proofs, we can check // - x = 0 => C = A - self.acc.fold( + acc_cf.fold( cs.namespace(|| format!("fold cf instance {i}")), X, transcript, )?; } - - Ok(self.acc) + Ok(()) } } diff --git a/src/parafold/cycle_fold/gadgets/ecc.rs b/src/parafold/cycle_fold/gadgets/ecc.rs index c832d2df9..eabf1f9eb 100644 --- a/src/parafold/cycle_fold/gadgets/ecc.rs +++ b/src/parafold/cycle_fold/gadgets/ecc.rs @@ -26,26 +26,25 @@ pub struct AllocatedPoint { } impl AllocatedPoint { - pub fn select_default(self, mut cs: CS, is_default: &Boolean) -> Result + pub fn select_default(&self, mut cs: CS, is_default: &Boolean) -> Result where CS: ConstraintSystem, { let zero = alloc_zero(cs.namespace(|| "alloc 0")); let one = alloc_one(cs.namespace(|| "alloc 1")); - let Self { - x, y, is_infinity, .. - } = self; - let x = conditionally_select(cs.namespace(|| "select x"), &zero, &x, is_default)?; - let y = conditionally_select(cs.namespace(|| "select y"), &zero, &y, is_default)?; + + let x = conditionally_select(cs.namespace(|| "select x"), &zero, &self.x, is_default)?; + let y = conditionally_select(cs.namespace(|| "select y"), &zero, &self.y, is_default)?; let is_infinity = conditionally_select( cs.namespace(|| "select is_infinity"), &one, - &is_infinity, + &self.is_infinity, is_default, )?; Ok(Self { x, y, is_infinity }) } + #[allow(unused)] pub fn enforce_trivial(&self, mut cs: CS, is_trivial: &Boolean) where CS: ConstraintSystem, diff --git a/src/parafold/cycle_fold/gadgets/emulated.rs b/src/parafold/cycle_fold/gadgets/emulated.rs index 1b3c97235..36e7a790e 100644 --- a/src/parafold/cycle_fold/gadgets/emulated.rs +++ b/src/parafold/cycle_fold/gadgets/emulated.rs @@ -1,15 +1,15 @@ use std::marker::PhantomData; use std::ops::BitAnd; -use bellpepper_core::{ConstraintSystem, SynthesisError, Variable}; use bellpepper_core::boolean::{AllocatedBit, Boolean}; use bellpepper_core::num::{AllocatedNum, Num}; +use bellpepper_core::{ConstraintSystem, SynthesisError, Variable}; use bellpepper_emulated::field_element::{ EmulatedFieldElement, EmulatedFieldParams, EmulatedLimbs, }; use bellpepper_emulated::util::bigint_to_scalar; use ff::{Field, PrimeField, PrimeFieldBits}; -use itertools::{Itertools, zip_eq}; +use itertools::{zip_eq, Itertools}; use neptune::circuit2::Elt; use num_bigint::{BigInt, Sign}; use num_traits::{Num as num_Num, One, Zero}; @@ -74,17 +74,6 @@ impl AllocatedBase { Self(EmulatedFieldElement::zero()) } - pub fn enforce_zero>(&self, mut cs: CS, is_zero: &Boolean) { - for (i, limb) in self.as_preimage().into_iter().enumerate() { - cs.enforce( - || format!("limb {i} is zero"), - |_| is_zero.lc(CS::one(), E::Scalar::ONE), - |_| limb.lc(), - |lc| lc, - ) - } - } - pub fn alloc_limbs>(mut cs: CS, limbs: Vec) -> Self { let element = EmulatedFieldElement::new_internal_element(EmulatedLimbs::Constant(limbs), 0) .allocate_field_element_unchecked(&mut cs.namespace(|| "alloc unchecked")) @@ -213,12 +202,6 @@ impl AllocatedBase { 0, )) } - - pub fn eq_native(&self, other: &E::Base) -> bool { - let lhs = self.to_big_int(); - let rhs = field_to_big_int(other); - lhs == rhs - } } pub fn field_to_big_int(f: &F) -> BigInt { diff --git a/src/parafold/cycle_fold/gadgets/secondary_commitment.rs b/src/parafold/cycle_fold/gadgets/secondary_commitment.rs index bec31b123..b413b8faa 100644 --- a/src/parafold/cycle_fold/gadgets/secondary_commitment.rs +++ b/src/parafold/cycle_fold/gadgets/secondary_commitment.rs @@ -1,12 +1,12 @@ -use bellpepper_core::{ConstraintSystem, SynthesisError}; use bellpepper_core::boolean::Boolean; +use bellpepper_core::{ConstraintSystem, SynthesisError}; use bitvec::macros::internal::funty::Fundamental; use neptune::circuit2::Elt; -use crate::Commitment; use crate::parafold::cycle_fold::gadgets::ecc::AllocatedPoint; -use crate::traits::{CurveCycleEquipped, Engine}; use crate::traits::commitment::CommitmentTrait; +use crate::traits::{CurveCycleEquipped, Engine}; +use crate::Commitment; #[derive(Debug, Clone)] pub struct AllocatedSecondaryCommitment { @@ -16,7 +16,7 @@ pub struct AllocatedSecondaryCommitment { impl AllocatedSecondaryCommitment { /// Allocates a new point on the curve using coordinates provided by `coords`. /// If coords = None, it allocates the default infinity point - pub fn alloc_unchecked(mut cs: CS, commitment: Commitment) -> Self + pub fn alloc_unchecked(mut cs: CS, commitment: &Commitment) -> Self where CS: ConstraintSystem, { @@ -60,16 +60,17 @@ impl AllocatedSecondaryCommitment { Ok(Self { commitment: res }) } - pub fn select_default(self, mut cs: CS, is_default: &Boolean) -> Result + pub fn select_default(&self, mut cs: CS, is_default: &Boolean) -> Result where CS: ConstraintSystem, { - let res = self + let commitment = self .commitment .select_default(cs.namespace(|| "select default"), is_default)?; - Ok(Self { commitment: res }) + Ok(Self { commitment }) } + #[allow(unused)] pub fn enforce_trivial(&self, mut cs: CS, is_trivial: &Boolean) where CS: ConstraintSystem, diff --git a/src/parafold/cycle_fold/nifs/circuit.rs b/src/parafold/cycle_fold/nifs/circuit.rs index 1901c4c20..a99e7c919 100644 --- a/src/parafold/cycle_fold/nifs/circuit.rs +++ b/src/parafold/cycle_fold/nifs/circuit.rs @@ -71,110 +71,79 @@ impl AllocatedSecondaryRelaxedR1CSInstance { Ok(()) } - // pub fn merge( - // mut cs: CS, - // self_L: Self, - // self_R: Self, - // transcript: &mut AllocatedTranscript, - // ) -> Result - // where - // CS: ConstraintSystem, - // { - // // Allocate T from transcript - // let T = AllocatedPoint::<::GE>::alloc_transcript( - // cs.namespace(|| "alloc T"), - // transcript, - // ); - // - // // Get truncated challenge - // let r_bits = transcript.squeeze_bits(cs.namespace(|| "r bits"), NUM_CHALLENGE_BITS)?; - // let r = BaseParams::base_from_bits(CS::one(), &r_bits); - // - // let Self { - // u: u_L, - // X: X_L, - // W: W_L, - // E: E_L, - // } = self_L; - // let Self { - // u: u_R, - // X: X_R, - // W: W_R, - // E: E_R, - // } = self_R; - // - // let u_next = u_R - // .mul(&mut cs.namespace(|| "u_R * r"), &r)? - // .add(&mut cs.namespace(|| "(u_R * r) + u_L"), &u_L)? - // .reduce(&mut cs.namespace(|| "((u_R * r) + u_L) % q"))?; - // let X_next = zip_eq(X_L, X_R) - // .enumerate() - // .map(|(i, (x_L, x_R))| { - // x_R - // .mul(&mut cs.namespace(|| "x_R * r"), &r)? - // .add(&mut cs.namespace(|| "(x_R * r) + x_L"), &x_L)? - // .reduce(&mut cs.namespace(|| "((x_R * r) + x_L) % q")) - // }) - // .collect::, _>>()?; - // - // let W_next = W_R - // .scalar_mul(cs.namespace(|| "r * W_R"), &r_bits)? - // .add(cs.namespace(|| "W_L + r * W_R"), &W_L)?; - // let E_next = { - // let E_tmp = E_R - // .scalar_mul(cs.namespace(|| "r * E_R"), &r_bits)? - // .add(cs.namespace(|| "T + r * E_R"), &T)?; - // E_tmp - // .scalar_mul(cs.namespace(|| "r * E_tmp"), &r_bits)? - // .add(cs.namespace(|| "E_L + r * E_tmp"), &E_L)? - // }; - // - // Ok(Self { - // u: u_next, - // X: X_next, - // W: W_next, - // E: E_next, - // }) - // } - - pub fn enforce_trivial(&self, mut cs: CS, is_trivial: &Boolean) + pub fn merge( + mut cs: CS, + self_L: Self, + self_R: Self, + transcript: &mut AllocatedTranscript, + ) -> Result where CS: ConstraintSystem, { - self - .u - .enforce_zero(cs.namespace(|| "enforce u = 0"), is_trivial); - for (i, x) in self.X.iter().enumerate() { - x.enforce_zero(cs.namespace(|| format!("enforce X[{i}] = 0")), is_trivial); - } - self - .W - .enforce_trivial(cs.namespace(|| "enforce trivial W"), is_trivial); - self - .E - .enforce_trivial(cs.namespace(|| "enforce trivial E"), is_trivial); + // Allocate T from transcript + let T = transcript.read_commitment_secondary(cs.namespace(|| "transcript T"))?; + + // Get truncated challenge + let r_bits = transcript.squeeze_bits(cs.namespace(|| "r bits"), NUM_CHALLENGE_BITS)?; + let r = AllocatedBase::from_bits_le(CS::one(), &r_bits); + + let Self { + u: u_L, + X: X_L, + W: W_L, + E: E_L, + } = self_L; + let Self { + u: u_R, + X: X_R, + W: W_R, + E: E_R, + } = self_R; + + let u_next = u_L.lc(&mut cs.namespace(|| "u_L + r * u_R % q"), &r, &u_R)?; + let X_next = zip_eq(X_L, X_R) + .enumerate() + .map(|(i, (x_L, x_R))| { + x_L.lc( + &mut cs.namespace(|| format!("X_L[{i}] + r * x_R[{i}] % q")), + &r, + &x_R, + ) + }) + .collect::, _>>()?; + + let W_next = W_L.lc(cs.namespace(|| "W_L + r * W_R"), &r_bits, &W_R)?; + let E_tmp = T.lc(cs.namespace(|| "T + r * E_R"), &r_bits, &E_R)?; + let E_next = E_L.lc(cs.namespace(|| "E_L + r * E_tmp"), &r_bits, &E_tmp)?; + + Ok(Self { + u: u_next, + X: X_next, + W: W_next, + E: E_next, + }) } /// Allocate and add the result to the transcript - pub fn alloc_unchecked(mut cs: CS, instance: RelaxedSecondaryR1CSInstance) -> Self + pub fn alloc_unchecked(mut cs: CS, instance: &RelaxedSecondaryR1CSInstance) -> Self where CS: ConstraintSystem, { let u = AllocatedBase::alloc_unchecked(cs.namespace(|| "read u"), instance.u); let X = instance .X - .into_iter() + .iter() .enumerate() - .map(|(i, x)| AllocatedBase::alloc_unchecked(cs.namespace(|| format!("read x[{i}]")), x)) + .map(|(i, x)| AllocatedBase::alloc_unchecked(cs.namespace(|| format!("read x[{i}]")), *x)) .collect(); let W = - AllocatedSecondaryCommitment::::alloc_unchecked(cs.namespace(|| "read W"), instance.W); + AllocatedSecondaryCommitment::::alloc_unchecked(cs.namespace(|| "read W"), &instance.W); let E = - AllocatedSecondaryCommitment::::alloc_unchecked(cs.namespace(|| "read E"), instance.E); + AllocatedSecondaryCommitment::::alloc_unchecked(cs.namespace(|| "read E"), &instance.E); Self { u, X, W, E } } - pub fn select_default(self, mut cs: CS, is_default: &Boolean) -> Result + pub fn select_default(&self, mut cs: CS, is_default: &Boolean) -> Result where CS: ConstraintSystem, { @@ -215,26 +184,4 @@ impl AllocatedSecondaryRelaxedR1CSInstance { self.E.as_preimage() ] } - - #[allow(unused)] - pub fn eq_native(&self, other: &RelaxedSecondaryR1CSInstance) -> Option { - if !self.u.eq_native(&other.u) { - return Some(false); - }; - - for (x, x_expected) in zip_eq(&self.X, &other.X) { - if !x.eq_native(x_expected) { - return Some(false); - } - } - - if !self.W.eq_native(&other.W)? { - return Some(false); - }; - if !self.E.eq_native(&other.E)? { - return Some(false); - }; - - return Some(true); - } } diff --git a/src/parafold/cycle_fold/nifs/prover.rs b/src/parafold/cycle_fold/nifs/prover.rs index 73764de45..57770655c 100644 --- a/src/parafold/cycle_fold/nifs/prover.rs +++ b/src/parafold/cycle_fold/nifs/prover.rs @@ -1,7 +1,8 @@ -use ff::Field; -use itertools::{chain, Itertools}; +use ff::{Field, PrimeField}; +use itertools::{chain, Itertools, zip_eq}; use rayon::prelude::*; +use crate::{Commitment, CommitmentKey, zip_with}; use crate::constants::NUM_CHALLENGE_BITS; use crate::errors::NovaError::{ProofVerifyError, UnSatIndex}; use crate::parafold::cycle_fold::nifs::NUM_IO_SECONDARY; @@ -10,9 +11,8 @@ use crate::parafold::transcript::prover::Transcript; use crate::parafold::transcript::TranscriptElement; use crate::r1cs::R1CSShape; use crate::supernova::error::SuperNovaError; -use crate::traits::commitment::CommitmentEngineTrait; use crate::traits::{CurveCycleEquipped, Engine}; -use crate::{Commitment, CommitmentKey}; +use crate::traits::commitment::CommitmentEngineTrait; /// Instance of a Relaxed-R1CS accumulator for a circuit. #[derive(Debug, Clone, PartialEq, Eq)] @@ -46,19 +46,6 @@ impl RelaxedSecondaryR1CS { } } - pub fn dummy() -> Self { - Self { - instance: RelaxedSecondaryR1CSInstance { - u: E::Base::ZERO, - X: vec![E::Base::ZERO; NUM_IO_SECONDARY], - W: Commitment::::default(), - E: Commitment::::default(), - }, - W: vec![], - E: vec![], - } - } - pub fn instance(&self) -> &RelaxedSecondaryR1CSInstance { &self.instance } @@ -72,6 +59,12 @@ impl RelaxedSecondaryR1CS { let _r = transcript.squeeze_bits(NUM_CHALLENGE_BITS); } + pub fn simulate_merge(transcript: &mut Transcript) { + let T = Commitment::::default(); + transcript.absorb(TranscriptElement::CommitmentSecondary(T)); + let _r = transcript.squeeze_bits(NUM_CHALLENGE_BITS); + } + pub fn fold( &mut self, ck: &CommitmentKey, @@ -100,18 +93,7 @@ impl RelaxedSecondaryR1CS { // TODO: Squeeze let r_bits = transcript.squeeze_bits(NUM_CHALLENGE_BITS); - let r = { - r_bits - .into_iter() - .rev() - .fold(E::Base::ZERO, |mut acc: E::Base, bit| { - acc = acc.double(); - if bit { - acc += E::Base::ONE; - } - acc - }) - }; + let r = bits_le_to_scalar::(&r_bits); self .W @@ -136,54 +118,55 @@ impl RelaxedSecondaryR1CS { self.instance.E = self.instance.E + T_comm * r; } - // pub fn merge( - // ck: &CommitmentKey, - // shape: &R1CSShape, - // acc_L: Self, - // acc_R: &Self, - // transcript: &mut Transcript, - // ) -> Self { - // let (T, T_comm) = compute_fold_proof( - // ck, - // shape, - // &acc_L.instance.u, - // &acc_L.instance.X, - // &acc_L.W, - // Some(acc_R.instance.u), - // &acc_R.instance.X, - // &acc_R.W, - // ); - // - // transcript.absorb(comm_to_base::(&T_comm)); - // let r = transcript.squeeze_bits_secondary(NUM_CHALLENGE_BITS); - // - // let W = zip_with!( - // (acc_L.W.into_par_iter(), acc_R.W.par_iter()), - // |w_L, w_R| w_L + r * w_R - // ) - // .collect(); - // - // let E = zip_with!( - // (acc_L.E.into_par_iter(), T.par_iter(), acc_R.E.par_iter()), - // |e_L, t, e_R| e_L + r * (*t + r * e_R) - // ) - // .collect(); - // - // let instance = { - // let u = acc_L.instance.u + r * acc_R.instance.u; - // let X = zip_eq(acc_L.instance.X, &acc_R.instance.X) - // .map(|(x_L, x_R)| x_L + r * x_R) - // .collect(); - // - // let W = acc_L.instance.W + acc_R.instance.W * r; - // let E_tmp = T_comm + acc_R.instance.E * r; - // let E = acc_L.instance.E + E_tmp * r; - // - // RelaxedSecondaryR1CSInstance { u, X, W, E } - // }; - // - // Self { instance, W, E } - // } + pub fn merge( + ck: &CommitmentKey, + shape: &R1CSShape, + acc_L: Self, + acc_R: Self, + transcript: &mut Transcript, + ) -> Self { + let (T, T_comm) = compute_fold_proof( + ck, + shape, + acc_L.instance.u, + &acc_L.instance.X, + &acc_L.W, + Some(acc_R.instance.u), + &acc_R.instance.X, + &acc_R.W, + ); + + transcript.absorb(TranscriptElement::CommitmentSecondary(T_comm)); + let r_bits = transcript.squeeze_bits(NUM_CHALLENGE_BITS); + let r = bits_le_to_scalar::(&r_bits); + + let instance = { + let u = acc_L.instance.u + r * acc_R.instance.u; + let X = zip_eq(acc_L.instance.X, acc_R.instance.X) + .map(|(x_L, x_R)| x_L + r * x_R) + .collect(); + + let W = acc_L.instance.W + acc_R.instance.W * r; + let E_tmp = T_comm + acc_R.instance.E * r; + let E = acc_L.instance.E + E_tmp * r; + + RelaxedSecondaryR1CSInstance { u, X, W, E } + }; + + let W = zip_with!( + (acc_L.W.into_par_iter(), acc_R.W.par_iter()), + |w_L, w_R| w_L + r * w_R + ) + .collect(); + + let E = zip_with!( + (acc_L.E.into_par_iter(), T.par_iter(), acc_R.E.par_iter()), + |e_L, t, e_R| e_L + r * (*t + r * e_R) + ) + .collect(); + + Self { instance, W, E } + } pub fn verify( &self, ck: &CommitmentKey, @@ -237,3 +220,13 @@ impl RelaxedSecondaryR1CSInstance { chain![[u], X, [W, E]] } } + +fn bits_le_to_scalar(bits: &[bool]) -> F { + bits.into_iter().rev().fold(F::ZERO, |mut acc: F, bit| { + acc = acc.double(); + if *bit { + acc += F::ONE; + } + acc + }) +} diff --git a/src/parafold/cycle_fold/prover.rs b/src/parafold/cycle_fold/prover.rs index 21b7e5d97..80186f9e8 100644 --- a/src/parafold/cycle_fold/prover.rs +++ b/src/parafold/cycle_fold/prover.rs @@ -25,23 +25,19 @@ use crate::traits::CurveCycleEquipped; #[derive(Debug, PartialEq, Eq)] pub struct ScalarMulAccumulator { deferred: Vec>, - acc: RelaxedSecondaryR1CS, } -impl ScalarMulAccumulator { - pub fn new(acc: RelaxedSecondaryR1CS) -> Self { - Self { - deferred: vec![], - acc, +impl Drop for ScalarMulAccumulator { + fn drop(&mut self) { + if !self.deferred.is_empty() { + panic!("unproved scalar mul instances") } } +} - /// Create a dummy accumulator for simulation purposes - pub fn dummy() -> Self { - Self { - deferred: vec![], - acc: RelaxedSecondaryR1CS::dummy(), - } +impl ScalarMulAccumulator { + pub fn new() -> Self { + Self { deferred: vec![] } } /// Given two commitments `A`, `B` and a scalar `x`, compute `C <- A + x * B` @@ -71,8 +67,9 @@ impl ScalarMulAccumulator { mut self, ck: &CommitmentKey, shape: &R1CSShape, + acc_cf: &mut RelaxedSecondaryR1CS, transcript: &mut Transcript, - ) -> Result, SynthesisError> { + ) -> Result<(), SynthesisError> { for instance in self.deferred.drain(..) { let mut cs = SatisfyingAssignment::::new(); @@ -87,17 +84,16 @@ impl ScalarMulAccumulator { assert_eq!(&X_expected, &X[1..]); - self.acc.fold(ck, shape, &X[1..], &W, transcript); + acc_cf.fold(ck, shape, &X[1..], &W, transcript); } - Ok(self.acc) + Ok(()) } - pub fn simulate_finalize(mut self, transcript: &mut Transcript) -> RelaxedSecondaryR1CS { + pub fn simulate_finalize(mut self, transcript: &mut Transcript) { self .deferred .drain(..) .for_each(|_| RelaxedSecondaryR1CS::simulate_fold(transcript)); - self.acc } } diff --git a/src/parafold/mod.rs b/src/parafold/mod.rs index a936a4a14..1fb89a4a5 100644 --- a/src/parafold/mod.rs +++ b/src/parafold/mod.rs @@ -6,37 +6,6 @@ mod nifs; mod nivc; -#[allow(dead_code)] -mod prover; +pub mod prover; mod transcript; - -// pub struct ProvingKey { -// /// Commitment Key -// ck: CommitmentKey, -// /// Random oracle constants used for hashing io of the self verification circuit. -// ro_consts: ROConstants, - -// /// Public Parameter digest -// pp: E::Scalar, -// /// Shape of the self verification circuit -// shape: R1CSShape, -// /// Proving keys for each NIVC [StepCircuit]s -// nivc: Vec>, -// } - -// pub struct IVCProvingKey { -// pp: E::Scalar, -// shape: R1CSShape, -// } -// -// #[derive(Clone)] -// pub struct NIVCProof { -// io: NIVCState, -// proof: FoldProof, -// } -// -// pub struct NIVCWitness { -// io: NIVCState, -// proof: R1CSProof, -// } diff --git a/src/parafold/nifs/circuit.rs b/src/parafold/nifs/circuit.rs index dc6fca532..579269441 100644 --- a/src/parafold/nifs/circuit.rs +++ b/src/parafold/nifs/circuit.rs @@ -1,13 +1,13 @@ -use bellpepper_core::num::AllocatedNum; use bellpepper_core::{ConstraintSystem, SynthesisError}; +use bellpepper_core::num::AllocatedNum; use ff::PrimeField; use itertools::*; -use neptune::circuit2::{poseidon_hash_allocated, Elt}; +use neptune::circuit2::{Elt, poseidon_hash_allocated}; -use crate::parafold::cycle_fold::circuit::AllocatedScalarMulAccumulator; use crate::parafold::cycle_fold::AllocatedPrimaryCommitment; +use crate::parafold::cycle_fold::circuit::AllocatedScalarMulAccumulator; use crate::parafold::nifs::{ - R1CSPoseidonConstants, RelaxedR1CSInstance, PRIMARY_R1CS_INSTANCE_SIZE, + PRIMARY_R1CS_INSTANCE_SIZE, R1CSPoseidonConstants, RelaxedR1CSInstance, }; use crate::parafold::transcript::circuit::AllocatedTranscript; use crate::traits::CurveCycleEquipped; @@ -17,7 +17,7 @@ use crate::traits::CurveCycleEquipped; pub struct AllocatedRelaxedR1CSInstance { pp: AllocatedNum, u: AllocatedNum, - X: [AllocatedNum;2], + X: AllocatedNum, W: AllocatedPrimaryCommitment, E: AllocatedPrimaryCommitment, } @@ -25,12 +25,12 @@ pub struct AllocatedRelaxedR1CSInstance { impl AllocatedRelaxedR1CSInstance { /// Folds an R1CSInstance into `self` pub fn fold( - self, + &mut self, mut cs: CS, - X_new: [AllocatedNum;2], + X_new: AllocatedNum, acc_sm: &mut AllocatedScalarMulAccumulator, transcript: &mut AllocatedTranscript, - ) -> Result + ) -> Result<(), SynthesisError> where CS: ConstraintSystem, { @@ -41,128 +41,106 @@ impl AllocatedRelaxedR1CSInstance { let r_bits = r.to_bits_le(cs.namespace(|| "r_bits"))?; let Self { - pp, + u: u_curr, X: X_curr, W: W_curr, E: E_curr, - u: u_curr, + .. } = self; // Linear combination of acc with new - let u_next = u_curr.add(cs.namespace(|| "u_next"), &r)?; - let X0_next = mul_add(cs.namespace(|| "X0_next"), &X_curr[0], &X_new[0], &r)?; - let X1_next = mul_add(cs.namespace(|| "X1_next"), &X_curr[1], &X_new[1], &r)?; + self.u = u_curr.add(cs.namespace(|| "u_new"), &r)?; + self.X = mul_add(cs.namespace(|| "X_next"), X_curr, &X_new, &r)?; // W_next = W_curr + r * W_new - let W_next = acc_sm.scalar_mul( + self.W = acc_sm.scalar_mul( cs.namespace(|| "W_next"), W_curr.clone(), W_new.clone(), r_bits.clone(), transcript, )?; - let E_next = acc_sm.scalar_mul( + self.E = acc_sm.scalar_mul( cs.namespace(|| "E_next"), E_curr.clone(), T.clone(), r_bits, transcript, )?; - - Ok(Self { - pp, - u: u_next, - X: [X0_next,X1_next], - W: W_next, - E: E_next, - }) + Ok(()) } - // /// Optimized merge over the primary curve, where the same `r` is used across many accumulators. - // pub fn merge_many( - // mut cs: CS, - // accs_L: Vec, - // accs_R: Vec, - // acc_sm: &mut AllocatedScalarMulAccumulator, - // transcript: &mut AllocatedTranscript, - // ) -> Result, SynthesisError> - // where - // CS: ConstraintSystem, - // { - // assert_eq!(accs_L.len(), accs_R.len()); - // - // // Add all cross-term commitments to the transcript. - // let Ts = (0..accs_L.len()) - // .map(|i| transcript.read_commitment_primary(cs.namespace(|| format!("transcript T[{i}]")))) - // .collect::, _>>()?; - // - // // Get common challenge - // let r = transcript.squeeze(cs.namespace(|| "squeeze r"))?; - // let r_bits = r.to_bits_le(cs.namespace(|| "r_bits"))?; - // - // // Merge all accumulators - // let accs_next = zip_eq(accs_L, accs_R) - // .zip_eq(Ts) - // .map(|((acc_L, acc_R), T)| { - // let Self { - // pp: pp_L, - // u: u_L, - // X: X_L, - // W: W_L, - // E: E_L, - // .. - // } = acc_L; - // let Self { - // pp: pp_R, - // u: u_R, - // X: X_R, - // W: W_R, - // E: E_R, - // .. - // } = acc_R; - // - // cs.enforce( - // || "pp_L = pp_R", - // |lc| lc, - // |lc| lc, - // |lc| lc + pp_L.get_variable() - pp_R.get_variable(), - // ); - // - // let u_next = mul_add(cs.namespace(|| "u_new"), &u_L, &u_R, &r)?; - // let X_next = mul_add(cs.namespace(|| "X_new[{i}]"), &X_L, &X_R, &r)?; - // let W_next = acc_sm.scalar_mul( - // cs.namespace(|| "W_next"), - // W_L.clone(), - // W_R.clone(), - // r_bits.clone(), - // transcript, - // )?; - // let E1_next = acc_sm.scalar_mul( - // cs.namespace(|| "E1_next"), - // T.clone(), - // E_R.clone(), - // r_bits.clone(), - // transcript, - // )?; - // let E_next = acc_sm.scalar_mul( - // cs.namespace(|| "E_next"), - // E_L.clone(), - // E1_next.clone(), - // r_bits.clone(), - // transcript, - // )?; - // - // Ok::(Self { - // pp: pp_L, - // u: u_next, - // X: X_next, - // W: W_next, - // E: E_next, - // }) - // }) - // .collect::, _>>()?; - // - // Ok(accs_next) - // } + /// Optimized merge over the primary curve, where the same `r` is used across many accumulators. + pub fn merge_many( + mut cs: CS, + accs_L: Vec, + accs_R: Vec, + acc_sm: &mut AllocatedScalarMulAccumulator, + transcript: &mut AllocatedTranscript, + ) -> Result, SynthesisError> + where + CS: ConstraintSystem, + { + assert_eq!(accs_L.len(), accs_R.len()); + + // Add all cross-term commitments to the transcript. + let Ts = (0..accs_L.len()) + .map(|i| transcript.read_commitment_primary(cs.namespace(|| format!("transcript T[{i}]")))) + .collect::, _>>()?; + + // Get common challenge + let r = transcript.squeeze(cs.namespace(|| "squeeze r"))?; + let r_bits = r.to_bits_le(cs.namespace(|| "r_bits"))?; + if let Some(r) = r.get_value() { + println!("c mm: {:?}", r); + } + + // Merge all accumulators + zip_eq(accs_L.into_iter(), accs_R.into_iter()) + .zip_eq(Ts) + .enumerate() + .map(|(i, ((acc_L, acc_R), T))| { + let mut cs = cs.namespace(|| format!("merge {i}")); + cs.enforce( + || "pp_L = pp_R", + |lc| lc, + |lc| lc, + |lc| lc + acc_L.pp.get_variable() - acc_R.pp.get_variable(), + ); + + let u_next = mul_add(cs.namespace(|| "u_next"), &acc_L.u, &acc_R.u, &r)?; + let X_next = mul_add(cs.namespace(|| "X_next"), &acc_L.X, &acc_R.X, &r)?; + let W_next = acc_sm.scalar_mul( + cs.namespace(|| "W_next"), + acc_L.W.clone(), + acc_R.W.clone(), + r_bits.clone(), + transcript, + )?; + let E1_next = acc_sm.scalar_mul( + cs.namespace(|| "E1_next"), + T, + acc_R.E.clone(), + r_bits.clone(), + transcript, + )?; + let E_next = acc_sm.scalar_mul( + cs.namespace(|| "E_next"), + acc_L.E.clone(), + E1_next.clone(), + r_bits.clone(), + transcript, + )?; + + Ok::(Self { + pp: acc_L.pp, + u: u_next, + X: X_next, + W: W_next, + E: E_next, + }) + }) + .collect::, _>>() + } /// Compute the hash of the accumulator over the primary curve. pub fn hash(&self, mut cs: CS) -> Result, SynthesisError> @@ -179,35 +157,27 @@ impl AllocatedRelaxedR1CSInstance { poseidon_hash_allocated(cs.namespace(|| "hash"), elements, &constants) } - pub fn alloc(mut cs: CS, instance: RelaxedR1CSInstance) -> Self + pub fn alloc(mut cs: CS, instance: &RelaxedR1CSInstance) -> Self where CS: ConstraintSystem, { let pp = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pp"), || instance.pp); let u = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc u"), || instance.u); - let X0 = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc X0"), || instance.X[0]); - let X1 = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc X1"), || instance.X[1]); + let X = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc X"), || instance.X); + let W = AllocatedPrimaryCommitment::alloc(cs.namespace(|| "alloc W"), &instance.W); let E = AllocatedPrimaryCommitment::alloc(cs.namespace(|| "alloc E"), &instance.E); - Self { - pp, - u, - X: [X0, X1], - W, - E, - } + Self { pp, u, X, W, E } } pub fn as_preimage(&self) -> impl IntoIterator> { chain!( [ - self.pp.clone(), - self.u.clone(), - self.X[0].clone(), - self.X[1].clone() - ] - .map(Elt::Allocated), + Elt::Allocated(self.pp.clone()), + Elt::Allocated(self.u.clone()), + Elt::Allocated(self.X.clone()), + ], self.W.as_preimage(), self.E.as_preimage() ) diff --git a/src/parafold/nifs/mod.rs b/src/parafold/nifs/mod.rs index 6a39e9d2b..30b1a7911 100644 --- a/src/parafold/nifs/mod.rs +++ b/src/parafold/nifs/mod.rs @@ -1,27 +1,27 @@ -use digest::consts::U12; use ff::Field; +use neptune::generic_array::typenum::U11; use neptune::poseidon::PoseidonConstants; use rayon::prelude::*; +use crate::{CE, Commitment, CommitmentKey}; use crate::r1cs::R1CSShape; -use crate::traits::commitment::CommitmentEngineTrait; use crate::traits::{CurveCycleEquipped, Engine}; -use crate::{Commitment, CommitmentKey, CE}; +use crate::traits::commitment::CommitmentEngineTrait; pub mod circuit; pub mod prover; -const PRIMARY_R1CS_INSTANCE_SIZE: usize = 12; +const PRIMARY_R1CS_INSTANCE_SIZE: usize = 11; /// Exact-sized Poseidon constants for hashing a RelaxedR1CSInstance. /// Assumes that Commitments are serialized as 4=BN_NUM_LIMBS limbs. -type R1CSPoseidonConstants = PoseidonConstants<::Scalar, U12>; +type R1CSPoseidonConstants = PoseidonConstants<::Scalar, U11>; /// Instance of a Relaxed-R1CS accumulator for a circuit. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RelaxedR1CSInstance { pp: E::Scalar, u: E::Scalar, - X: [E::Scalar; 2], + X: E::Scalar, W: Commitment, E: Commitment, } diff --git a/src/parafold/nifs/prover.rs b/src/parafold/nifs/prover.rs index 2fa2fa7f1..391400ee3 100644 --- a/src/parafold/nifs/prover.rs +++ b/src/parafold/nifs/prover.rs @@ -1,13 +1,12 @@ use ff::Field; -use itertools::Itertools; +use itertools::{chain, Itertools}; use neptune::Poseidon; use rayon::prelude::*; -use crate::{Commitment, CommitmentKey}; use crate::errors::NovaError::{ProofVerifyError, UnSatIndex}; use crate::parafold::cycle_fold::prover::ScalarMulAccumulator; use crate::parafold::nifs::{ - compute_fold_proof, PRIMARY_R1CS_INSTANCE_SIZE, R1CSPoseidonConstants, RelaxedR1CSInstance, + compute_fold_proof, R1CSPoseidonConstants, RelaxedR1CSInstance, PRIMARY_R1CS_INSTANCE_SIZE, }; use crate::parafold::transcript::prover::Transcript; use crate::parafold::transcript::TranscriptElement; @@ -15,6 +14,7 @@ use crate::r1cs::R1CSShape; use crate::supernova::error::SuperNovaError; use crate::traits::commitment::CommitmentEngineTrait; use crate::traits::CurveCycleEquipped; +use crate::{zip_with, Commitment, CommitmentKey}; /// A full Relaxed-R1CS accumulator for a circuit /// # TODO: @@ -31,12 +31,12 @@ pub struct RelaxedR1CS { impl RelaxedR1CS { pub fn new(shape: &R1CSShape) -> Self { - assert_eq!(shape.num_io, 2); + assert_eq!(shape.num_io, 2); // TODO HACK: IO needs to be even, it really is 1 Self { instance: RelaxedR1CSInstance { pp: shape.digest(), u: E::Scalar::ZERO, - X: [E::Scalar::ZERO; 2], + X: E::Scalar::ZERO, W: Commitment::::default(), E: Commitment::::default(), }, @@ -51,14 +51,38 @@ impl RelaxedR1CS { /// Simulate the fold protocol for a circuit on the primary curve, creating a trivial proof, /// while updating the transcript with the standard pattern. pub fn simulate_fold(acc_sm: &mut ScalarMulAccumulator, transcript: &mut Transcript) { - let W = Commitment::::default(); + let W_curr = Commitment::::default(); + let E_curr = Commitment::::default(); + let W_new = Commitment::::default(); let T = Commitment::::default(); - transcript.absorb(TranscriptElement::CommitmentPrimary(W)); + transcript.absorb(TranscriptElement::CommitmentPrimary(W_new)); transcript.absorb(TranscriptElement::CommitmentPrimary(T)); let r = transcript.squeeze(); - let _ = acc_sm.scalar_mul(W, W, r, transcript); - let _ = acc_sm.scalar_mul(T, T, r, transcript); + let _W_next = acc_sm.scalar_mul(W_curr, W_new, r, transcript); + let _E_next = acc_sm.scalar_mul(E_curr, T, r, transcript); + } + + pub fn simulate_merge_many( + n: usize, + acc_sm: &mut ScalarMulAccumulator, + transcript: &mut Transcript, + ) { + let W_L = Commitment::::default(); + let W_R = Commitment::::default(); + let E_L = Commitment::::default(); + let E_R = Commitment::::default(); + let T = Commitment::::default(); + for _ in 0..n { + transcript.absorb(TranscriptElement::CommitmentPrimary(T.clone())); + } + + let r = transcript.squeeze(); + for _ in 0..n { + let _W = acc_sm.scalar_mul(W_L, W_R, r, transcript); + let E_tmp = acc_sm.scalar_mul(T, E_R, r, transcript); + let _E = acc_sm.scalar_mul(E_L, E_tmp, r, transcript); + } } /// Given the public IO `X_new` for a circuit with R1CS representation `shape`, @@ -73,7 +97,7 @@ impl RelaxedR1CS { &mut self, ck: &CommitmentKey, shape: &R1CSShape, - X_new: [E::Scalar; 2], + X_new: E::Scalar, W_new: &[E::Scalar], acc_sm: &mut ScalarMulAccumulator, transcript: &mut Transcript, @@ -85,10 +109,10 @@ impl RelaxedR1CS { ck, shape, self.instance.u, - &self.instance.X, + &[self.instance.X, self.instance.X], // TODO HACK: IO needs to be even &self.W, None, - &X_new, + &[X_new, X_new], // TODO HACK: IO needs to be even W_new, ) }; @@ -109,10 +133,8 @@ impl RelaxedR1CS { .zip_eq(T.par_iter()) .for_each(|(e, t)| *e += r * t); - // For non-relaxed instances, u_new = 1 self.instance.u += r; - self.instance.X[0] += r * X_new[0]; - self.instance.X[1] += r * X_new[1]; + self.instance.X += r * X_new; // Compute scalar multiplications and resulting instances to be proved with the CycleFold circuit // W_comm_next = W_comm_curr + r * W_comm_new @@ -122,83 +144,88 @@ impl RelaxedR1CS { self.instance.E = acc_sm.scalar_mul(self.instance.E, T_comm, r, transcript); } - // /// Given two lists of [RelaxedR1CS] accumulators, - // pub fn merge_many( - // ck: &CommitmentKey, - // shapes: &[R1CSShape], - // mut accs_L: Vec, - // accs_R: &[Self], - // acc_sm: &mut ScalarMulAccumulator, - // transcript: &mut Transcript, - // ) -> Vec { - // // TODO: parallelize - // let (Ts, T_comms): (Vec<_>, Vec<_>) = zip_with!( - // (accs_L.iter_mut(), accs_R.iter(), shapes), - // |acc_L, acc_R, shape| { - // compute_fold_proof( - // ck, - // shape, - // &acc_L.instance.u, - // &[acc_L.instance.X], - // &acc_L.W, - // Some(acc_R.instance.u), - // &[acc_R.instance.X], - // &acc_R.W, - // ) - // } - // ) - // .unzip(); - // - // for T_comm in &T_comms { - // transcript.absorb(T_comm.into()); - // } - // let r = transcript.squeeze(); - // - // zip_with!( - // ( - // accs_L.into_iter(), - // accs_R.iter(), - // Ts.iter(), - // T_comms.into_iter() - // ), - // |acc_L, acc_R, T, T_comm| { - // let W = zip_with!( - // (acc_L.W.into_par_iter(), acc_R.W.par_iter()), - // |w_L, w_R| w_L + r * w_R - // ) - // .collect(); - // - // let E = zip_with!( - // (acc_L.E.into_par_iter(), T.par_iter(), acc_R.E.par_iter()), - // |e_L, t, e_R| e_L + r * (*t + r * e_R) - // ) - // .collect(); - // - // let instance = { - // assert_eq!(acc_L.instance.pp, acc_R.instance.pp); - // let pp = acc_L.instance.pp; - // - // let u = acc_L.instance.u + r * acc_R.instance.u; - // let X = acc_L.instance.X + r * acc_R.instance.X; - // - // // Compute scalar multiplications and resulting instances to be proved with the CycleFold circuit - // // W_next = W_L + r * W_R - // let W = acc_sm.scalar_mul(acc_L.instance.W, acc_R.instance.W, r, transcript); - // - // let E_tmp = acc_sm.scalar_mul(T_comm, acc_R.instance.E, r, transcript); - // // E_next = E_L + r * E1_next = E_L + r * T + r^2 * E_R - // let E = acc_sm.scalar_mul(acc_L.instance.E, E_tmp, r, transcript); - // - // RelaxedR1CSInstance { pp, u, X, W, E } - // }; - // Self { instance, W, E } - // } - // ) - // .collect() - // } - + /// Given two lists of [RelaxedR1CS] accumulators, + pub fn merge_many( + ck: &CommitmentKey, + shapes: &[R1CSShape], + mut accs_L: Vec, + accs_R: &[Self], + acc_sm: &mut ScalarMulAccumulator, + transcript: &mut Transcript, + ) -> Vec { + // TODO: parallelize + let (Ts, T_comms): (Vec<_>, Vec<_>) = zip_with!( + (accs_L.iter_mut(), accs_R.iter(), shapes), + |acc_L, acc_R, shape| { + compute_fold_proof( + ck, + shape, + acc_L.instance.u, + &[acc_L.instance.X, acc_L.instance.X], + &acc_L.W, + Some(acc_R.instance.u), + &[acc_R.instance.X, acc_R.instance.X], + &acc_R.W, + ) + } + ) + .unzip(); + + for T_comm in &T_comms { + transcript.absorb(TranscriptElement::CommitmentPrimary(*T_comm)); + } + let r = transcript.squeeze(); + println!("p mm: {:?}", r); + + zip_with!( + ( + accs_L.into_iter(), + accs_R.iter(), + Ts.iter(), + T_comms.into_iter() + ), + |acc_L, acc_R, T, T_comm| { + let W = zip_with!( + (acc_L.W.into_par_iter(), acc_R.W.par_iter()), + |w_L, w_R| w_L + r * w_R + ) + .collect(); + + let E = zip_with!( + (acc_L.E.into_par_iter(), T.par_iter(), acc_R.E.par_iter()), + |e_L, t, e_R| e_L + r * (*t + r * e_R) + ) + .collect(); + + let instance = { + assert_eq!(acc_L.instance.pp, acc_R.instance.pp); + let pp = acc_L.instance.pp; + + let u = acc_L.instance.u + r * acc_R.instance.u; + let X = acc_L.instance.X + r * acc_R.instance.X; + + // Compute scalar multiplications and resulting instances to be proved with the CycleFold circuit + // W_next = W_L + r * W_R + let W = acc_sm.scalar_mul(acc_L.instance.W, acc_R.instance.W, r, transcript); + + let E_tmp = acc_sm.scalar_mul(T_comm, acc_R.instance.E, r, transcript); + // E_next = E_L + r * E1_next = E_L + r * T + r^2 * E_R + let E = acc_sm.scalar_mul(acc_L.instance.E, E_tmp, r, transcript); + + RelaxedR1CSInstance { pp, u, X, W, E } + }; + Self { instance, W, E } + } + ) + .collect() + } + pub fn verify(&self, ck: &CommitmentKey, shape: &R1CSShape) -> Result<(), SuperNovaError> { - let E_expected = shape.compute_E(&self.W, &self.instance.u, &self.instance.X)?; + let E_expected = shape.compute_E( + &self.W, + &self.instance.u, + &[self.instance.X, self.instance.X], // TODO HACK: IO needs to be even + )?; self .E .iter() @@ -237,13 +264,13 @@ impl RelaxedR1CSInstance { } } pub fn as_preimage(&self) -> impl IntoIterator> { - let pp = TranscriptElement::Scalar(self.pp); - let u = TranscriptElement::Scalar(self.u); - let X0 = TranscriptElement::Scalar(self.X[0]); - let X1 = TranscriptElement::Scalar(self.X[1]); - let W = TranscriptElement::CommitmentPrimary(self.W.clone()); - let E = TranscriptElement::CommitmentPrimary(self.E.clone()); - [pp, u, X0, X1, W, E] + chain!( + [self.pp, self.u, self.X].map(TranscriptElement::Scalar), + [ + TranscriptElement::CommitmentPrimary(self.W.clone()), + TranscriptElement::CommitmentPrimary(self.E.clone()) + ], + ) } /// On the primary curve, the instances are stored as hashes in the recursive state. @@ -261,8 +288,8 @@ impl RelaxedR1CSInstance { #[cfg(test)] mod tests { - use bellpepper_core::ConstraintSystem; use bellpepper_core::test_cs::TestConstraintSystem; + use bellpepper_core::ConstraintSystem; use itertools::zip_eq; use crate::parafold::nifs::circuit::AllocatedRelaxedR1CSInstance; @@ -282,7 +309,7 @@ mod tests { let acc = RelaxedR1CSInstance::::dummy(); let allocated_acc = - AllocatedRelaxedR1CSInstance::::alloc(cs.namespace(|| "alloc acc"), acc.clone()); + AllocatedRelaxedR1CSInstance::::alloc(cs.namespace(|| "alloc acc"), &acc); let acc_hash = acc.hash(); let allocated_acc_hash = allocated_acc.hash(cs.namespace(|| "hash")).unwrap(); diff --git a/src/parafold/nivc/circuit.rs b/src/parafold/nivc/circuit.rs index b14255f9c..3c0a6dc4f 100644 --- a/src/parafold/nivc/circuit.rs +++ b/src/parafold/nivc/circuit.rs @@ -1,8 +1,8 @@ -use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError}; use bellpepper_core::boolean::{AllocatedBit, Boolean}; use bellpepper_core::num::AllocatedNum; +use bellpepper_core::{ConstraintSystem, LinearCombination, SynthesisError}; use ff::Field; -use itertools::{chain, zip_eq}; +use itertools::{chain, enumerate, zip_eq}; use neptune::circuit2::Elt; use neptune::hash_type::HashType; use neptune::sponge::api::{IOPattern, SpongeAPI, SpongeOp}; @@ -17,7 +17,8 @@ use crate::parafold::cycle_fold::nifs::circuit::AllocatedSecondaryRelaxedR1CSIns use crate::parafold::nifs::circuit::AllocatedRelaxedR1CSInstance; use crate::parafold::nifs::RelaxedR1CSInstance; use crate::parafold::nivc::{ - AllocatedNIVCIO, NIVCIO, NIVCPoseidonConstants, NIVCStateInstance, NIVCUpdateProof, + AllocatedNIVCIO, NIVCCircuitInput, NIVCMergeProof, NIVCPoseidonConstants, NIVCStateInstance, + NIVCIO, }; use crate::parafold::transcript::circuit::AllocatedTranscript; use crate::parafold::transcript::TranscriptConstants; @@ -26,102 +27,165 @@ use crate::traits::CurveCycleEquipped; /// A representation of a NIVC state, where `io` represents the computations inputs and outputs, /// and the `accs` are the accumulators for each step function that was used to produce this result. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct AllocatedNIVCState { + transcript_state: AllocatedNum, io: AllocatedNIVCIO, accs_hash: Vec>, acc_cf: AllocatedSecondaryRelaxedR1CSInstance, } impl AllocatedNIVCState { - /// Loads a previously proved state from a proof of its correctness. - pub fn from_proof( - mut cs: CS, - ro_consts: &TranscriptConstants, - proof: NIVCUpdateProof, - ) -> Result<(Self, AllocatedNum), SynthesisError> + pub fn alloc_unverified(mut cs: CS, state: &NIVCStateInstance) -> (Self, Boolean) where CS: ConstraintSystem, { - let NIVCUpdateProof { - transcript_prev, - transcript_buffer, - state, - acc_prev, - index_prev, - } = proof; - - // Allocate inputs: - // - previous transcript state - // - previous output - let transcript_prev = - AllocatedNum::alloc_infallible(cs.namespace(|| "alloc transcript_prev"), || transcript_prev); - let state = AllocatedNIVCState::alloc_unchecked(cs.namespace(|| "alloc state"), state); + let transcript_state = + AllocatedNum::alloc_infallible(cs.namespace(|| "alloc transcript_state"), || { + state.transcript_state + }); + + let io = AllocatedNIVCIO::alloc(cs.namespace(|| "alloc io"), &state.io); + let accs_hash = enumerate(&state.accs_hash) + .map(|(i, acc_hash)| { + AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc acc_hash {i}")), || *acc_hash) + }) + .collect::>(); + let acc_cf = AllocatedSecondaryRelaxedR1CSInstance::alloc_unchecked( + cs.namespace(|| "alloc acc_cf"), + &state.acc_cf, + ); // Define the base case as transcript_prev == 0 - let is_base_case: Boolean = { + let is_base_case = { let zero = alloc_zero(cs.namespace(|| "alloc zero")); - Boolean::from(alloc_num_equals( - cs.namespace(|| "transcript_prev == 0"), - &transcript_prev, + let is_base_case = alloc_num_equals( + cs.namespace(|| "transcript_state == 0"), + &transcript_state, &zero, - )?) + ) + .unwrap(); + Boolean::from(is_base_case) }; - // Compute hash of inputs - let state_hash = state.hash(cs.namespace(|| "state hash"))?; - - // Set the IO of the previous circuit - let X_prev = [state_hash, transcript_prev]; - - // Initialize the transcript with the previous IO, and load the contents generated by the prover. - let mut transcript = - AllocatedTranscript::new(ro_consts.clone(), X_prev.clone(), transcript_buffer); - // Enforce that the current IO is trivial, i.e. io.in == io.out - state.io.enforce_trivial( + io.enforce_trivial( cs.namespace(|| "is_base_case => (io.in == io.out)"), &is_base_case, ); - // Enforce that the secondary curve circuit is trivial - state.acc_cf.enforce_trivial( - cs.namespace(|| "is_base_case => acc_cf.is_trivial"), - &is_base_case, + ( + Self { + transcript_state, + io, + accs_hash, + acc_cf, + }, + is_base_case, + ) + } + + /// Loads a previously proved state from a proof of its correctness. + pub fn init( + mut cs: CS, + ro_consts: TranscriptConstants, + input: &NIVCCircuitInput, + ) -> Result + where + CS: ConstraintSystem, + { + let NIVCCircuitInput { instance, proof } = input; + let (mut state, is_base_case) = Self::alloc_unverified(cs.namespace(|| "alloc"), instance); + let state_hash = state.hash(cs.namespace(|| "state hash"))?; + + let mut transcript = AllocatedTranscript::new( + ro_consts, + [state_hash.clone()], + proof.transcript_buffer.clone(), ); // Initialize scalar mul accumulator for folding - let mut acc_sm = AllocatedScalarMulAccumulator::new(state.acc_cf); - - // Update the set of accumulators with the fresh folding proof - let accs_hash_next = Self::update_accs( - cs.namespace(|| "update accs"), - state.accs_hash, - X_prev, - acc_prev, - index_prev, - &is_base_case, - &mut acc_sm, - &mut transcript, - )?; + let mut acc_sm = AllocatedScalarMulAccumulator::new(); + + // Load pre-image of accumulator to be updated + let mut acc = AllocatedRelaxedR1CSInstance::alloc(cs.namespace(|| "alloc acc"), &proof.acc); + + // Compute its hash + let acc_hash_curr = acc.hash(cs.namespace(|| "acc_hash_curr"))?; + + // Create selector for acc_hash_curr and ensure it is contained in accs_hash + let accs_hash_selector = { + let bits = state + .accs_hash + .iter() + .enumerate() + .map(|(i, acc_hash)| { + // Allocate a bit which is true if i == index_prev + let bit = proof.index.map_or(false, |index_curr| index_curr == i); + let bit = AllocatedBit::alloc(cs.namespace(|| format!("alloc selector[{i}]")), Some(bit)) + .unwrap(); + + // Ensure acc_hash[index] = acc_hash_curr + cs.enforce( + || format!("bit[{i}] * (acc_hash[{i}] - acc_hash_curr) = 0"), + |lc| lc + bit.get_variable(), + |lc| lc + acc_hash.get_variable() - acc_hash_curr.get_variable(), + |lc| lc, + ); + + Boolean::Is(bit) + }) + .collect::>(); + + let bits_sum = bits.iter().fold(LinearCombination::zero(), |lc, bit| { + lc + &bit.lc(CS::one(), E::Scalar::ONE) + }); + + // Ensure only 1 selection bit is true, except in the base case where all bits are 0 + cs.enforce( + || "is_base.not = ∑_i bits[i]", + |lc| lc, + |lc| lc, + |_| is_base_case.not().lc(CS::one(), E::Scalar::ONE) - &bits_sum, + ); + + bits + }; + + // Set the R1CS IO as the transcript init followed by the state + let X_new = state_hash; + acc.fold(cs.namespace(|| "fold"), X_new, &mut acc_sm, &mut transcript)?; + let acc_hash_next = acc.hash(cs.namespace(|| "hash acc_hash_next"))?; + + // Update hashes of accumulators in state + state.accs_hash = zip_eq(&state.accs_hash, &accs_hash_selector) + .enumerate() + .map(|(i, (acc_hash, bit))| { + conditionally_select( + cs.namespace(|| format!("acc_hash_next {i}")), + &acc_hash_next, + &acc_hash, + bit, + ) + }) + .collect::, _>>()?; // Prove all scalar multiplication by updating the secondary curve accumulator - let acc_cf_next = acc_sm.finalize(cs.namespace(|| "finalize acc_sm"), &mut transcript)?; + acc_sm.finalize( + cs.namespace(|| "finalize acc_sm"), + &mut state.acc_cf, + &mut transcript, + )?; // If this is the first iteration, then reset `acc_cf` to its default state since no scalar multiplications // were actually computed - let acc_cf_next = - acc_cf_next.select_default(cs.namespace(|| "enforce trivial acc_cf"), &is_base_case)?; + state.acc_cf = state + .acc_cf + .select_default(cs.namespace(|| "enforce trivial acc_cf"), &is_base_case)?; - // Finalize transcript, and return the compressed state - let transcript_state = transcript.seal(cs.namespace(|| "transcript seal"))?; + state.transcript_state = transcript.seal(cs.namespace(|| "checkpoint"))?; - let state_next = Self { - io: state.io, - accs_hash: accs_hash_next, - acc_cf: acc_cf_next, - }; - Ok((state_next, transcript_state)) + Ok(state) } pub fn update_io( @@ -146,126 +210,81 @@ impl AllocatedNIVCState { Ok(self.io.to_native()) } - // pub fn merge( - // mut cs: CS, - // self_L: Self, - // self_R: Self, - // ro_consts: &TranscriptConstants, - // proof: NIVCMergeProof, - // transcript: &mut AllocatedTranscript, - // ) -> Result<(Self, NIVCIO), SynthesisError> - // where - // CS: ConstraintSystem, - // { - // let mut acc_sm = AllocatedScalarMulAccumulator::new(); - // - // let Self { - // io: io_L, - // accs_hash: accs_hash_L, - // acc_cf: acc_cf_L, - // } = self_L; - // let Self { - // io: io_R, - // accs_hash: accs_hash_R, - // acc_cf: acc_cf_R, - // } = self_R; - // - // let io = AllocatedNIVCIO::merge(cs.namespace(|| "io merge"), io_L, io_R); - // - // // Load the preimages of the accumulators in each state - // let (accs_L, accs_R) = { - // let accs_L = Self::load_accs( - // cs.namespace(|| "load accs_R"), - // proof.accs_L, - // accs_hash_L, - // ro_consts, - // )?; - // let accs_R = Self::load_accs( - // cs.namespace(|| "load accs_R"), - // proof.accs_R, - // accs_hash_R, - // ro_consts, - // )?; - // (accs_L, accs_R) - // }; - // - // // Merge the two lists of accumulators and return their hashes - // let accs_hash = { - // let accs = AllocatedRelaxedR1CSInstance::merge_many( - // cs.namespace(|| "accs"), - // accs_L, - // accs_R, - // &mut acc_sm, - // proof.nivc_merge_proof, - // transcript, - // )?; - // - // accs - // .into_iter() - // .map(|acc| acc.hash(cs.namespace(|| "hash acc"), ro_consts)) - // .collect::, _>>()? - // }; - // - // // Merge the secondary curve accumulators - // let acc_cf = AllocatedSecondaryRelaxedR1CSInstance::merge( - // cs.namespace(|| "merge acc_cf"), - // acc_cf_L, - // acc_cf_R, - // proof.cf_merge_proof, - // transcript, - // )?; - // - // // Prove all scalar multiplications by folding the result into the secondary curve accumulator - // let acc_cf = acc_sm.finalize( - // cs.namespace(|| "acc_sm finalize"), - // acc_cf, - // proof.sm_fold_proofs, - // transcript, - // )?; - // let state = Self { - // io, - // accs_hash, - // acc_cf, - // }; - // let io = state.io.to_native()?; - // - // Ok((state, io)) - // } - pub fn inputize(&self, mut cs: CS) -> Result<(), SynthesisError> where CS: ConstraintSystem, { - let hash = self.hash(cs.namespace(|| "hash state"))?; - hash.inputize(cs.namespace(|| "inputize hash")) + let state_hash = self.hash(cs.namespace(|| "state hash"))?; + state_hash.inputize(cs.namespace(|| "inputize state_hash"))?; + state_hash.inputize(cs.namespace(|| "HACK inputize state_hash")) } - pub fn alloc_unchecked(mut cs: CS, state: NIVCStateInstance) -> Self + pub fn merge( + mut cs: CS, + self_L: Self, + self_R: Self, + proof: NIVCMergeProof, + ro_consts: TranscriptConstants, + ) -> Result<(Self, Option>), SynthesisError> where CS: ConstraintSystem, { - let io = AllocatedNIVCIO::alloc(cs.namespace(|| "alloc io"), state.io); + let accs_L = self_L.load_accs(cs.namespace(|| "load accs_L"), &proof.accs_L)?; + let accs_R = self_R.load_accs(cs.namespace(|| "load accs_R"), &proof.accs_R)?; + + if let (Some(l), Some(r)) = ( + self_L.transcript_state.get_value(), + self_R.transcript_state.get_value(), + ) { + println!("c: {:?} {:?}", l, r); + } + let mut transcript = AllocatedTranscript::new( + ro_consts, + [self_L.transcript_state, self_R.transcript_state], + proof.transcript_buffer, + ); + + let io = AllocatedNIVCIO::merge(cs.namespace(|| "io merge"), self_L.io, self_R.io); - let accs_hash = state - .accs + let mut acc_cf = AllocatedSecondaryRelaxedR1CSInstance::merge( + cs.namespace(|| "acc_cf merge"), + self_L.acc_cf, + self_R.acc_cf, + &mut transcript, + )?; + + let mut acc_sm = AllocatedScalarMulAccumulator::new(); + // Merge the two lists of accumulators and return their hashes + + let accs = AllocatedRelaxedR1CSInstance::merge_many( + cs.namespace(|| "accs"), + accs_L, + accs_R, + &mut acc_sm, + &mut transcript, + )?; + + let accs_hash = accs .iter() - .enumerate() - .map(|(i, acc)| { - let acc_hash = acc.hash(); - AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc acc_hash {i}")), || acc_hash) - }) - .collect::>(); + .map(|acc| acc.hash(cs.namespace(|| "hash acc"))) + .collect::, _>>()?; - let acc_cf = AllocatedSecondaryRelaxedR1CSInstance::alloc_unchecked( - cs.namespace(|| "alloc acc_cf"), - state.acc_cf, - ); + // Prove all scalar multiplications by folding the result into the secondary curve accumulator + acc_sm.finalize( + cs.namespace(|| "acc_sm finalize"), + &mut acc_cf, + &mut transcript, + )?; - Self { + let state = Self { + transcript_state: transcript.seal(cs.namespace(|| "checkpoint"))?, io, accs_hash, acc_cf, - } + }; + let io = state.io.to_native(); + + Ok((state, io)) } pub fn hash(&self, mut cs: CS) -> Result, SynthesisError> @@ -284,180 +303,101 @@ impl AllocatedNIVCState { SpongeAPI::absorb(&mut sponge, num_absorbs, &elements, acc); let state_out = SpongeAPI::squeeze(&mut sponge, 1, acc); SpongeAPI::finish(&mut sponge, acc).expect("no error"); + state_out[0].ensure_allocated(acc, true) } - fn update_accs( + fn load_accs( + &self, mut cs: CS, - accs_hash: Vec>, - X_prev: [AllocatedNum; 2], - acc_prev: RelaxedR1CSInstance, - index_prev: Option, - is_base_case: &Boolean, - acc_sm: &mut AllocatedScalarMulAccumulator, - transcript: &mut AllocatedTranscript, - ) -> Result>, SynthesisError> + accs: &[RelaxedR1CSInstance], + ) -> Result>, SynthesisError> where CS: ConstraintSystem, { - let (acc_prev_hash, acc_curr_hash) = { - // Load pre-image of accumulator to be updated - let acc_prev = - AllocatedRelaxedR1CSInstance::alloc(cs.namespace(|| "alloc acc_prev"), acc_prev); - - // Compute its hash - let acc_prev_hash = acc_prev.hash(cs.namespace(|| "hash acc_prev"))?; - - // Set the R1CS IO as the transcript init followed by the state - - let acc_curr = acc_prev.fold(cs.namespace(|| "fold"), X_prev, acc_sm, transcript)?; - - let acc_curr_hash = acc_curr.hash(cs.namespace(|| "hash acc_curr"))?; - - (acc_prev_hash, acc_curr_hash) - }; + zip_eq(accs, &self.accs_hash) + .map(|(acc_native, acc_hash)| { + let acc = AllocatedRelaxedR1CSInstance::alloc(cs.namespace(|| "alloc acc"), acc_native); + let acc_hash_real = acc.hash(cs.namespace(|| "hash acc"))?; - // Create selector for acc_prev_hash and ensure it is contained in accs_hash - let accs_hash_selector = { - let bits = accs_hash - .iter() - .enumerate() - .map(|(i, acc_hash)| { - // Allocate a bit which is true if i == index_prev - let bit = index_prev.map_or(false, |index_prev| index_prev == i); - let bit = AllocatedBit::alloc(cs.namespace(|| format!("alloc selector[{i}]")), Some(bit)) - .unwrap(); - - // Ensure acc_hash[index_prev] = acc_prev_hash - cs.enforce( - || format!("bit[{i}] * (acc_hash[{i}] - acc_prev_hash[{i}]) = 0"), - |lc| lc + bit.get_variable(), - |lc| lc + acc_hash.get_variable() - acc_prev_hash.get_variable(), - |lc| lc, - ); - - Boolean::Is(bit) - }) - .collect::>(); - - // Compute sum of all bits - let lc_sum = bits.iter().fold(LinearCombination::zero(), |lc, bit| { - lc + &bit.lc(CS::one(), E::Scalar::ONE) - }); - - // Ensure only 1 selection bit is true, except in the base case where all bits are 0 - cs.enforce( - || "is_base.not = ∑_i bits[i]", - |lc| lc, - |lc| lc, - |_| is_base_case.not().lc(CS::one(), E::Scalar::ONE) - &lc_sum, - ); - - bits - }; - - // Update hashes of accumulators in state - zip_eq(accs_hash.into_iter(), accs_hash_selector.iter()) - .enumerate() - .map(|(i, (acc_hash, bit))| { - conditionally_select( - cs.namespace(|| format!("accs_hash_curr {i}")), - &acc_curr_hash, - &acc_hash, - bit, - ) + // Ensure the loaded accumulator's hash matches the one from the state + cs.enforce( + || "acc_hash_real == acc_hash", + |lc| lc, + |lc| lc, + |lc| lc + acc_hash_real.get_variable() - acc_hash.get_variable(), + ); + Ok::<_, SynthesisError>(acc) }) .collect::, _>>() } pub fn as_preimage(&self) -> impl IntoIterator> + '_ { chain![ + [Elt::Allocated(self.transcript_state.clone())], self.io.as_preimage(), self.accs_hash.iter().cloned().map(Elt::Allocated), self.acc_cf.as_preimage() ] } +} - #[allow(unused)] - pub fn eq_native(&self, other: &NIVCStateInstance) -> Option { - for (lhs, acc) in zip_eq(&self.accs_hash, &other.accs) { - let rhs = acc.hash(); - if lhs.get_value()? != rhs { - return Some(false); - } - } - - if !self.io.eq_native(&other.io)? { - return Some(false); - } +impl AllocatedNIVCIO { + pub fn alloc(mut cs: CS, state: &NIVCIO) -> Self + where + CS: ConstraintSystem, + { + let pc_in = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pc_in"), || state.pc_in); + let z_in = enumerate(&state.z_in) + .map(|(i, z)| { + AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc z_in[{i}]")), || *z) + }) + .collect(); + let pc_out = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pc_out"), || state.pc_out); + let z_out = enumerate(&state.z_out) + .map(|(i, z)| { + AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc z_out[{i}]")), || *z) + }) + .collect(); - if !self.acc_cf.eq_native(&other.acc_cf)? { - return Some(false); + Self { + pc_in, + z_in, + pc_out, + z_out, } - - Some(true) } + pub fn merge(mut cs: CS, io_L: Self, io_R: Self) -> Self + where + CS: ConstraintSystem, + { + // io_L.pc_out = io_R.pc_in + cs.enforce( + || "io_L.pc_out = io_R.pc_in", + |lc| lc, + |lc| lc, + |lc| lc + io_L.pc_out.get_variable() - io_R.pc_in.get_variable(), + ); - // fn load_accs( - // mut cs: CS, - // accs_native: Vec>, - // accs_hash: Vec>, - // ) -> Result>, SynthesisError> - // where - // CS: ConstraintSystem, - // { - // zip_eq(accs_native, accs_hash) - // .map( - // |(acc_native, acc_hash): (RelaxedR1CSInstance, AllocatedNum)| { - // let acc = AllocatedRelaxedR1CSInstance::alloc(cs.namespace(|| "alloc acc"), acc_native); - // let acc_hash_real = acc.hash(cs.namespace(|| "hash acc"))?; - // - // // Ensure the loaded accumulator's hash matches the one from the state - // cs.enforce( - // || "acc_hash_real == acc_hash", - // |lc| lc, - // |lc| lc, - // |lc| lc + acc_hash_real.get_variable() - acc_hash.get_variable(), - // ); - // Ok::<_, SynthesisError>(acc) - // }, - // ) - // .collect::, _>>() - // } -} + // io_L.z_out = io_R.z_in + zip_eq(&io_L.z_out, &io_R.z_in) + .enumerate() + .for_each(|(i, (z_L_i, z_R_i))| { + cs.enforce( + || format!("io_L.z_out[{i}] = io_R.z_in[{i}]"), + |lc| lc, + |lc| lc, + |lc| lc + z_L_i.get_variable() - z_R_i.get_variable(), + ); + }); -impl AllocatedNIVCIO { - // pub fn merge(mut cs: CS, io_L: Self, io_R: Self) -> Self - // where - // CS: ConstraintSystem, - // { - // // io_L.pc_out = io_R.pc_in - // cs.enforce( - // || "io_L.pc_out = io_R.pc_in", - // |lc| lc, - // |lc| lc, - // |lc| lc + io_L.pc_out.get_variable() - io_R.pc_in.get_variable(), - // ); - // - // // io_L.z_out = io_R.z_in - // zip_eq(&io_L.z_out, &io_R.z_in) - // .enumerate() - // .for_each(|(i, (z_L_i, z_R_i))| { - // cs.enforce( - // || format!("io_L.z_out[{i}] = io_R.z_in[{i}]"), - // |lc| lc, - // |lc| lc, - // |lc| lc + z_L_i.get_variable() - z_R_i.get_variable(), - // ); - // }); - // - // Self { - // pc_in: io_L.pc_in, - // z_in: io_L.z_in, - // pc_out: io_R.pc_out, - // z_out: io_R.z_out, - // } - // } + Self { + pc_in: io_L.pc_in, + z_in: io_L.z_in, + pc_out: io_R.pc_out, + z_out: io_R.z_out, + } + } pub fn enforce_trivial(&self, mut cs: CS, is_trivial: &Boolean) where @@ -496,42 +436,6 @@ impl AllocatedNIVCIO { .map(Elt::Allocated) } - pub fn alloc(mut cs: CS, state: NIVCIO) -> Self - where - CS: ConstraintSystem, - { - let NIVCIO { - pc_in, - z_in, - pc_out, - z_out, - } = state; - - let pc_in = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pc_in"), || pc_in); - let z_in = z_in - .into_iter() - .enumerate() - .map(|(i, z)| { - AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc z_in[{i}]")), || z) - }) - .collect(); - let pc_out = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pc_out"), || pc_out); - let z_out = z_out - .into_iter() - .enumerate() - .map(|(i, z)| { - AllocatedNum::alloc_infallible(cs.namespace(|| format!("alloc z_out[{i}]")), || z) - }) - .collect(); - - Self { - pc_in, - z_in, - pc_out, - z_out, - } - } - /// Attempt to extract the native representation. pub fn to_native(&self) -> Option> { let pc_in = self.pc_in.get_value()?; @@ -553,9 +457,4 @@ impl AllocatedNIVCIO { z_out, }) } - - pub fn eq_native(&self, other: &NIVCIO) -> Option { - let self_native = self.to_native()?; - Some(&self_native == other) - } } diff --git a/src/parafold/nivc/mod.rs b/src/parafold/nivc/mod.rs index f5a0046b9..a7c164df5 100644 --- a/src/parafold/nivc/mod.rs +++ b/src/parafold/nivc/mod.rs @@ -4,11 +4,8 @@ use neptune::generic_array::typenum::U16; use neptune::poseidon::PoseidonConstants; use crate::parafold::cycle_fold::nifs::prover::RelaxedSecondaryR1CSInstance; -use crate::parafold::cycle_fold::prover::ScalarMulAccumulator; -use crate::parafold::nifs::prover::RelaxedR1CS; use crate::parafold::nifs::RelaxedR1CSInstance; use crate::parafold::transcript::{TranscriptConstants, TranscriptElement}; -use crate::parafold::transcript::prover::Transcript; use crate::traits::{CurveCycleEquipped, Engine}; pub mod circuit; @@ -40,64 +37,78 @@ type NIVCPoseidonConstants = PoseidonConstants<::Scalar, U16>; /// Succinct representation of the recursive NIVC state that is known #[derive(Clone, Debug, PartialEq)] pub struct NIVCStateInstance { - io: NIVCIO, - accs: Vec>, - acc_cf: RelaxedSecondaryR1CSInstance, + transcript_state: E::Scalar, + pub io: NIVCIO, + pub accs_hash: Vec, + pub acc_cf: RelaxedSecondaryR1CSInstance, } -/// A proof for loading a previous NIVC output inside a circuit. #[derive(Debug, Clone)] -pub struct NIVCUpdateProof { - transcript_prev: E::Scalar, - transcript_buffer: Vec>, - - state: NIVCStateInstance, - - acc_prev: RelaxedR1CSInstance, - index_prev: Option, +pub struct NIVCCircuitInput { + pub instance: NIVCStateInstance, + pub proof: NIVCStateProof, } -impl NIVCUpdateProof { +impl NIVCCircuitInput { pub fn dummy( ro_consts: TranscriptConstants, arity: usize, - num_circuit: usize, + num_circuits: usize, ) -> Self { - let state = NIVCStateInstance::::dummy(arity, num_circuit); + let instance = NIVCStateInstance::dummy(arity, num_circuits); - let state_hash: E::Scalar = E::Scalar::ZERO; - let transcript_init: E::Scalar = E::Scalar::ZERO; + let transcript = instance.simulate_update(ro_consts); - let mut transcript = Transcript::new(ro_consts.clone(), [state_hash, transcript_init]); + let (_, transcript_buffer) = transcript.seal(); - let mut acc_sm = ScalarMulAccumulator::dummy(); - RelaxedR1CS::simulate_fold(&mut acc_sm, &mut transcript); - let _ = acc_sm.simulate_finalize(&mut transcript); + let proof = NIVCStateProof { + transcript_buffer, + acc: RelaxedR1CSInstance::dummy(), + index: None, + }; - let (_, transcript_buffer) = transcript.seal(); + Self { instance, proof } + } +} + +/// A proof for loading a previous NIVC output inside a circuit. +#[derive(Debug, Clone)] +pub struct NIVCStateProof { + pub transcript_buffer: Vec>, + pub acc: RelaxedR1CSInstance, + pub index: Option, +} + +#[derive(Debug, Clone)] +pub struct NIVCMergeProof { + pub transcript_buffer: Vec>, + pub accs_L: Vec>, + pub accs_R: Vec>, +} + +impl NIVCMergeProof { + pub fn dummy(ro_consts: TranscriptConstants, num_circuits: usize) -> Self { + let dummy_transcript = E::Scalar::ZERO; + let transcript = NIVCStateInstance::simulate_merge( + dummy_transcript, + dummy_transcript, + num_circuits, + ro_consts, + ); + let (_, transcript_buffer) = transcript.seal(); Self { - transcript_prev: transcript_init, transcript_buffer, - state, - acc_prev: RelaxedR1CSInstance::dummy(), - index_prev: None, + accs_L: vec![RelaxedR1CSInstance::::dummy(); num_circuits], + accs_R: vec![RelaxedR1CSInstance::::dummy(); num_circuits], } } } -// #[derive(Debug, Clone)] -// pub struct NIVCMergeProof { -// transcript_buffer: Vec, -// accs_L: Vec>, -// accs_R: Vec>, -// } - #[cfg(test)] mod tests { - use bellpepper_core::ConstraintSystem; use bellpepper_core::test_cs::TestConstraintSystem; - use itertools::zip_eq; + use bellpepper_core::ConstraintSystem; use crate::parafold::nivc::circuit::AllocatedNIVCState; use crate::provider::Bn256EngineKZG as E; @@ -110,15 +121,13 @@ mod tests { type CS = TestConstraintSystem; #[test] - fn test_from_proof() { + fn test_verify() { let mut cs = CS::new(); let ro_consts = TranscriptConstants::::new(); - let dummy_proof = NIVCUpdateProof::::dummy(ro_consts.clone(), 4, 4); + let dummy_input = NIVCCircuitInput::::dummy(ro_consts.clone(), 0, 1); - let _state = - AllocatedNIVCState::from_proof(cs.namespace(|| "from proof"), &ro_consts, dummy_proof) - .unwrap(); + let _state = AllocatedNIVCState::init(cs.namespace(|| "alloc"), ro_consts, &dummy_input); if !cs.is_satisfied() { println!("{:?}", cs.which_is_unsatisfied()); @@ -131,34 +140,12 @@ mod tests { let mut cs = CS::new(); let state = NIVCStateInstance::::dummy(4, 4); - let allocated_state = - AllocatedNIVCState::::alloc_unchecked(cs.namespace(|| "alloc state"), state.clone()); + let (allocated_state, _) = + AllocatedNIVCState::::alloc_unverified(cs.namespace(|| "alloc state"), &state); let state_hash = state.hash(); let allocated_state_hash = allocated_state.hash(cs.namespace(|| "hash")).unwrap(); - let state_field = state - .as_preimage() - .into_iter() - .map(|x| x.to_field()) - .flatten() - .collect::>(); - let allocated_state_field = allocated_state - .as_preimage() - .into_iter() - .enumerate() - .map(|(i, x)| { - x.ensure_allocated(&mut cs.namespace(|| format!("alloc x[{i}]")), true) - .unwrap() - }) - .collect::>(); - - assert_eq!(state_field.len(), allocated_state_field.len()); - - for (_i, (x, allocated_x)) in zip_eq(&state_field, &allocated_state_field).enumerate() { - assert_eq!(*x, allocated_x.get_value().unwrap()); - } - assert_eq!(state_hash, allocated_state_hash.get_value().unwrap()); if !cs.is_satisfied() { diff --git a/src/parafold/nivc/prover.rs b/src/parafold/nivc/prover.rs index 7ff89be26..0e367512d 100644 --- a/src/parafold/nivc/prover.rs +++ b/src/parafold/nivc/prover.rs @@ -2,226 +2,171 @@ use ff::Field; use itertools::{chain, zip_eq}; use neptune::hash_type::HashType; use neptune::sponge::api::{IOPattern, SpongeAPI, SpongeOp}; -use neptune::sponge::vanilla::{Sponge, SpongeTrait}; use neptune::sponge::vanilla::Mode::Simplex; +use neptune::sponge::vanilla::{Sponge, SpongeTrait}; use neptune::Strength; -use crate::CommitmentKey; +use crate::errors::NovaError; use crate::parafold::cycle_fold::nifs::prover::{ RelaxedSecondaryR1CS, RelaxedSecondaryR1CSInstance, }; use crate::parafold::cycle_fold::prover::ScalarMulAccumulator; use crate::parafold::nifs::prover::RelaxedR1CS; use crate::parafold::nifs::RelaxedR1CSInstance; -use crate::parafold::nivc::{NIVCIO, NIVCPoseidonConstants, NIVCStateInstance, NIVCUpdateProof}; -use crate::parafold::transcript::{TranscriptConstants, TranscriptElement}; +use crate::parafold::nivc::{ + NIVCCircuitInput, NIVCMergeProof, NIVCPoseidonConstants, NIVCStateInstance, NIVCStateProof, + NIVCIO, +}; +use crate::parafold::prover::{NIVCUpdateWitness, ProvingKey}; use crate::parafold::transcript::prover::Transcript; +use crate::parafold::transcript::{TranscriptConstants, TranscriptElement}; use crate::r1cs::R1CSShape; use crate::supernova::error::SuperNovaError; use crate::traits::CurveCycleEquipped; +use crate::CommitmentKey; #[derive(Debug)] pub struct NIVCState { - pub(crate) transcript_state: E::Scalar, - pub(crate) io: NIVCIO, + transcript_state: E::Scalar, + io: NIVCIO, accs: Vec>, acc_cf: RelaxedSecondaryR1CS, } -#[derive(Debug)] -pub struct NIVCUpdateWitness { - pub(crate) index: usize, - pub(crate) W: Vec, -} - impl NIVCState { - /// Initialize the prover state and create a default proof for the first iteration. - /// - /// # Details - /// - /// In the first iteration, the circuit verifier checks the base-case conditions, but does not update any - /// of the accumulators. To ensure uniformity with the non-base case path, the transcript will be updated - /// in the normal way, albeit with dummy proof data. - pub fn new( - shapes: &[R1CSShape], - shape_cf: &R1CSShape, - pc_init: usize, - z_init: Vec, - ro_consts: &TranscriptConstants, - ) -> (Self, NIVCUpdateProof) { - let transcript_init: E::Scalar = E::Scalar::ZERO; - let io = NIVCIO::new(pc_init, z_init); - let accs = shapes - .iter() - .map(|shape| RelaxedR1CS::new(shape)) - .collect::>(); - let acc_cf = RelaxedSecondaryR1CS::new(shape_cf); - - let state_instance = NIVCStateInstance::new( - io.clone(), - accs.iter().map(|acc| acc.instance().clone()).collect(), - acc_cf.instance().clone(), - ); - - let state_hash = state_instance.hash(); - - let mut transcript = Transcript::new(ro_consts.clone(), [state_hash, transcript_init]); - - let mut acc_sm = ScalarMulAccumulator::dummy(); - RelaxedR1CS::simulate_fold(&mut acc_sm, &mut transcript); - let _ = acc_sm.simulate_finalize(&mut transcript); - - let (transcript_state, transcript_buffer) = transcript.seal(); - - let state = Self { - transcript_state, + pub fn new(io: NIVCIO, accs: Vec>, acc_cf: RelaxedSecondaryR1CS) -> Self { + Self { + transcript_state: E::Scalar::ZERO, io, accs, acc_cf, - }; - let proof = NIVCUpdateProof { - transcript_prev: transcript_init, + } + } + + pub fn init(&mut self, ro_consts: TranscriptConstants) -> NIVCCircuitInput { + let instance = self.instance(); + let transcript = instance.simulate_update(ro_consts); + let (transcript_state, transcript_buffer) = transcript.seal(); + self.transcript_state = transcript_state; + + let proof = NIVCStateProof { transcript_buffer, - state: state_instance, - acc_prev: RelaxedR1CSInstance::::dummy(), - index_prev: None, + acc: RelaxedR1CSInstance::dummy(), + index: None, }; - (state, proof) + NIVCCircuitInput { instance, proof } } pub fn update( - self, - ck: &CommitmentKey, - ck_cf: &CommitmentKey, - ro_consts: &TranscriptConstants, - shapes: &[R1CSShape], - shape_cf: &R1CSShape, - witness_prev: &NIVCUpdateWitness, - ) -> (Self, NIVCUpdateProof) { - let state_instance_prev = self.instance(); - - let Self { - transcript_state: transcript_prev, - io, - mut accs, - acc_cf, - } = self; + &mut self, + pk: &ProvingKey, + witness: &NIVCUpdateWitness, + ) -> Result, NovaError> { + self.io.update(witness.io_next.clone()); - let state_hash_prev = state_instance_prev.hash(); - let X_prev = [state_hash_prev, transcript_prev]; + let instance = self.instance(); - let mut transcript = Transcript::new(ro_consts.clone(), X_prev.clone()); + let state_hash = instance.hash(); - let mut acc_sm = ScalarMulAccumulator::new(acc_cf); + assert_eq!(state_hash, witness.X); - let NIVCUpdateWitness { - index: index_prev, - W: W_prev, - } = witness_prev; + let mut transcript = Transcript::new(pk.ro_consts.clone(), [state_hash]); - let index_prev = *index_prev; + let mut acc_sm = ScalarMulAccumulator::new(); - let acc_prev = accs[index_prev].instance().clone(); + let index_prev = witness.index; + let acc_prev = &mut self.accs[index_prev]; + let acc_prev_instance = acc_prev.instance().clone(); - let shape_prev = &shapes[index_prev]; + let shape_prev = &pk.shapes[index_prev]; // Fold the proof for the previous iteration into the correct accumulator - accs[index_prev].fold(ck, shape_prev, X_prev, W_prev, &mut acc_sm, &mut transcript); - let acc_cf = acc_sm.finalize(ck_cf, shape_cf, &mut transcript).unwrap(); + let X_prev = state_hash; + acc_prev.fold( + &pk.ck, + shape_prev, + X_prev, + &witness.W, + &mut acc_sm, + &mut transcript, + ); + acc_sm.finalize(&pk.ck_cf, &pk.shape_cf, &mut self.acc_cf, &mut transcript)?; let (transcript_state, transcript_buffer) = transcript.seal(); + self.transcript_state = transcript_state; - let proof = NIVCUpdateProof { - transcript_prev, + let proof = NIVCStateProof { transcript_buffer, - state: state_instance_prev, - acc_prev, - index_prev: Some(index_prev), + acc: acc_prev_instance, + index: Some(index_prev), }; - let state = Self { + + Ok(NIVCCircuitInput { instance, proof }) + } + + pub fn merge(pk: &ProvingKey, self_L: Self, self_R: Self) -> (Self, NIVCMergeProof) { + println!( + "p: {:?} {:?}", + self_L.transcript_state, self_R.transcript_state + ); + let mut transcript = Transcript::new( + pk.ro_consts.clone(), + [self_L.transcript_state, self_R.transcript_state], + ); + + let io = NIVCIO::merge(self_L.io, self_R.io); + + let mut acc_cf = RelaxedSecondaryR1CS::::merge( + &pk.ck_cf, + &pk.shape_cf, + self_L.acc_cf, + self_R.acc_cf, + &mut transcript, + ); + + let mut acc_sm = ScalarMulAccumulator::new(); + + let accs_L_instance = self_L + .accs + .iter() + .map(|acc| acc.instance().clone()) + .collect(); + let accs_R_instance = self_R + .accs + .iter() + .map(|acc| acc.instance().clone()) + .collect(); + + let accs = RelaxedR1CS::::merge_many( + &pk.ck, + &pk.shapes, + self_L.accs, + &self_R.accs, + &mut acc_sm, + &mut transcript, + ); + + acc_sm + .finalize(&pk.ck_cf, &pk.shape_cf, &mut acc_cf, &mut transcript) + .unwrap(); + + let (transcript_state, transcript_buffer) = transcript.seal(); + + let self_next = Self { transcript_state, io, accs, acc_cf, }; - (state, proof) - } - // pub fn merge( - // ck: &CommitmentKey, - // ck_cf: &CommitmentKey, - // shapes: &[R1CSShape], - // shape_cf: &R1CSShape, - // self_L: Self, - // self_R: &Self, - // ) -> (Self, NIVCMergeProof) { - // let Self { - // transcript: transcript_L, - // io: io_L, - // accs: accs_L, - // acc_cf: acc_cf_L, - // } = self_L; - // let Self { - // transcript: transcript_R, - // io: io_R, - // accs: accs_R, - // acc_cf: acc_cf_R, - // } = self_R; - // - // let mut acc_sm = ScalarMulAccumulator::new(); - // let mut transcript = Transcript::merge(transcript_L, transcript_R); - // - // let io = NIVCIO::merge(io_L, io_R.clone()); - // - // let accs_L_instance = accs_L - // .iter() - // .map(|acc| acc.instance()) - // .cloned() - // .collect::>(); - // let accs_R_instance = accs_R - // .iter() - // .map(|acc| acc.instance()) - // .cloned() - // .collect::>(); - // - // let (accs, nivc_merge_proof) = - // RelaxedR1CS::merge_many(ck, shapes, accs_L, accs_R, &mut acc_sm, &mut transcript); - // - // let (mut acc_cf, cf_merge_proof) = RelaxedR1CS::::merge_secondary::( - // ck_cf, - // shape_cf, - // acc_cf_L, - // acc_cf_R, - // &mut transcript, - // ); - // - // let sm_fold_proofs = acc_sm.finalize(ck_cf, shape_cf, &mut acc_cf, &mut transcript); - // - // let self_next = Self { - // transcript, - // io, - // accs, - // acc_cf, - // }; - // - // let merge_proof = NIVCMergeProof { - // accs_L: accs_L_instance, - // accs_R: accs_R_instance, - // nivc_merge_proof, - // cf_merge_proof, - // sm_fold_proofs, - // }; - // - // (self_next, merge_proof) - // } + let merge_proof = NIVCMergeProof { + transcript_buffer, + accs_L: accs_L_instance, + accs_R: accs_R_instance, + }; - pub fn instance(&self) -> NIVCStateInstance { - NIVCStateInstance { - io: self.io.clone(), - accs: self.accs.iter().map(|acc| acc.instance().clone()).collect(), - acc_cf: self.acc_cf.instance().clone(), - } + (self_next, merge_proof) } pub fn verify( @@ -237,32 +182,66 @@ impl NIVCState { self.acc_cf.verify(ck_cf, shape_cf)?; Ok(()) } + + pub fn instance(&self) -> NIVCStateInstance { + NIVCStateInstance { + transcript_state: self.transcript_state, + io: self.io.clone(), + accs_hash: self.accs.iter().map(|acc| acc.instance().hash()).collect(), + acc_cf: self.acc_cf.instance().clone(), + } + } } impl NIVCStateInstance { - pub fn new( - io: NIVCIO, - accs: Vec>, - acc_cf: RelaxedSecondaryR1CSInstance, - ) -> Self { - Self { io, accs, acc_cf } + pub fn simulate_update(&self, ro_consts: TranscriptConstants) -> Transcript { + let state_hash = self.hash(); + + let mut transcript = Transcript::new(ro_consts, [state_hash]); + + let mut acc_sm = ScalarMulAccumulator::new(); + RelaxedR1CS::simulate_fold(&mut acc_sm, &mut transcript); + let _ = acc_sm.simulate_finalize(&mut transcript); + transcript + } + + pub fn simulate_merge( + transcript_L: E::Scalar, + transcript_R: E::Scalar, + num_circuits: usize, + ro_consts: TranscriptConstants, + ) -> Transcript { + let mut transcript = Transcript::new(ro_consts.clone(), [transcript_L, transcript_R]); + + let mut acc_sm = ScalarMulAccumulator::new(); + RelaxedSecondaryR1CS::simulate_merge(&mut transcript); + RelaxedR1CS::simulate_merge_many(num_circuits, &mut acc_sm, &mut transcript); + let _ = acc_sm.simulate_finalize(&mut transcript); + transcript } pub fn dummy(arity: usize, num_circuit: usize) -> Self { Self { + transcript_state: E::Scalar::ZERO, io: NIVCIO::dummy(arity), - accs: vec![RelaxedR1CSInstance::dummy(); num_circuit], + accs_hash: vec![E::Scalar::ZERO; num_circuit], acc_cf: RelaxedSecondaryR1CSInstance::dummy(), } } pub fn hash(&self) -> E::Scalar { - let elements = self - .as_preimage() - .into_iter() - .map(|x| x.to_field()) - .flatten() - .collect::>(); + let elements = chain![ + [TranscriptElement::Scalar(self.transcript_state)], + self.io.as_preimage(), + self + .accs_hash + .iter() + .map(|acc_hash| TranscriptElement::Scalar(*acc_hash)), + self.acc_cf.as_preimage() + ] + .map(|x| x.to_field()) + .flatten() + .collect::>(); let num_absorbs = elements.len() as u32; let constants = @@ -277,14 +256,6 @@ impl NIVCStateInstance { SpongeAPI::finish(&mut sponge, acc).expect("no error"); hash[0] } - - pub fn as_preimage(&self) -> impl IntoIterator> + '_ { - let accs_hash = self - .accs - .iter() - .map(|acc| TranscriptElement::Scalar(acc.hash())); - chain![self.io.as_preimage(), accs_hash, self.acc_cf.as_preimage()] - } } impl NIVCIO { @@ -313,16 +284,16 @@ impl NIVCIO { self.z_out = io_next.z_out; } - // pub fn merge(self_L: Self, self_R: Self) -> Self { - // assert_eq!(self_L.pc_out, self_R.pc_in); - // assert_eq!(self_L.z_out, self_R.z_in); - // Self { - // pc_in: self_L.pc_in, - // z_in: self_L.z_in, - // pc_out: self_R.pc_out, - // z_out: self_R.z_out, - // } - // } + pub fn merge(self_L: Self, self_R: Self) -> Self { + assert_eq!(self_L.pc_out, self_R.pc_in); + assert_eq!(self_L.z_out, self_R.z_in); + Self { + pc_in: self_L.pc_in, + z_in: self_L.z_in, + pc_out: self_R.pc_out, + z_out: self_R.z_out, + } + } pub fn as_preimage(&self) -> impl IntoIterator> + '_ { chain![ diff --git a/src/parafold/prover.rs b/src/parafold/prover.rs index 7c045fb46..3126c1c6b 100644 --- a/src/parafold/prover.rs +++ b/src/parafold/prover.rs @@ -9,10 +9,13 @@ use crate::bellpepper::r1cs::NovaShape; use crate::bellpepper::shape_cs::ShapeCS; use crate::bellpepper::solver::SatisfyingAssignment; use crate::CommitmentKey; -use crate::parafold::circuit::synthesize_step; +use crate::errors::NovaError; +use crate::parafold::circuit::{synthesize_merge, synthesize_step}; +use crate::parafold::cycle_fold::nifs::prover::RelaxedSecondaryR1CS; use crate::parafold::cycle_fold::prover::ScalarMulInstance; -use crate::parafold::nivc::NIVCUpdateProof; -use crate::parafold::nivc::prover::{NIVCState, NIVCUpdateWitness}; +use crate::parafold::nifs::prover::RelaxedR1CS; +use crate::parafold::nivc::{NIVCCircuitInput, NIVCIO, NIVCMergeProof}; +use crate::parafold::nivc::prover::NIVCState; use crate::parafold::transcript::TranscriptConstants; use crate::r1cs::{commitment_key_size, CommitmentKeyHint, R1CSShape}; use crate::supernova::{NonUniformCircuit, StepCircuit}; @@ -21,13 +24,13 @@ use crate::traits::commitment::CommitmentEngineTrait; pub struct ProvingKey { // public params - ck: Arc>, - ck_cf: Arc>, + pub(crate) ck: Arc>, + pub(crate) ck_cf: Arc>, // Shapes for each augmented StepCircuit. The last shape is for the merge circuit. - arity: usize, - shapes: Vec>, - shape_cf: R1CSShape, - ro_consts: TranscriptConstants, + pub(crate) arity: usize, + pub(crate) shapes: Vec>, + pub(crate) shape_cf: R1CSShape, + pub(crate) ro_consts: TranscriptConstants, } impl ProvingKey { @@ -38,19 +41,21 @@ impl ProvingKey { ) -> Self { let num_circuits_nivc = non_uniform_circuit.num_circuits(); // total number of circuits also contains merge circuit. - let num_circuits = num_circuits_nivc; // +1 when supporting merge + let num_circuits = num_circuits_nivc + 1; let transcript_constants = TranscriptConstants::::new_with_strength_and_type( Strength::Standard, HashType::Sponge, ); let arity = non_uniform_circuit.primary_circuit(0).arity(); - let default_proof = - NIVCUpdateProof::::dummy(transcript_constants.clone(), arity, num_circuits); - let circuits = (0..num_circuits) + let dummy_input = + NIVCCircuitInput::::dummy(transcript_constants.clone(), arity, num_circuits); + let dummy_merge_proof = NIVCMergeProof::::dummy(transcript_constants.clone(), num_circuits); + + let circuits = (0..num_circuits_nivc) .map(|i| non_uniform_circuit.primary_circuit(i)) .collect::>(); - let shapes = circuits + let mut shapes = circuits .into_par_iter() .map(|circuit| { assert_eq!(circuit.arity(), arity); @@ -58,8 +63,8 @@ impl ProvingKey { let mut cs: ShapeCS = ShapeCS::new(); let _ = synthesize_step( &mut cs, - &transcript_constants, - default_proof.clone(), + &dummy_input, + transcript_constants.clone(), &circuit, ) .unwrap(); @@ -67,6 +72,19 @@ impl ProvingKey { cs.r1cs_shape() }) .collect::>(); + let shape_merge = { + let mut cs: ShapeCS = ShapeCS::new(); + let _ = synthesize_merge( + &mut cs, + &dummy_input, + &dummy_input, + dummy_merge_proof, + transcript_constants.clone(), + ) + .unwrap(); + cs.r1cs_shape() + }; + shapes.push(shape_merge); let shape_cf = { let mut cs: ShapeCS = ShapeCS::new(); @@ -101,54 +119,78 @@ impl ProvingKey { } } +#[derive(Debug)] pub struct RecursiveSNARK { - // state state: NIVCState, - proof: NIVCUpdateProof, + + circuit_input: NIVCCircuitInput, +} + +#[derive(Debug)] +pub struct NIVCUpdateWitness { + pub(crate) index: usize, + pub(crate) X: E::Scalar, + pub(crate) W: Vec, + pub(crate) io_next: NIVCIO, } impl RecursiveSNARK { + /// Initialize the prover state and create a default proof for the first iteration. + /// + /// # Details + /// + /// In the first iteration, the circuit verifier checks the base-case conditions, but does not update any + /// of the accumulators. To ensure uniformity with the non-base case path, the transcript will be updated + /// in the normal way, albeit with dummy proof data. pub fn new(pk: &ProvingKey, pc_init: usize, z_init: Vec) -> Self { let num_circuits = pk.shapes.len(); assert!(pc_init < num_circuits); assert_eq!(z_init.len(), pk.arity); - let (state, proof) = NIVCState::new(&pk.shapes, &pk.shape_cf, pc_init, z_init, &pk.ro_consts); + let io = NIVCIO::new(pc_init, z_init); + let accs = pk.shapes.iter().map(RelaxedR1CS::new).collect(); + let acc_cf = RelaxedSecondaryR1CS::new(&pk.shape_cf); + + let mut state = NIVCState::new(io, accs, acc_cf); - Self { state, proof } + let circuit_input = state.init(pk.ro_consts.clone()); + + Self { + state, + circuit_input, + } } - pub fn prove_step>(self, pk: &ProvingKey, step_circuit: &C) -> Self { - let Self { mut state, proof } = self; + pub fn prove_step>( + &mut self, + pk: &ProvingKey, + step_circuit: &C, + ) -> Result<(), NovaError> { let circuit_index = step_circuit.circuit_index(); + let mut cs = SatisfyingAssignment::::new(); - let (io_next, alloc_state) = - synthesize_step(&mut cs, &pk.ro_consts, proof, step_circuit).unwrap(); + let io_next = synthesize_step( + &mut cs, + &self.circuit_input, + pk.ro_consts.clone(), + step_circuit, + )? + .unwrap(); let (X, W) = cs.to_assignments(); - - state.io.update(io_next.unwrap()); - let state_instance = state.instance(); - debug_assert!(alloc_state.eq_native(&state_instance).unwrap()); - - debug_assert_eq!(state_instance.hash(), X[1]); - debug_assert_eq!(state.transcript_state, X[2]); + assert_eq!(X.len(), 3); + assert_eq!(X[1], X[2]); // TOD HACK IO needs to be even let witness = NIVCUpdateWitness { index: circuit_index, - W: W.to_vec(), + X: X[1], + W, + io_next, }; - let (state, proof) = state.update( - &pk.ck, - &pk.ck_cf, - &pk.ro_consts, - &pk.shapes, - &pk.shape_cf, - &witness, - ); + self.circuit_input = self.state.update(pk, &witness)?; - Self { state, proof } + Ok(()) } pub fn verify(&self, pk: &ProvingKey) -> bool { @@ -159,55 +201,39 @@ impl RecursiveSNARK { true } - // pub fn merge>( - // pk: &ProvingKey, - // self_L: Self, - // self_R: &Self, - // ) -> Self { - // let Self { - // state: state_L, - // proof: proof_L, - // } = self_L; - // let Self { - // state: state_R, - // proof: proof_R, - // } = self_R; - // - // let mut transcript = Transcript::new(); - // - // let (state, proof) = NIVCState::merge( - // &pk.ck, - // &pk.shapes, - // state_L, - // state_R, - // proof_L, - // proof_R.clone(), - // &mut transcript, - // ); - // - // let circuit_index = pk.shapes.len(); - // let mut cs = SatisfyingAssignment::::new(); - // let state_instance = AllocatedNIVCState::new_merge(&mut cs, &pk.nivc_hasher, proof).unwrap(); - // let W = cs.aux_assignment(); - // // assert state_instance == state.instance - // let witness = NIVCUpdateWitness { - // state: state_instance, - // index: circuit_index, - // W: W.to_vec(), - // }; - // - // let mut transcript = Transcript::new(); - // - // let proof = state.update( - // &pk.ck, - // &pk.shapes, - // &pk.nivc_hasher, - // &witness, - // &mut transcript, - // ); - // - // Self { state, proof } - // } + pub fn merge(pk: &ProvingKey, self_L: Self, self_R: Self) -> Result { + let (mut state, proof) = NIVCState::merge(pk, self_L.state, self_R.state); + + let mut cs = SatisfyingAssignment::::new(); + let io = synthesize_merge( + &mut cs, + &self_L.circuit_input, + &self_R.circuit_input, + proof, + pk.ro_consts.clone(), + )? + .unwrap(); + + let (X, W) = cs.to_assignments(); + assert_eq!(X.len(), 3); + assert_eq!(X[1], X[2]); // TOD HACK IO needs to be even + + let index = pk.shapes.len() - 1; + + let witness = NIVCUpdateWitness { + index, + X: X[1], + W, + io_next: io, + }; + + let circuit_input = state.update(pk, &witness)?; + + Ok(Self { + state, + circuit_input, + }) + } } #[cfg(test)] @@ -218,7 +244,7 @@ mod tests { use bellpepper_core::num::AllocatedNum; use bellpepper_core::test_cs::TestConstraintSystem; use expect_test::expect; - use ff::PrimeField; + use ff::{Field, PrimeField}; use crate::provider::Bn256EngineKZG as E; use crate::traits::Engine; @@ -228,8 +254,6 @@ mod tests { type Scalar = ::Scalar; - type CS = TestConstraintSystem; - #[derive(Clone, Debug)] struct TrivialCircuit { index: usize, @@ -239,7 +263,7 @@ mod tests { impl StepCircuit for TrivialCircuit { fn arity(&self) -> usize { - 0 + 1 } fn circuit_index(&self) -> usize { @@ -250,12 +274,17 @@ mod tests { &self, cs: &mut CS, _pc: Option<&AllocatedNum>, - _z: &[AllocatedNum], + z: &[AllocatedNum], ) -> Result<(Option>, Vec>), SynthesisError> { let pc_next = AllocatedNum::alloc_infallible(cs.namespace(|| "alloc pc"), || { F::from(self.pc_next as u64) }); - Ok((Some(pc_next), vec![])) + + let z_next = AllocatedNum::alloc(cs.namespace(|| "alloc z_next"), || { + let z_next = z[0].get_value().unwrap_or_default(); + Ok(z_next + F::ONE) + })?; + Ok((Some(pc_next), vec![z_next])) } } @@ -300,16 +329,16 @@ mod tests { let circuit = nc.primary_circuit(0); let ro_consts = TranscriptConstants::::new(); - let proof = NIVCUpdateProof::::dummy(ro_consts.clone(), circuit.arity(), num_circuits); + let inputs = NIVCCircuitInput::::dummy(ro_consts.clone(), circuit.arity(), num_circuits); let mut cs = TestConstraintSystem::::new(); - let _ = synthesize_step(&mut cs, &ro_consts, proof.clone(), &circuit).unwrap(); + let _ = synthesize_step(&mut cs, &inputs, ro_consts, &circuit).unwrap(); if !cs.is_satisfied() { println!("{:?}", cs.which_is_unsatisfied().unwrap()); } - expect![["18752"]].assert_eq(&cs.num_constraints().to_string()); + expect!["18682"].assert_eq(&cs.num_constraints().to_string()); } #[test] @@ -322,15 +351,64 @@ mod tests { let pk = ProvingKey::::setup(&nc, &*default_ck_hint(), &*default_ck_hint()); let pc_init = 0; - let z_init = vec![]; + let z_init = vec![Scalar::ZERO]; println!("NEW"); let mut snark = RecursiveSNARK::new(&pk, pc_init, z_init); - for i in 0..5 { + for i in 0..3 { println!("{i}"); - snark = snark.prove_step(&pk, &nc.primary_circuit(i % num_circuits)); + snark + .prove_step(&pk, &nc.primary_circuit(i % num_circuits)) + .unwrap(); } assert!(snark.verify(&pk)) } + + #[test] + fn test_prove_merge() { + let num_circuits: usize = 2; + let nc = TrivialNonUniform:: { + num_circuits, + _marker: Default::default(), + }; + let pk = ProvingKey::::setup(&nc, &*default_ck_hint(), &*default_ck_hint()); + + let pc_init_L = 0; + let z_init_L = vec![Scalar::from(0)]; + let pc_init_R = 1; + let z_init_R = vec![Scalar::from(1)]; + + println!("NEW"); + let mut snark_L = RecursiveSNARK::new(&pk, pc_init_L, z_init_L); + let mut snark_R = RecursiveSNARK::new(&pk, pc_init_R, z_init_R); + + snark_L.prove_step(&pk, &nc.primary_circuit(0)).unwrap(); + snark_R.prove_step(&pk, &nc.primary_circuit(1)).unwrap(); + + assert!(snark_L.verify(&pk)); + assert!(snark_R.verify(&pk)); + let snark = RecursiveSNARK::merge(&pk, snark_L, snark_R).unwrap(); + assert!(snark.verify(&pk)); + } + + #[test] + fn test_merge() { + let num_circuits: usize = 1; + let nc = TrivialNonUniform:: { + num_circuits, + _marker: Default::default(), + }; + let pk = ProvingKey::::setup(&nc, &*default_ck_hint(), &*default_ck_hint()); + + let pc_init = 0; + let z_init = vec![Scalar::from(0)]; + + println!("NEW"); + let snark_L = RecursiveSNARK::new(&pk, pc_init, z_init.clone()); + let snark_R = RecursiveSNARK::new(&pk, pc_init, z_init.clone()); + + let snark = RecursiveSNARK::merge(&pk, snark_L, snark_R).unwrap(); + assert!(snark.verify(&pk)); + } } diff --git a/src/parafold/transcript/circuit.rs b/src/parafold/transcript/circuit.rs index a43eb333d..246b3dcef 100644 --- a/src/parafold/transcript/circuit.rs +++ b/src/parafold/transcript/circuit.rs @@ -1,7 +1,6 @@ use bellpepper_core::{ConstraintSystem, SynthesisError}; use bellpepper_core::boolean::Boolean; use bellpepper_core::num::AllocatedNum; -use itertools::chain; use neptune::circuit2::Elt; use neptune::sponge::api::{IOPattern, SpongeAPI, SpongeOp}; use neptune::sponge::circuit::SpongeCircuit; @@ -16,8 +15,6 @@ use crate::traits::CurveCycleEquipped; pub struct AllocatedTranscript { constants: TranscriptConstants, - // Output challenge of the previous round - prev: Option>, // Elements to be hashed in this round state: Vec>, @@ -32,10 +29,10 @@ impl AllocatedTranscript { init: impl IntoIterator>, buffer: Vec>, ) -> Self { + let state = Vec::from_iter(init.into_iter().map(Elt::Allocated)); Self { constants, - prev: None, - state: Vec::from_iter(init.into_iter().map(Elt::Allocated)), + state, buffer: buffer.into_iter(), } } @@ -114,11 +111,7 @@ impl AllocatedTranscript { where CS: ConstraintSystem, { - let elements = chain!( - self.prev.iter().cloned().map(Elt::Allocated), - self.state.drain(..) - ) - .collect::>(); + let elements = self.state.drain(..).collect::>(); let num_absorbs = elements.len() as u32; @@ -135,7 +128,8 @@ impl AllocatedTranscript { state_out[0].ensure_allocated(acc, true)? }; - self.prev = Some(hash.clone()); + + self.state.push(Elt::Allocated(hash.clone())); Ok(hash) } @@ -168,10 +162,14 @@ impl AllocatedTranscript { } // Absorb the remaining elements into the sponge - if !self.state.is_empty() { - let _ = self.squeeze(&mut cs)?; + if self.state.len() == 1 { + return self + .state + .first() + .expect("state can never be empty") + .ensure_allocated(&mut cs.namespace(|| "alloc challenge"), true); } - Ok(self.prev.unwrap()) + self.squeeze(cs.namespace(|| "squeeze")) } pub(in crate::parafold::transcript) fn state(&self) -> Vec> {