mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-28 14:56:40 +01:00
Traits for witnesses and committed instances (#157)
* Add traits for witness and committed instance
* Implement witness and committed instance traits for Nova and HyperNova
* Implement witness and committed instance traits for ProtoGalaxy
* Improve the clarity of docs for `Witness{Var}Ext::get_openings`
* Avoid cloning `z_i`
* Fix grammar issues
* Rename `Ext` traits for committed instances and witnesses to `Ops`
* Implement `to_sponge_bytes`
This commit is contained in:
@@ -21,7 +21,6 @@ use ark_std::{fmt::Debug, One, Zero};
|
||||
use core::{borrow::Borrow, marker::PhantomData};
|
||||
|
||||
use super::{CommittedInstance, NovaCycleFoldConfig};
|
||||
use crate::constants::NOVA_N_BITS_RO;
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{
|
||||
CycleFoldChallengeGadget, CycleFoldCommittedInstance, CycleFoldCommittedInstanceVar,
|
||||
@@ -32,15 +31,13 @@ use crate::folding::circuits::{
|
||||
};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::transcript::{AbsorbNonNativeGadget, Transcript, TranscriptVar};
|
||||
use crate::{constants::NOVA_N_BITS_RO, folding::traits::CommittedInstanceVarOps};
|
||||
|
||||
/// CommittedInstanceVar contains the u, x, cmE and cmW values which are folded on the main Nova
|
||||
/// constraints field (E1::Fr, where E1 is the main curve). The peculiarity is that cmE and cmW are
|
||||
/// represented non-natively over the constraint field.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommittedInstanceVar<C: CurveGroup>
|
||||
where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
pub struct CommittedInstanceVar<C: CurveGroup> {
|
||||
pub u: FpVar<C::ScalarField>,
|
||||
pub x: Vec<FpVar<C::ScalarField>>,
|
||||
pub cmE: NonNativeAffineVar<C>,
|
||||
@@ -50,7 +47,6 @@ where
|
||||
impl<C> AllocVar<CommittedInstance<C>, CF1<C>> for CommittedInstanceVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<CommittedInstance<C>>>(
|
||||
cs: impl Into<Namespace<CF1<C>>>,
|
||||
@@ -80,7 +76,7 @@ where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
fn to_sponge_bytes(&self) -> Result<Vec<UInt8<C::ScalarField>>, SynthesisError> {
|
||||
unimplemented!()
|
||||
FpVar::batch_to_sponge_bytes(&self.to_sponge_field_elements()?)
|
||||
}
|
||||
|
||||
fn to_sponge_field_elements(&self) -> Result<Vec<FpVar<C::ScalarField>>, SynthesisError> {
|
||||
@@ -94,35 +90,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> CommittedInstanceVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
/// hash implements the committed instance hash compatible with the native implementation from
|
||||
/// CommittedInstance.hash.
|
||||
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U` is the
|
||||
/// `CommittedInstance`.
|
||||
/// Additionally it returns the vector of the field elements from the self parameters, so they
|
||||
/// can be reused in other gadgets avoiding recalculating (reconstraining) them.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn hash<S: CryptographicSponge, T: TranscriptVar<CF1<C>, S>>(
|
||||
self,
|
||||
sponge: &T,
|
||||
pp_hash: FpVar<CF1<C>>,
|
||||
i: FpVar<CF1<C>>,
|
||||
z_0: Vec<FpVar<CF1<C>>>,
|
||||
z_i: Vec<FpVar<CF1<C>>>,
|
||||
) -> Result<(FpVar<CF1<C>>, Vec<FpVar<CF1<C>>>), SynthesisError> {
|
||||
let mut sponge = sponge.clone();
|
||||
let U_vec = self.to_sponge_field_elements()?;
|
||||
sponge.absorb(&pp_hash)?;
|
||||
sponge.absorb(&i)?;
|
||||
sponge.absorb(&z_0)?;
|
||||
sponge.absorb(&z_i)?;
|
||||
sponge.absorb(&U_vec)?;
|
||||
Ok((sponge.squeeze_field_elements(1)?.pop().unwrap(), U_vec))
|
||||
impl<C: CurveGroup> CommittedInstanceVarOps<C> for CommittedInstanceVar<C> {
|
||||
type PointVar = NonNativeAffineVar<C>;
|
||||
|
||||
fn get_commitments(&self) -> Vec<Self::PointVar> {
|
||||
vec![self.cmW.clone(), self.cmE.clone()]
|
||||
}
|
||||
|
||||
fn get_public_inputs(&self) -> &[FpVar<CF1<C>>] {
|
||||
&self.x
|
||||
}
|
||||
|
||||
fn enforce_incoming(&self) -> Result<(), SynthesisError> {
|
||||
let zero = NonNativeUintVar::new_constant(ConstraintSystemRef::None, CF2::<C>::zero())?;
|
||||
self.cmE.x.enforce_equal_unaligned(&zero)?;
|
||||
self.cmE.y.enforce_equal_unaligned(&zero)?;
|
||||
self.u.enforce_equal(&FpVar::one())
|
||||
}
|
||||
|
||||
fn enforce_partial_equal(&self, other: &Self) -> Result<(), SynthesisError> {
|
||||
self.u.enforce_equal(&other.u)?;
|
||||
self.x.enforce_equal(&other.x)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,24 +347,12 @@ where
|
||||
// `transcript` is for challenge generation.
|
||||
let mut transcript = sponge.clone();
|
||||
|
||||
// get z_{i+1} from the F circuit
|
||||
let i_usize = self.i_usize.unwrap_or(0);
|
||||
let z_i1 =
|
||||
self.F
|
||||
.generate_step_constraints(cs.clone(), i_usize, z_i.clone(), external_inputs)?;
|
||||
|
||||
let is_basecase = i.is_zero()?;
|
||||
|
||||
// Primary Part
|
||||
// P.1. Compute u_i.x
|
||||
// u_i.x[0] = H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
i.clone(),
|
||||
z_0.clone(),
|
||||
z_i.clone(),
|
||||
)?;
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
// u_i.x[1] = H(cf_U_i)
|
||||
let (cf_u_i_x, cf_U_i_vec) = cf_U_i.clone().hash(&sponge, pp_hash.clone())?;
|
||||
|
||||
@@ -421,21 +397,28 @@ where
|
||||
U_i1.cmW = U_i1_cmW;
|
||||
|
||||
// P.4.a compute and check the first output of F'
|
||||
|
||||
// get z_{i+1} from the F circuit
|
||||
let i_usize = self.i_usize.unwrap_or(0);
|
||||
let z_i1 = self
|
||||
.F
|
||||
.generate_step_constraints(cs.clone(), i_usize, z_i, external_inputs)?;
|
||||
|
||||
// Base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{\bot})
|
||||
// Non-base case: u_{i+1}.x[0] == H((i+1, z_0, z_{i+1}, U_{i+1})
|
||||
let (u_i1_x, _) = U_i1.clone().hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
i + FpVar::<CF1<C1>>::one(),
|
||||
z_0.clone(),
|
||||
z_i1.clone(),
|
||||
&pp_hash,
|
||||
&(i + FpVar::<CF1<C1>>::one()),
|
||||
&z_0,
|
||||
&z_i1,
|
||||
)?;
|
||||
let (u_i1_x_base, _) = CommittedInstanceVar::new_constant(cs.clone(), u_dummy)?.hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
FpVar::<CF1<C1>>::one(),
|
||||
z_0.clone(),
|
||||
z_i1.clone(),
|
||||
&pp_hash,
|
||||
&FpVar::<CF1<C1>>::one(),
|
||||
&z_0,
|
||||
&z_i1,
|
||||
)?;
|
||||
let x = FpVar::new_input(cs.clone(), || Ok(self.x.unwrap_or(u_i1_x_base.value()?)))?;
|
||||
x.enforce_equal(&is_basecase.select(&u_i1_x_base, &u_i1_x)?)?;
|
||||
@@ -538,6 +521,7 @@ pub mod tests {
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::nifs::tests::prepare_simple_fold_inputs;
|
||||
use crate::folding::nova::nifs::NIFS;
|
||||
use crate::folding::traits::CommittedInstanceOps;
|
||||
use crate::transcript::poseidon::poseidon_canonical_config;
|
||||
|
||||
#[test]
|
||||
@@ -591,6 +575,36 @@ pub mod tests {
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
/// test that checks the native CommittedInstance.to_sponge_{bytes,field_elements}
|
||||
/// vs the R1CS constraints version
|
||||
#[test]
|
||||
pub fn test_committed_instance_to_sponge_preimage() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
let ci = CommittedInstance::<Projective> {
|
||||
cmE: Projective::rand(&mut rng),
|
||||
u: Fr::rand(&mut rng),
|
||||
cmW: Projective::rand(&mut rng),
|
||||
x: vec![Fr::rand(&mut rng); 1],
|
||||
};
|
||||
|
||||
let bytes = ci.to_sponge_bytes_as_vec();
|
||||
let field_elements = ci.to_sponge_field_elements_as_vec();
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
let ciVar =
|
||||
CommittedInstanceVar::<Projective>::new_witness(cs.clone(), || Ok(ci.clone())).unwrap();
|
||||
let bytes_var = ciVar.to_sponge_bytes().unwrap();
|
||||
let field_elements_var = ciVar.to_sponge_field_elements().unwrap();
|
||||
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
// check that the natively computed and in-circuit computed hashes match
|
||||
assert_eq!(bytes_var.value().unwrap(), bytes);
|
||||
assert_eq!(field_elements_var.value().unwrap(), field_elements);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_committed_instance_hash() {
|
||||
let mut rng = ark_std::test_rng();
|
||||
@@ -609,7 +623,7 @@ pub mod tests {
|
||||
};
|
||||
|
||||
// compute the CommittedInstance hash natively
|
||||
let h = ci.hash(&sponge, pp_hash, i, z_0.clone(), z_i.clone());
|
||||
let h = ci.hash(&sponge, pp_hash, i, &z_0, &z_i);
|
||||
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
@@ -624,7 +638,7 @@ pub mod tests {
|
||||
|
||||
// compute the CommittedInstance hash in-circuit
|
||||
let (hVar, _) = ciVar
|
||||
.hash(&sponge, pp_hashVar, iVar, z_0Var, z_iVar)
|
||||
.hash(&sponge, &pp_hashVar, &iVar, &z_0Var, &z_iVar)
|
||||
.unwrap();
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ use super::{
|
||||
nifs::NIFS,
|
||||
CommittedInstance, Nova, Witness,
|
||||
};
|
||||
use crate::arith::r1cs::R1CS;
|
||||
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentScheme};
|
||||
use crate::folding::circuits::{
|
||||
cyclefold::{CycleFoldCommittedInstance, CycleFoldWitness},
|
||||
nonnative::{affine::NonNativeAffineVar, uint::NonNativeUintVar},
|
||||
@@ -41,6 +39,11 @@ use crate::utils::{
|
||||
vec::poly_from_vec,
|
||||
};
|
||||
use crate::Error;
|
||||
use crate::{arith::r1cs::R1CS, folding::traits::WitnessVarOps};
|
||||
use crate::{
|
||||
commitment::{pedersen::Params as PedersenParams, CommitmentScheme},
|
||||
folding::traits::CommittedInstanceVarOps,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RelaxedR1CSGadget {}
|
||||
@@ -135,7 +138,6 @@ pub struct WitnessVar<C: CurveGroup> {
|
||||
impl<C> AllocVar<Witness<C>, CF1<C>> for WitnessVar<C>
|
||||
where
|
||||
C: CurveGroup,
|
||||
<C as ark_ec::CurveGroup>::BaseField: PrimeField,
|
||||
{
|
||||
fn new_variable<T: Borrow<Witness<C>>>(
|
||||
cs: impl Into<Namespace<CF1<C>>>,
|
||||
@@ -160,6 +162,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> WitnessVarOps<C::ScalarField> for WitnessVar<C> {
|
||||
fn get_openings(&self) -> Vec<(&[FpVar<C::ScalarField>], FpVar<C::ScalarField>)> {
|
||||
vec![(&self.E, self.rE.clone()), (&self.W, self.rW.clone())]
|
||||
}
|
||||
}
|
||||
|
||||
/// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM)
|
||||
/// verification.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -398,13 +406,7 @@ where
|
||||
(u_i.u.is_one()?).enforce_equal(&Boolean::TRUE)?;
|
||||
|
||||
// 3.a u_i.x[0] == H(i, z_0, z_i, U_i)
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(
|
||||
&sponge,
|
||||
pp_hash.clone(),
|
||||
i.clone(),
|
||||
z_0.clone(),
|
||||
z_i.clone(),
|
||||
)?;
|
||||
let (u_i_x, U_i_vec) = U_i.clone().hash(&sponge, &pp_hash, &i, &z_0, &z_i)?;
|
||||
(u_i.x[0]).enforce_equal(&u_i_x)?;
|
||||
|
||||
#[cfg(feature = "light-test")]
|
||||
|
||||
@@ -13,6 +13,7 @@ use ark_std::fmt::Debug;
|
||||
use ark_std::rand::RngCore;
|
||||
use ark_std::{One, UniformRand, Zero};
|
||||
use core::marker::PhantomData;
|
||||
use decider_eth_circuit::WitnessVar;
|
||||
|
||||
use crate::folding::circuits::cyclefold::{
|
||||
fold_cyclefold_circuit, CycleFoldCircuit, CycleFoldCommittedInstance, CycleFoldConfig,
|
||||
@@ -38,9 +39,11 @@ pub mod nifs;
|
||||
pub mod serialize;
|
||||
pub mod traits;
|
||||
pub mod zk;
|
||||
use circuits::{AugmentedFCircuit, ChallengeGadget};
|
||||
use circuits::{AugmentedFCircuit, ChallengeGadget, CommittedInstanceVar};
|
||||
use nifs::NIFS;
|
||||
|
||||
use super::traits::{CommittedInstanceOps, WitnessOps};
|
||||
|
||||
/// Configuration for Nova's CycleFold circuit
|
||||
pub struct NovaCycleFoldConfig<C: CurveGroup> {
|
||||
_c: PhantomData<C>,
|
||||
@@ -83,9 +86,8 @@ impl<C: CurveGroup> Absorb for CommittedInstance<C>
|
||||
where
|
||||
C::ScalarField: Absorb,
|
||||
{
|
||||
fn to_sponge_bytes(&self, _dest: &mut Vec<u8>) {
|
||||
// This is never called
|
||||
unimplemented!()
|
||||
fn to_sponge_bytes(&self, dest: &mut Vec<u8>) {
|
||||
C::ScalarField::batch_to_sponge_bytes(&self.to_sponge_field_elements_as_vec(), dest);
|
||||
}
|
||||
|
||||
fn to_sponge_field_elements<F: PrimeField>(&self, dest: &mut Vec<F>) {
|
||||
@@ -103,30 +105,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> CommittedInstance<C>
|
||||
where
|
||||
<C as Group>::ScalarField: Absorb,
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
/// hash implements the committed instance hash compatible with the gadget implemented in
|
||||
/// nova/circuits.rs::CommittedInstanceVar.hash.
|
||||
/// Returns `H(i, z_0, z_i, U_i)`, where `i` can be `i` but also `i+1`, and `U_i` is the
|
||||
/// `CommittedInstance`.
|
||||
pub fn hash<T: Transcript<C::ScalarField>>(
|
||||
&self,
|
||||
sponge: &T,
|
||||
pp_hash: C::ScalarField, // public params hash
|
||||
i: C::ScalarField,
|
||||
z_0: Vec<C::ScalarField>,
|
||||
z_i: Vec<C::ScalarField>,
|
||||
) -> C::ScalarField {
|
||||
let mut sponge = sponge.clone();
|
||||
sponge.absorb(&pp_hash);
|
||||
sponge.absorb(&i);
|
||||
sponge.absorb(&z_0);
|
||||
sponge.absorb(&z_i);
|
||||
sponge.absorb(&self);
|
||||
sponge.squeeze_field_elements(1)[0]
|
||||
impl<C: CurveGroup> CommittedInstanceOps<C> for CommittedInstance<C> {
|
||||
type Var = CommittedInstanceVar<C>;
|
||||
|
||||
fn get_commitments(&self) -> Vec<C> {
|
||||
vec![self.cmW, self.cmE]
|
||||
}
|
||||
|
||||
fn is_incoming(&self) -> bool {
|
||||
self.cmE == C::zero() && self.u == One::one()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,6 +175,14 @@ impl<C: CurveGroup> Witness<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: CurveGroup> WitnessOps<C::ScalarField> for Witness<C> {
|
||||
type Var = WitnessVar<C>;
|
||||
|
||||
fn get_openings(&self) -> Vec<(&[C::ScalarField], C::ScalarField)> {
|
||||
vec![(&self.W, self.rW), (&self.E, self.rE)]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PreprocessorParam<C1, C2, FC, CS1, CS2, const H: bool = false>
|
||||
where
|
||||
@@ -696,8 +691,8 @@ where
|
||||
&sponge,
|
||||
self.pp_hash,
|
||||
self.i + C1::ScalarField::one(),
|
||||
self.z_0.clone(),
|
||||
z_i1.clone(),
|
||||
&self.z_0,
|
||||
&z_i1,
|
||||
);
|
||||
// u_{i+1}.x[1] = H(cf_U_{i+1})
|
||||
let cf_u_i1_x: C1::ScalarField;
|
||||
@@ -907,7 +902,7 @@ where
|
||||
|
||||
// check that u_i's output points to the running instance
|
||||
// u_i.X[0] == H(i, z_0, z_i, U_i)
|
||||
let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, z_0, z_i.clone());
|
||||
let expected_u_i_x = U_i.hash(&sponge, pp_hash, num_steps, &z_0, &z_i);
|
||||
if expected_u_i_x != u_i.x[0] {
|
||||
return Err(Error::IVCVerificationFail);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ use ark_std::{One, Zero};
|
||||
|
||||
use crate::{
|
||||
arith::r1cs::{RelaxedR1CS, R1CS},
|
||||
folding::traits::CommittedInstanceOps,
|
||||
RngCore,
|
||||
};
|
||||
use ark_crypto_primitives::sponge::{
|
||||
@@ -226,7 +227,7 @@ where
|
||||
|
||||
// b. Check computed hashes are correct
|
||||
let mut sponge = PoseidonSponge::<C1::ScalarField>::new(poseidon_config);
|
||||
let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, z_0, z_i);
|
||||
let expected_u_i_x = proof.U_i.hash(&sponge, pp_hash, i, &z_0, &z_i);
|
||||
if expected_u_i_x != proof.u_i.x[0] {
|
||||
return Err(Error::zkIVCVerificationFail);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user