@ -0,0 +1,496 @@ |
|||
/// This file implements the HyperNova's onchain (Ethereum's EVM) decider.
|
|||
use ark_crypto_primitives::sponge::Absorb;
|
|||
use ark_ec::{CurveGroup, Group};
|
|||
use ark_ff::PrimeField;
|
|||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar, ToConstraintFieldGadget};
|
|||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
|||
use ark_snark::SNARK;
|
|||
use ark_std::rand::{CryptoRng, RngCore};
|
|||
use ark_std::{One, Zero};
|
|||
use core::marker::PhantomData;
|
|||
|
|||
pub use super::decider_eth_circuit::DeciderEthCircuit;
|
|||
use super::{lcccs::LCCCS, HyperNova};
|
|||
use crate::commitment::{
|
|||
kzg::Proof as KZGProof, pedersen::Params as PedersenParams, CommitmentScheme,
|
|||
};
|
|||
use crate::folding::circuits::{nonnative::affine::NonNativeAffineVar, CF2};
|
|||
use crate::folding::nova::decider_eth::VerifierParam;
|
|||
use crate::frontend::FCircuit;
|
|||
use crate::Error;
|
|||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
|||
|
|||
#[derive(Debug, Clone, Eq, PartialEq, CanonicalSerialize, CanonicalDeserialize)]
|
|||
pub struct Proof<C1, CS1, S>
|
|||
where
|
|||
C1: CurveGroup,
|
|||
CS1: CommitmentScheme<C1, ProverChallenge = C1::ScalarField, Challenge = C1::ScalarField>,
|
|||
S: SNARK<C1::ScalarField>,
|
|||
{
|
|||
snark_proof: S::Proof,
|
|||
kzg_proof: CS1::Proof,
|
|||
// rho used at the last fold, U_{i+1}=NIMFS.V(rho, U_i, u_i), it is checked in-circuit
|
|||
rho: C1::ScalarField,
|
|||
U_i1: LCCCS<C1>, // U_{i+1}, which is checked in-circuit
|
|||
// the KZG challenge is provided by the prover, but in-circuit it is checked to match
|
|||
// the in-circuit computed computed one.
|
|||
kzg_challenge: C1::ScalarField,
|
|||
}
|
|||
|
|||
/// Onchain Decider, for ethereum use cases
|
|||
#[derive(Clone, Debug)]
|
|||
pub struct Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize> {
|
|||
_c1: PhantomData<C1>,
|
|||
_gc1: PhantomData<GC1>,
|
|||
_c2: PhantomData<C2>,
|
|||
_gc2: PhantomData<GC2>,
|
|||
_fc: PhantomData<FC>,
|
|||
_cs1: PhantomData<CS1>,
|
|||
_cs2: PhantomData<CS2>,
|
|||
_s: PhantomData<S>,
|
|||
_fs: PhantomData<FS>,
|
|||
}
|
|||
|
|||
impl<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, const MU: usize, const NU: usize>
|
|||
DeciderTrait<C1, C2, FC, FS> for Decider<C1, GC1, C2, GC2, FC, CS1, CS2, S, FS, MU, NU>
|
|||
where
|
|||
C1: CurveGroup,
|
|||
C2: CurveGroup,
|
|||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
|||
FC: FCircuit<C1::ScalarField>,
|
|||
// CS1 is a KZG commitment, where challenge is C1::Fr elem
|
|||
CS1: CommitmentScheme<
|
|||
C1,
|
|||
ProverChallenge = C1::ScalarField,
|
|||
Challenge = C1::ScalarField,
|
|||
Proof = KZGProof<C1>,
|
|||
>,
|
|||
// enforce that the CS2 is Pedersen commitment scheme, since we're at Ethereum's EVM decider
|
|||
CS2: CommitmentScheme<C2, ProverParams = PedersenParams<C2>>,
|
|||
S: SNARK<C1::ScalarField>,
|
|||
FS: FoldingScheme<C1, C2, FC>,
|
|||
<C1 as CurveGroup>::BaseField: PrimeField,
|
|||
<C2 as CurveGroup>::BaseField: PrimeField,
|
|||
<C1 as Group>::ScalarField: Absorb,
|
|||
<C2 as Group>::ScalarField: Absorb,
|
|||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
|||
for<'b> &'b GC1: GroupOpsBounds<'b, C1, GC1>,
|
|||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
|||
// constrain FS into HyperNova, since this is a Decider specifically for HyperNova
|
|||
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false>: From<FS>,
|
|||
crate::folding::hypernova::ProverParams<C1, C2, CS1, CS2, false>: |
|||
From<<FS as FoldingScheme<C1, C2, FC>>::ProverParam>,
|
|||
crate::folding::hypernova::VerifierParams<C1, C2, CS1, CS2, false>: |
|||
From<<FS as FoldingScheme<C1, C2, FC>>::VerifierParam>,
|
|||
{
|
|||
type PreprocessorParam = (FS::ProverParam, FS::VerifierParam);
|
|||
type ProverParam = (S::ProvingKey, CS1::ProverParams);
|
|||
type Proof = Proof<C1, CS1, S>;
|
|||
type VerifierParam = VerifierParam<C1, CS1::VerifierParams, S::VerifyingKey>;
|
|||
type PublicInput = Vec<C1::ScalarField>;
|
|||
type CommittedInstance = ();
|
|||
|
|||
fn preprocess(
|
|||
mut rng: impl RngCore + CryptoRng,
|
|||
prep_param: Self::PreprocessorParam,
|
|||
fs: FS,
|
|||
) -> Result<(Self::ProverParam, Self::VerifierParam), Error> {
|
|||
let circuit =
|
|||
DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
|
|||
fs.into(),
|
|||
)
|
|||
.unwrap();
|
|||
|
|||
// get the Groth16 specific setup for the circuit
|
|||
let (g16_pk, g16_vk) = S::circuit_specific_setup(circuit, &mut rng).unwrap();
|
|||
|
|||
// get the FoldingScheme prover & verifier params from HyperNova
|
|||
#[allow(clippy::type_complexity)]
|
|||
let hypernova_pp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
|
|||
C1,
|
|||
C2,
|
|||
FC,
|
|||
>>::ProverParam = prep_param.0.into();
|
|||
#[allow(clippy::type_complexity)]
|
|||
let hypernova_vp: <HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, false> as FoldingScheme<
|
|||
C1,
|
|||
C2,
|
|||
FC,
|
|||
>>::VerifierParam = prep_param.1.into();
|
|||
let pp_hash = hypernova_vp.pp_hash()?;
|
|||
|
|||
let pp = (g16_pk, hypernova_pp.cs_pp);
|
|||
|
|||
let vp = VerifierParam {
|
|||
pp_hash,
|
|||
snark_vp: g16_vk,
|
|||
cs_vp: hypernova_vp.cs_vp,
|
|||
};
|
|||
Ok((pp, vp))
|
|||
}
|
|||
|
|||
fn prove(
|
|||
mut rng: impl RngCore + CryptoRng,
|
|||
pp: Self::ProverParam,
|
|||
folding_scheme: FS,
|
|||
) -> Result<Self::Proof, Error> {
|
|||
let (snark_pk, cs_pk): (S::ProvingKey, CS1::ProverParams) = pp;
|
|||
|
|||
let circuit = DeciderEthCircuit::<C1, GC1, C2, GC2, CS1, CS2>::from_hypernova::<FC, MU, NU>(
|
|||
folding_scheme.into(),
|
|||
)?;
|
|||
|
|||
let snark_proof = S::prove(&snark_pk, circuit.clone(), &mut rng)
|
|||
.map_err(|e| Error::Other(e.to_string()))?;
|
|||
|
|||
// Notice that since the `circuit` has been constructed at the `from_hypernova` call, which
|
|||
// in case of failure it would have returned an error there, the next two unwraps should
|
|||
// never reach an error.
|
|||
let rho_Fr = circuit.rho.ok_or(Error::Empty)?;
|
|||
let W_i1 = circuit.W_i1.ok_or(Error::Empty)?;
|
|||
|
|||
// get the challenges that have been already computed when preparing the circuit inputs in
|
|||
// the above `from_hypernova` call
|
|||
let challenge_W = circuit
|
|||
.kzg_challenge
|
|||
.ok_or(Error::MissingValue("kzg_challenge".to_string()))?;
|
|||
|
|||
// generate KZG proofs
|
|||
let U_cmW_proof = CS1::prove_with_challenge(
|
|||
&cs_pk,
|
|||
challenge_W,
|
|||
&W_i1.w,
|
|||
&C1::ScalarField::zero(),
|
|||
None,
|
|||
)?;
|
|||
|
|||
Ok(Self::Proof {
|
|||
snark_proof,
|
|||
kzg_proof: U_cmW_proof,
|
|||
rho: rho_Fr,
|
|||
U_i1: circuit.U_i1.ok_or(Error::Empty)?,
|
|||
kzg_challenge: challenge_W,
|
|||
})
|
|||
}
|
|||
|
|||
fn verify(
|
|||
vp: Self::VerifierParam,
|
|||
i: C1::ScalarField,
|
|||
z_0: Vec<C1::ScalarField>,
|
|||
z_i: Vec<C1::ScalarField>,
|
|||
// we don't use the instances at the verifier level, since we check them in-circuit
|
|||
_running_instance: &Self::CommittedInstance,
|
|||
_incoming_instance: &Self::CommittedInstance,
|
|||
proof: &Self::Proof,
|
|||
) -> Result<bool, Error> {
|
|||
if i <= C1::ScalarField::one() {
|
|||
return Err(Error::NotEnoughSteps);
|
|||
}
|
|||
|
|||
let (pp_hash, snark_vk, cs_vk): (C1::ScalarField, S::VerifyingKey, CS1::VerifierParams) =
|
|||
(vp.pp_hash, vp.snark_vp, vp.cs_vp);
|
|||
|
|||
// Note: the NIMFS proof is checked inside the DeciderEthCircuit, which ensures that the
|
|||
// 'proof.U_i1' is correctly computed
|
|||
|
|||
let (cmC_x, cmC_y) = NonNativeAffineVar::inputize(proof.U_i1.C)?;
|
|||
|
|||
let public_input: Vec<C1::ScalarField> = [
|
|||
vec![pp_hash, i],
|
|||
z_0,
|
|||
z_i,
|
|||
// U_i+1:
|
|||
cmC_x,
|
|||
cmC_y,
|
|||
vec![proof.U_i1.u],
|
|||
proof.U_i1.x.clone(),
|
|||
proof.U_i1.r_x.clone(),
|
|||
proof.U_i1.v.clone(),
|
|||
vec![proof.kzg_challenge, proof.kzg_proof.eval, proof.rho],
|
|||
]
|
|||
.concat();
|
|||
|
|||
let snark_v = S::verify(&snark_vk, &public_input, &proof.snark_proof)
|
|||
.map_err(|e| Error::Other(e.to_string()))?;
|
|||
if !snark_v {
|
|||
return Err(Error::SNARKVerificationFail);
|
|||
}
|
|||
|
|||
// we're at the Ethereum EVM case, so the CS1 is KZG commitments
|
|||
CS1::verify_with_challenge(&cs_vk, proof.kzg_challenge, &proof.U_i1.C, &proof.kzg_proof)?;
|
|||
|
|||
Ok(true)
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
pub mod tests {
|
|||
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
|
|||
use ark_groth16::Groth16;
|
|||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
|||
use ark_serialize::{Compress, Validate};
|
|||
|
|||
use super::*;
|
|||
use crate::commitment::{kzg::KZG, pedersen::Pedersen};
|
|||
use crate::folding::hypernova::cccs::CCCS;
|
|||
use crate::folding::hypernova::{
|
|||
PreprocessorParam, ProverParams, VerifierParams as HyperNovaVerifierParams,
|
|||
};
|
|||
use crate::folding::nova::decider_eth::VerifierParam;
|
|||
use crate::frontend::utils::CubicFCircuit;
|
|||
use crate::transcript::poseidon::poseidon_canonical_config;
|
|||
|
|||
#[test]
|
|||
fn test_decider() {
|
|||
const MU: usize = 1;
|
|||
const NU: usize = 1;
|
|||
// use HyperNova as FoldingScheme
|
|||
type HN = HyperNova<
|
|||
Projective,
|
|||
GVar,
|
|||
Projective2,
|
|||
GVar2,
|
|||
CubicFCircuit<Fr>,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
MU,
|
|||
NU,
|
|||
false,
|
|||
>;
|
|||
type D = Decider<
|
|||
Projective,
|
|||
GVar,
|
|||
Projective2,
|
|||
GVar2,
|
|||
CubicFCircuit<Fr>,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
Groth16<Bn254>, // here we define the Snark to use in the decider
|
|||
HN, // here we define the FoldingScheme to use
|
|||
MU,
|
|||
NU,
|
|||
>;
|
|||
|
|||
let mut rng = rand::rngs::OsRng;
|
|||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
|||
|
|||
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
|||
let z_0 = vec![Fr::from(3_u32)];
|
|||
|
|||
let prep_param = PreprocessorParam::new(poseidon_config, F_circuit);
|
|||
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
|
|||
|
|||
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
|
|||
hypernova
|
|||
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
|
|||
.unwrap();
|
|||
hypernova
|
|||
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
|
|||
.unwrap(); // do a 2nd step
|
|||
|
|||
// prepare the Decider prover & verifier params
|
|||
let (decider_pp, decider_vp) =
|
|||
D::preprocess(&mut rng, hypernova_params, hypernova.clone()).unwrap();
|
|||
|
|||
// decider proof generation
|
|||
let proof = D::prove(rng, decider_pp, hypernova.clone()).unwrap();
|
|||
|
|||
// decider proof verification
|
|||
let verified = D::verify(
|
|||
decider_vp,
|
|||
hypernova.i,
|
|||
hypernova.z_0,
|
|||
hypernova.z_i,
|
|||
&(),
|
|||
&(),
|
|||
&proof,
|
|||
)
|
|||
.unwrap();
|
|||
assert!(verified);
|
|||
}
|
|||
|
|||
#[test]
|
|||
fn test_decider_serialization() {
|
|||
const MU: usize = 1;
|
|||
const NU: usize = 1;
|
|||
// use HyperNova as FoldingScheme
|
|||
type HN = HyperNova<
|
|||
Projective,
|
|||
GVar,
|
|||
Projective2,
|
|||
GVar2,
|
|||
CubicFCircuit<Fr>,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
MU,
|
|||
NU,
|
|||
false,
|
|||
>;
|
|||
type D = Decider<
|
|||
Projective,
|
|||
GVar,
|
|||
Projective2,
|
|||
GVar2,
|
|||
CubicFCircuit<Fr>,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
Groth16<Bn254>, // here we define the Snark to use in the decider
|
|||
HN, // here we define the FoldingScheme to use
|
|||
MU,
|
|||
NU,
|
|||
>;
|
|||
|
|||
let mut rng = ark_std::test_rng();
|
|||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
|||
|
|||
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
|||
let z_0 = vec![Fr::from(3_u32)];
|
|||
|
|||
let prep_param = PreprocessorParam::new(poseidon_config.clone(), F_circuit);
|
|||
let hypernova_params = HN::preprocess(&mut rng, &prep_param).unwrap();
|
|||
|
|||
let hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
|
|||
|
|||
let mut rng = rand::rngs::OsRng;
|
|||
|
|||
// prepare the Decider prover & verifier params
|
|||
let (decider_pp, decider_vp) =
|
|||
D::preprocess(&mut rng, hypernova_params.clone(), hypernova.clone()).unwrap();
|
|||
|
|||
let mut hypernova_pp_serialized = vec![];
|
|||
hypernova_params
|
|||
.0
|
|||
.clone()
|
|||
.serialize_compressed(&mut hypernova_pp_serialized)
|
|||
.unwrap();
|
|||
let mut hypernova_vp_serialized = vec![];
|
|||
hypernova_params
|
|||
.1
|
|||
.clone()
|
|||
.serialize_compressed(&mut hypernova_vp_serialized)
|
|||
.unwrap();
|
|||
|
|||
let hypernova_pp_deserialized = ProverParams::<
|
|||
Projective,
|
|||
Projective2,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
false,
|
|||
>::deserialize_prover_params(
|
|||
hypernova_pp_serialized.as_slice(),
|
|||
Compress::Yes,
|
|||
Validate::No,
|
|||
&hypernova_params.0.ccs,
|
|||
&poseidon_config,
|
|||
)
|
|||
.unwrap();
|
|||
|
|||
let hypernova_vp_deserialized = HyperNovaVerifierParams::<
|
|||
Projective,
|
|||
Projective2,
|
|||
KZG<'static, Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
false,
|
|||
>::deserialize_verifier_params(
|
|||
hypernova_vp_serialized.as_slice(),
|
|||
Compress::Yes,
|
|||
Validate::No,
|
|||
&hypernova_params.0.ccs.unwrap(),
|
|||
&poseidon_config,
|
|||
)
|
|||
.unwrap();
|
|||
|
|||
let hypernova_params = (hypernova_pp_deserialized, hypernova_vp_deserialized);
|
|||
let mut hypernova = HN::init(&hypernova_params, F_circuit, z_0.clone()).unwrap();
|
|||
|
|||
hypernova
|
|||
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
|
|||
.unwrap();
|
|||
hypernova
|
|||
.prove_step(&mut rng, vec![], Some((vec![], vec![])))
|
|||
.unwrap();
|
|||
|
|||
// decider proof generation
|
|||
let proof = D::prove(rng, decider_pp, hypernova.clone()).unwrap();
|
|||
|
|||
let verified = D::verify(
|
|||
decider_vp.clone(),
|
|||
hypernova.i.clone(),
|
|||
hypernova.z_0.clone(),
|
|||
hypernova.z_i.clone(),
|
|||
&(),
|
|||
&(),
|
|||
&proof,
|
|||
)
|
|||
.unwrap();
|
|||
assert!(verified);
|
|||
|
|||
// The rest of this test will serialize the data and deserialize it back, and use it to
|
|||
// verify the proof:
|
|||
|
|||
// serialize the verifier_params, proof and public inputs
|
|||
let mut decider_vp_serialized = vec![];
|
|||
decider_vp
|
|||
.serialize_compressed(&mut decider_vp_serialized)
|
|||
.unwrap();
|
|||
let mut proof_serialized = vec![];
|
|||
proof.serialize_compressed(&mut proof_serialized).unwrap();
|
|||
// serialize the public inputs in a single packet
|
|||
let mut public_inputs_serialized = vec![];
|
|||
hypernova
|
|||
.i
|
|||
.serialize_compressed(&mut public_inputs_serialized)
|
|||
.unwrap();
|
|||
hypernova
|
|||
.z_0
|
|||
.serialize_compressed(&mut public_inputs_serialized)
|
|||
.unwrap();
|
|||
hypernova
|
|||
.z_i
|
|||
.serialize_compressed(&mut public_inputs_serialized)
|
|||
.unwrap();
|
|||
hypernova
|
|||
.U_i
|
|||
.serialize_compressed(&mut public_inputs_serialized)
|
|||
.unwrap();
|
|||
hypernova
|
|||
.u_i
|
|||
.serialize_compressed(&mut public_inputs_serialized)
|
|||
.unwrap();
|
|||
|
|||
// deserialize back the verifier_params, proof and public inputs
|
|||
let decider_vp_deserialized =
|
|||
VerifierParam::<
|
|||
Projective,
|
|||
<KZG<'static, Bn254> as CommitmentScheme<Projective>>::VerifierParams,
|
|||
<Groth16<Bn254> as SNARK<Fr>>::VerifyingKey,
|
|||
>::deserialize_compressed(&mut decider_vp_serialized.as_slice())
|
|||
.unwrap();
|
|||
|
|||
let proof_deserialized =
|
|||
Proof::<Projective, KZG<'static, Bn254>, Groth16<Bn254>>::deserialize_compressed(
|
|||
&mut proof_serialized.as_slice(),
|
|||
)
|
|||
.unwrap();
|
|||
|
|||
let mut reader = public_inputs_serialized.as_slice();
|
|||
let i_deserialized = Fr::deserialize_compressed(&mut reader).unwrap();
|
|||
let z_0_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
|
|||
let z_i_deserialized = Vec::<Fr>::deserialize_compressed(&mut reader).unwrap();
|
|||
let _U_i = LCCCS::<Projective>::deserialize_compressed(&mut reader).unwrap();
|
|||
let _u_i = CCCS::<Projective>::deserialize_compressed(&mut reader).unwrap();
|
|||
|
|||
let verified = D::verify(
|
|||
decider_vp_deserialized,
|
|||
i_deserialized.clone(),
|
|||
z_0_deserialized.clone(),
|
|||
z_i_deserialized.clone(),
|
|||
&(),
|
|||
&(),
|
|||
&proof_deserialized,
|
|||
)
|
|||
.unwrap();
|
|||
assert!(verified);
|
|||
}
|
|||
}
|
@ -0,0 +1,420 @@ |
|||
use crate::arith::ccs::CCS;
|
|||
use crate::arith::r1cs::R1CS;
|
|||
use crate::folding::hypernova::ProverParams;
|
|||
use crate::folding::hypernova::VerifierParams;
|
|||
use ark_crypto_primitives::sponge::poseidon::PoseidonConfig;
|
|||
use ark_crypto_primitives::sponge::Absorb;
|
|||
use ark_ec::{CurveGroup, Group};
|
|||
use ark_ff::PrimeField;
|
|||
use ark_r1cs_std::groups::{CurveVar, GroupOpsBounds};
|
|||
use ark_r1cs_std::ToConstraintFieldGadget;
|
|||
use ark_serialize::CanonicalDeserialize;
|
|||
use ark_serialize::{CanonicalSerialize, Compress, SerializationError, Validate};
|
|||
use ark_std::marker::PhantomData;
|
|||
|
|||
use crate::folding::hypernova::cccs::CCCS;
|
|||
use crate::folding::hypernova::lcccs::LCCCS;
|
|||
use crate::folding::hypernova::Witness;
|
|||
use crate::folding::nova::{
|
|||
CommittedInstance as CycleFoldCommittedInstance, Witness as CycleFoldWitness,
|
|||
};
|
|||
use crate::FoldingScheme;
|
|||
use crate::{
|
|||
commitment::CommitmentScheme,
|
|||
folding::{circuits::CF2, nova::PreprocessorParam},
|
|||
frontend::FCircuit,
|
|||
};
|
|||
|
|||
use super::HyperNova;
|
|||
|
|||
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
|
|||
CanonicalSerialize for HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
|
|||
where
|
|||
C1: CurveGroup,
|
|||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|||
C2: CurveGroup,
|
|||
GC2: CurveVar<C2, CF2<C2>>,
|
|||
FC: FCircuit<C1::ScalarField>,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
{
|
|||
fn serialize_compressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), ark_serialize::SerializationError> {
|
|||
self.serialize_with_mode(writer, ark_serialize::Compress::Yes)
|
|||
}
|
|||
|
|||
fn compressed_size(&self) -> usize {
|
|||
self.serialized_size(ark_serialize::Compress::Yes)
|
|||
}
|
|||
|
|||
fn serialize_uncompressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), ark_serialize::SerializationError> {
|
|||
self.serialize_with_mode(writer, ark_serialize::Compress::No)
|
|||
}
|
|||
|
|||
fn uncompressed_size(&self) -> usize {
|
|||
self.serialized_size(ark_serialize::Compress::No)
|
|||
}
|
|||
|
|||
fn serialize_with_mode<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
mut writer: W,
|
|||
compress: ark_serialize::Compress,
|
|||
) -> Result<(), ark_serialize::SerializationError> {
|
|||
self.pp_hash.serialize_with_mode(&mut writer, compress)?;
|
|||
self.i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.z_0.serialize_with_mode(&mut writer, compress)?;
|
|||
self.z_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.W_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.U_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.w_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.u_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.cf_W_i.serialize_with_mode(&mut writer, compress)?;
|
|||
self.cf_U_i.serialize_with_mode(&mut writer, compress)
|
|||
}
|
|||
|
|||
fn serialized_size(&self, compress: ark_serialize::Compress) -> usize {
|
|||
self.pp_hash.serialized_size(compress)
|
|||
+ self.i.serialized_size(compress)
|
|||
+ self.z_0.serialized_size(compress)
|
|||
+ self.z_i.serialized_size(compress)
|
|||
+ self.W_i.serialized_size(compress)
|
|||
+ self.U_i.serialized_size(compress)
|
|||
+ self.w_i.serialized_size(compress)
|
|||
+ self.u_i.serialized_size(compress)
|
|||
+ self.cf_W_i.serialized_size(compress)
|
|||
+ self.cf_U_i.serialized_size(compress)
|
|||
}
|
|||
}
|
|||
|
|||
impl<C1, GC1, C2, GC2, FC, CS1, CS2, const MU: usize, const NU: usize, const H: bool>
|
|||
HyperNova<C1, GC1, C2, GC2, FC, CS1, CS2, MU, NU, H>
|
|||
where
|
|||
C1: CurveGroup,
|
|||
GC1: CurveVar<C1, CF2<C1>> + ToConstraintFieldGadget<CF2<C1>>,
|
|||
C2: CurveGroup,
|
|||
GC2: CurveVar<C2, CF2<C2>> + ToConstraintFieldGadget<CF2<C2>>,
|
|||
FC: FCircuit<C1::ScalarField, Params = ()>,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
<C1 as CurveGroup>::BaseField: PrimeField,
|
|||
<C2 as CurveGroup>::BaseField: PrimeField,
|
|||
<C1 as Group>::ScalarField: Absorb,
|
|||
<C2 as Group>::ScalarField: Absorb,
|
|||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
|||
for<'a> &'a GC1: GroupOpsBounds<'a, C1, GC1>,
|
|||
for<'a> &'a GC2: GroupOpsBounds<'a, C2, GC2>,
|
|||
{
|
|||
#[allow(clippy::too_many_arguments)]
|
|||
pub fn deserialize_hypernova<R: std::io::prelude::Read>(
|
|||
mut reader: R,
|
|||
compress: Compress,
|
|||
validate: Validate,
|
|||
poseidon_config: PoseidonConfig<C1::ScalarField>,
|
|||
cs_pp: CS1::ProverParams,
|
|||
cs_vp: CS1::VerifierParams,
|
|||
cf_cs_pp: CS2::ProverParams,
|
|||
cf_cs_vp: CS2::VerifierParams,
|
|||
) -> Result<Self, SerializationError> {
|
|||
let f_circuit = FC::new(()).unwrap();
|
|||
let prep_param = PreprocessorParam {
|
|||
poseidon_config: poseidon_config.clone(),
|
|||
F: f_circuit.clone(),
|
|||
cs_pp: Some(cs_pp.clone()),
|
|||
cs_vp: Some(cs_vp.clone()),
|
|||
cf_cs_pp: Some(cf_cs_pp.clone()),
|
|||
cf_cs_vp: Some(cf_cs_vp.clone()),
|
|||
};
|
|||
// `test_rng` won't be used in `preprocess`, since parameters have already been initialized
|
|||
let (prover_params, verifier_params) = Self::preprocess(ark_std::test_rng(), &prep_param)
|
|||
.or(Err(SerializationError::InvalidData))?;
|
|||
let pp_hash = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let i = C1::ScalarField::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let z_0 = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let z_i = Vec::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let W_i =
|
|||
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let U_i = LCCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let w_i =
|
|||
Witness::<C1::ScalarField>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let u_i = CCCS::<C1>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let cf_W_i =
|
|||
CycleFoldWitness::<C2>::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let cf_U_i = CycleFoldCommittedInstance::<C2>::deserialize_with_mode(
|
|||
&mut reader,
|
|||
compress,
|
|||
validate,
|
|||
)?;
|
|||
let ccs = prover_params.ccs.ok_or(SerializationError::InvalidData)?;
|
|||
|
|||
Ok(HyperNova {
|
|||
_gc1: PhantomData,
|
|||
_c2: PhantomData,
|
|||
_gc2: PhantomData,
|
|||
ccs,
|
|||
cf_r1cs: verifier_params.cf_r1cs,
|
|||
poseidon_config,
|
|||
cs_pp,
|
|||
cf_cs_pp,
|
|||
F: f_circuit,
|
|||
pp_hash,
|
|||
i,
|
|||
z_0,
|
|||
z_i,
|
|||
W_i,
|
|||
U_i,
|
|||
w_i,
|
|||
u_i,
|
|||
cf_W_i,
|
|||
cf_U_i,
|
|||
})
|
|||
}
|
|||
}
|
|||
|
|||
impl<
|
|||
C1: CurveGroup,
|
|||
C2: CurveGroup,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
const H: bool,
|
|||
> CanonicalSerialize for ProverParams<C1, C2, CS1, CS2, H>
|
|||
{
|
|||
fn serialize_compressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.serialize_with_mode(writer, Compress::Yes)
|
|||
}
|
|||
|
|||
fn compressed_size(&self) -> usize {
|
|||
self.serialized_size(Compress::Yes)
|
|||
}
|
|||
|
|||
fn serialize_uncompressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.serialize_with_mode(writer, Compress::No)
|
|||
}
|
|||
|
|||
fn uncompressed_size(&self) -> usize {
|
|||
self.serialized_size(Compress::No)
|
|||
}
|
|||
|
|||
fn serialize_with_mode<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
mut writer: W,
|
|||
compress: Compress,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.cs_pp.serialize_with_mode(&mut writer, compress)?;
|
|||
self.cf_cs_pp.serialize_with_mode(&mut writer, compress)
|
|||
}
|
|||
|
|||
fn serialized_size(&self, compress: Compress) -> usize {
|
|||
self.cs_pp.serialized_size(compress) + self.cf_cs_pp.serialized_size(compress)
|
|||
}
|
|||
}
|
|||
|
|||
impl<
|
|||
C1: CurveGroup,
|
|||
C2: CurveGroup,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
const H: bool,
|
|||
> ProverParams<C1, C2, CS1, CS2, H>
|
|||
{
|
|||
pub fn deserialize_prover_params<R: std::io::prelude::Read>(
|
|||
mut reader: R,
|
|||
compress: Compress,
|
|||
validate: Validate,
|
|||
ccs: &Option<CCS<C1::ScalarField>>,
|
|||
poseidon_config: &PoseidonConfig<C1::ScalarField>,
|
|||
) -> Result<Self, SerializationError> {
|
|||
let cs_pp = CS1::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let cf_cs_pp = CS2::ProverParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
|
|||
Ok(ProverParams {
|
|||
cs_pp,
|
|||
cf_cs_pp,
|
|||
ccs: ccs.clone(),
|
|||
poseidon_config: poseidon_config.clone(),
|
|||
})
|
|||
}
|
|||
}
|
|||
|
|||
impl<
|
|||
C1: CurveGroup,
|
|||
C2: CurveGroup,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
const H: bool,
|
|||
> CanonicalSerialize for VerifierParams<C1, C2, CS1, CS2, H>
|
|||
{
|
|||
fn serialize_compressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.serialize_with_mode(writer, Compress::Yes)
|
|||
}
|
|||
|
|||
fn compressed_size(&self) -> usize {
|
|||
self.serialized_size(Compress::Yes)
|
|||
}
|
|||
|
|||
fn serialize_uncompressed<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
writer: W,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.serialize_with_mode(writer, Compress::No)
|
|||
}
|
|||
|
|||
fn uncompressed_size(&self) -> usize {
|
|||
self.serialized_size(Compress::No)
|
|||
}
|
|||
|
|||
fn serialize_with_mode<W: std::io::prelude::Write>(
|
|||
&self,
|
|||
mut writer: W,
|
|||
compress: Compress,
|
|||
) -> Result<(), SerializationError> {
|
|||
self.cf_r1cs.serialize_with_mode(&mut writer, compress)?;
|
|||
self.cs_vp.serialize_with_mode(&mut writer, compress)?;
|
|||
self.cf_cs_vp.serialize_with_mode(&mut writer, compress)
|
|||
}
|
|||
|
|||
fn serialized_size(&self, compress: Compress) -> usize {
|
|||
self.cf_r1cs.serialized_size(compress)
|
|||
+ self.cs_vp.serialized_size(compress)
|
|||
+ self.cf_cs_vp.serialized_size(compress)
|
|||
}
|
|||
}
|
|||
|
|||
impl<
|
|||
C1: CurveGroup,
|
|||
C2: CurveGroup,
|
|||
CS1: CommitmentScheme<C1, H>,
|
|||
CS2: CommitmentScheme<C2, H>,
|
|||
const H: bool,
|
|||
> VerifierParams<C1, C2, CS1, CS2, H>
|
|||
{
|
|||
pub fn deserialize_verifier_params<R: std::io::Read>(
|
|||
mut reader: R,
|
|||
compress: Compress,
|
|||
validate: Validate,
|
|||
ccs: &CCS<C1::ScalarField>,
|
|||
poseidon_config: &PoseidonConfig<C1::ScalarField>,
|
|||
) -> Result<Self, SerializationError> {
|
|||
let cf_r1cs = R1CS::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let cs_vp = CS1::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
let cf_cs_vp = CS2::VerifierParams::deserialize_with_mode(&mut reader, compress, validate)?;
|
|||
Ok(VerifierParams {
|
|||
ccs: ccs.clone(),
|
|||
poseidon_config: poseidon_config.clone(),
|
|||
cf_r1cs,
|
|||
cs_vp,
|
|||
cf_cs_vp,
|
|||
})
|
|||
}
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
pub mod tests {
|
|||
use crate::FoldingScheme;
|
|||
use crate::MultiFolding;
|
|||
use ark_serialize::{Compress, Validate, Write};
|
|||
use std::fs;
|
|||
|
|||
use crate::{
|
|||
commitment::{kzg::KZG, pedersen::Pedersen},
|
|||
folding::hypernova::{tests::test_ivc_opt, HyperNova},
|
|||
frontend::{utils::CubicFCircuit, FCircuit},
|
|||
transcript::poseidon::poseidon_canonical_config,
|
|||
};
|
|||
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as Projective};
|
|||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as Projective2};
|
|||
use ark_serialize::CanonicalSerialize;
|
|||
|
|||
#[test]
|
|||
fn test_serde_hypernova() {
|
|||
let poseidon_config = poseidon_canonical_config::<Fr>();
|
|||
let F_circuit = CubicFCircuit::<Fr>::new(()).unwrap();
|
|||
let (mut hn, (_, verifier_params), _, _, _) = test_ivc_opt::<
|
|||
KZG<Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
false,
|
|||
>(poseidon_config.clone(), F_circuit);
|
|||
|
|||
let mut writer = vec![];
|
|||
assert!(hn.serialize_compressed(&mut writer).is_ok());
|
|||
let mut writer = vec![];
|
|||
assert!(hn.serialize_uncompressed(&mut writer).is_ok());
|
|||
|
|||
let mut file = fs::OpenOptions::new()
|
|||
.create(true)
|
|||
.write(true)
|
|||
.open("./hypernova.serde")
|
|||
.unwrap();
|
|||
|
|||
file.write_all(&writer).unwrap();
|
|||
|
|||
let bytes = fs::read("./hypernova.serde").unwrap();
|
|||
|
|||
let mut hn_deserialized = HyperNova::<
|
|||
Projective,
|
|||
GVar,
|
|||
Projective2,
|
|||
GVar2,
|
|||
CubicFCircuit<Fr>,
|
|||
KZG<Bn254>,
|
|||
Pedersen<Projective2>,
|
|||
2,
|
|||
3,
|
|||
false,
|
|||
>::deserialize_hypernova(
|
|||
bytes.as_slice(),
|
|||
Compress::No,
|
|||
Validate::No,
|
|||
poseidon_config,
|
|||
hn.cs_pp.clone(),
|
|||
verifier_params.cs_vp,
|
|||
hn.cf_cs_pp.clone(),
|
|||
verifier_params.cf_cs_vp,
|
|||
)
|
|||
.unwrap();
|
|||
|
|||
assert_eq!(hn.i, hn_deserialized.i);
|
|||
|
|||
let mut rng = ark_std::test_rng();
|
|||
for _ in 0..3 {
|
|||
// prepare some new instances to fold in the multifolding step
|
|||
let mut lcccs = vec![];
|
|||
for j in 0..1 {
|
|||
let instance_state = vec![Fr::from(j as u32 + 85_u32)];
|
|||
let (U, W) = hn
|
|||
.new_running_instance(&mut rng, instance_state, vec![])
|
|||
.unwrap();
|
|||
lcccs.push((U, W));
|
|||
}
|
|||
let mut cccs = vec![];
|
|||
for j in 0..2 {
|
|||
let instance_state = vec![Fr::from(j as u32 + 15_u32)];
|
|||
let (u, w) = hn
|
|||
.new_incoming_instance(&mut rng, instance_state, vec![])
|
|||
.unwrap();
|
|||
cccs.push((u, w));
|
|||
}
|
|||
|
|||
hn.prove_step(&mut rng, vec![], Some((lcccs.clone(), cccs.clone())))
|
|||
.unwrap();
|
|||
hn_deserialized
|
|||
.prove_step(&mut rng, vec![], Some((lcccs, cccs)))
|
|||
.unwrap();
|
|||
}
|
|||
|
|||
assert_eq!(hn.z_i, hn_deserialized.z_i);
|
|||
}
|
|||
}
|
@ -1,69 +1,90 @@ |
|||
use ark_crypto_primitives::sponge::Absorb;
|
|||
use ark_ec::{CurveGroup, Group};
|
|||
use ark_std::One;
|
|||
use ark_ec::CurveGroup;
|
|||
use ark_std::{rand::RngCore, One, UniformRand};
|
|||
|
|||
use super::{CommittedInstance, Witness};
|
|||
use crate::arith::{r1cs::R1CS, Arith};
|
|||
use crate::arith::r1cs::{RelaxedR1CS, R1CS};
|
|||
use crate::Error;
|
|||
|
|||
/// NovaR1CS extends R1CS methods with Nova specific methods
|
|||
pub trait NovaR1CS<C: CurveGroup> {
|
|||
/// returns a dummy instance (Witness and CommittedInstance) for the current R1CS structure
|
|||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>);
|
|||
|
|||
/// checks the R1CS relation (un-relaxed) for the given Witness and CommittedInstance.
|
|||
fn check_instance_relation(
|
|||
&self,
|
|||
W: &Witness<C>,
|
|||
U: &CommittedInstance<C>,
|
|||
) -> Result<(), Error>;
|
|||
|
|||
/// checks the Relaxed R1CS relation (corresponding to the current R1CS) for the given Witness
|
|||
/// and CommittedInstance.
|
|||
fn check_relaxed_instance_relation(
|
|||
&self,
|
|||
W: &Witness<C>,
|
|||
U: &CommittedInstance<C>,
|
|||
) -> Result<(), Error>;
|
|||
}
|
|||
|
|||
impl<C: CurveGroup> NovaR1CS<C> for R1CS<C::ScalarField>
|
|||
where
|
|||
<C as Group>::ScalarField: Absorb,
|
|||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
|||
{
|
|||
fn dummy_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
|||
impl<C: CurveGroup> RelaxedR1CS<C, Witness<C>, CommittedInstance<C>> for R1CS<C::ScalarField> {
|
|||
fn dummy_running_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
|||
let w_len = self.A.n_cols - 1 - self.l;
|
|||
let w_dummy = Witness::<C>::dummy(w_len, self.A.n_rows);
|
|||
let u_dummy = CommittedInstance::<C>::dummy(self.l);
|
|||
(w_dummy, u_dummy)
|
|||
}
|
|||
|
|||
// notice that this method does not check the commitment correctness
|
|||
fn check_instance_relation(
|
|||
&self,
|
|||
W: &Witness<C>,
|
|||
U: &CommittedInstance<C>,
|
|||
fn dummy_incoming_instance(&self) -> (Witness<C>, CommittedInstance<C>) {
|
|||
self.dummy_running_instance()
|
|||
}
|
|||
|
|||
fn is_relaxed(_w: &Witness<C>, u: &CommittedInstance<C>) -> bool {
|
|||
u.cmE != C::zero() || u.u != C::ScalarField::one()
|
|||
}
|
|||
|
|||
fn extract_z(w: &Witness<C>, u: &CommittedInstance<C>) -> Vec<C::ScalarField> {
|
|||
[&[u.u][..], &u.x, &w.W].concat()
|
|||
}
|
|||
|
|||
fn check_error_terms(
|
|||
w: &Witness<C>,
|
|||
_u: &CommittedInstance<C>,
|
|||
e: Vec<C::ScalarField>,
|
|||
) -> Result<(), Error> {
|
|||
if U.cmE != C::zero() || U.u != C::ScalarField::one() {
|
|||
return Err(Error::R1CSUnrelaxedFail);
|
|||
if w.E == e {
|
|||
Ok(())
|
|||
} else {
|
|||
Err(Error::NotSatisfied)
|
|||
}
|
|||
|
|||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
|||
self.check_relation(&Z)
|
|||
}
|
|||
|
|||
// notice that this method does not check the commitment correctness
|
|||
fn check_relaxed_instance_relation(
|
|||
fn sample<CS>(
|
|||
&self,
|
|||
W: &Witness<C>,
|
|||
U: &CommittedInstance<C>,
|
|||
) -> Result<(), Error> {
|
|||
let mut rel_r1cs = self.clone().relax();
|
|||
rel_r1cs.u = U.u;
|
|||
rel_r1cs.E = W.E.clone();
|
|||
params: &CS::ProverParams,
|
|||
mut rng: impl RngCore,
|
|||
) -> Result<(Witness<C>, CommittedInstance<C>), Error>
|
|||
where
|
|||
CS: crate::commitment::CommitmentScheme<C, true>,
|
|||
{
|
|||
// Implements sampling a (committed) RelaxedR1CS
|
|||
// See construction 5 in https://eprint.iacr.org/2023/573.pdf
|
|||
let u = C::ScalarField::rand(&mut rng);
|
|||
let rE = C::ScalarField::rand(&mut rng);
|
|||
let rW = C::ScalarField::rand(&mut rng);
|
|||
|
|||
let W = (0..self.A.n_cols - self.l - 1)
|
|||
.map(|_| C::ScalarField::rand(&mut rng))
|
|||
.collect();
|
|||
let x = (0..self.l)
|
|||
.map(|_| C::ScalarField::rand(&mut rng))
|
|||
.collect::<Vec<C::ScalarField>>();
|
|||
let mut z = vec![u];
|
|||
z.extend(&x);
|
|||
z.extend(&W);
|
|||
|
|||
let E = <Self as RelaxedR1CS<C, Witness<C>, CommittedInstance<C>>>::compute_E(
|
|||
&self.A, &self.B, &self.C, &z, &u,
|
|||
)?;
|
|||
|
|||
debug_assert!(
|
|||
z.len() == self.A.n_cols,
|
|||
"Length of z is {}, while A has {} columns.",
|
|||
z.len(),
|
|||
self.A.n_cols
|
|||
);
|
|||
|
|||
let witness = Witness { E, rE, W, rW };
|
|||
let mut cm_witness = witness.commit::<CS, true>(params, x)?;
|
|||
|
|||
// witness.commit() sets u to 1, we set it to the sampled u value
|
|||
cm_witness.u = u;
|
|||
|
|||
debug_assert!(
|
|||
self.check_relaxed_relation(&witness, &cm_witness).is_ok(),
|
|||
"Sampled a non satisfiable relaxed R1CS, sampled u: {}, computed E: {:?}",
|
|||
u,
|
|||
witness.E
|
|||
);
|
|||
|
|||
let Z: Vec<C::ScalarField> = [vec![U.u], U.x.to_vec(), W.W.to_vec()].concat();
|
|||
rel_r1cs.check_relation(&Z)
|
|||
Ok((witness, cm_witness))
|
|||
}
|
|||
}
|
@ -0,0 +1,181 @@ |
|||
use ark_ff::PrimeField;
|
|||
use ark_r1cs_std::{alloc::AllocVar, fields::fp::FpVar};
|
|||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
|||
#[cfg(test)]
|
|||
use ark_std::marker::PhantomData;
|
|||
use ark_std::{fmt::Debug, Zero};
|
|||
|
|||
use super::FCircuit;
|
|||
use crate::Error;
|
|||
|
|||
/// DummyCircuit is a circuit that has dummy state and external inputs whose
|
|||
/// lengths are specified in the `state_len` and `external_inputs_len`
|
|||
/// parameters, without any constraints.
|
|||
#[derive(Clone, Debug)]
|
|||
pub struct DummyCircuit {
|
|||
state_len: usize,
|
|||
external_inputs_len: usize,
|
|||
}
|
|||
impl<F: PrimeField> FCircuit<F> for DummyCircuit {
|
|||
type Params = (usize, usize);
|
|||
|
|||
fn new((state_len, external_inputs_len): Self::Params) -> Result<Self, Error> {
|
|||
Ok(Self {
|
|||
state_len,
|
|||
external_inputs_len,
|
|||
})
|
|||
}
|
|||
fn state_len(&self) -> usize {
|
|||
self.state_len
|
|||
}
|
|||
fn external_inputs_len(&self) -> usize {
|
|||
self.external_inputs_len
|
|||
}
|
|||
fn step_native(
|
|||
&self,
|
|||
_i: usize,
|
|||
_z_i: Vec<F>,
|
|||
_external_inputs: Vec<F>,
|
|||
) -> Result<Vec<F>, Error> {
|
|||
Ok(vec![F::zero(); self.state_len])
|
|||
}
|
|||
fn generate_step_constraints(
|
|||
&self,
|
|||
cs: ConstraintSystemRef<F>,
|
|||
_i: usize,
|
|||
_z_i: Vec<FpVar<F>>,
|
|||
_external_inputs: Vec<FpVar<F>>,
|
|||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
|||
Vec::new_witness(cs.clone(), || Ok(vec![Zero::zero(); self.state_len]))
|
|||
}
|
|||
}
|
|||
|
|||
/// CubicFCircuit is a struct that implements the FCircuit trait, for the R1CS example circuit
|
|||
/// from https://www.vitalik.ca/general/2016/12/10/qap.html, which checks `x^3 + x + 5 = y`.
|
|||
/// `z_i` is used as `x`, and `z_{i+1}` is used as `y`, and at the next step, `z_{i+1}` will be
|
|||
/// assigned to `z_i`, and a new `z+{i+1}` will be computted.
|
|||
#[cfg(test)]
|
|||
#[derive(Clone, Copy, Debug)]
|
|||
pub struct CubicFCircuit<F: PrimeField> {
|
|||
_f: PhantomData<F>,
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
|||
type Params = ();
|
|||
fn new(_params: Self::Params) -> Result<Self, Error> {
|
|||
Ok(Self { _f: PhantomData })
|
|||
}
|
|||
fn state_len(&self) -> usize {
|
|||
1
|
|||
}
|
|||
fn external_inputs_len(&self) -> usize {
|
|||
0
|
|||
}
|
|||
fn step_native(
|
|||
&self,
|
|||
_i: usize,
|
|||
z_i: Vec<F>,
|
|||
_external_inputs: Vec<F>,
|
|||
) -> Result<Vec<F>, Error> {
|
|||
Ok(vec![z_i[0] * z_i[0] * z_i[0] + z_i[0] + F::from(5_u32)])
|
|||
}
|
|||
fn generate_step_constraints(
|
|||
&self,
|
|||
cs: ConstraintSystemRef<F>,
|
|||
_i: usize,
|
|||
z_i: Vec<FpVar<F>>,
|
|||
_external_inputs: Vec<FpVar<F>>,
|
|||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
|||
let five = FpVar::<F>::new_constant(cs.clone(), F::from(5u32))?;
|
|||
let z_i = z_i[0].clone();
|
|||
|
|||
Ok(vec![&z_i * &z_i * &z_i + &z_i + &five])
|
|||
}
|
|||
}
|
|||
|
|||
/// CustomFCircuit is a circuit that has the number of constraints specified in the
|
|||
/// `n_constraints` parameter. Note that the generated circuit will have very sparse matrices.
|
|||
#[cfg(test)]
|
|||
#[derive(Clone, Copy, Debug)]
|
|||
pub struct CustomFCircuit<F: PrimeField> {
|
|||
_f: PhantomData<F>,
|
|||
pub n_constraints: usize,
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
impl<F: PrimeField> FCircuit<F> for CustomFCircuit<F> {
|
|||
type Params = usize;
|
|||
|
|||
fn new(params: Self::Params) -> Result<Self, Error> {
|
|||
Ok(Self {
|
|||
_f: PhantomData,
|
|||
n_constraints: params,
|
|||
})
|
|||
}
|
|||
fn state_len(&self) -> usize {
|
|||
1
|
|||
}
|
|||
fn external_inputs_len(&self) -> usize {
|
|||
0
|
|||
}
|
|||
fn step_native(
|
|||
&self,
|
|||
_i: usize,
|
|||
z_i: Vec<F>,
|
|||
_external_inputs: Vec<F>,
|
|||
) -> Result<Vec<F>, Error> {
|
|||
let mut z_i1 = F::one();
|
|||
for _ in 0..self.n_constraints - 1 {
|
|||
z_i1 *= z_i[0];
|
|||
}
|
|||
Ok(vec![z_i1])
|
|||
}
|
|||
fn generate_step_constraints(
|
|||
&self,
|
|||
cs: ConstraintSystemRef<F>,
|
|||
_i: usize,
|
|||
z_i: Vec<FpVar<F>>,
|
|||
_external_inputs: Vec<FpVar<F>>,
|
|||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
|||
let mut z_i1 = FpVar::<F>::new_witness(cs.clone(), || Ok(F::one()))?;
|
|||
for _ in 0..self.n_constraints - 1 {
|
|||
z_i1 *= z_i[0].clone();
|
|||
}
|
|||
|
|||
Ok(vec![z_i1])
|
|||
}
|
|||
}
|
|||
|
|||
/// WrapperCircuit is a circuit that wraps any circuit that implements the FCircuit trait. This
|
|||
/// is used to test the `FCircuit.generate_step_constraints` method. This is a similar wrapping
|
|||
/// than the one done in the `AugmentedFCircuit`, but without adding all the extra constraints
|
|||
/// of the AugmentedF circuit logic, in order to run lighter tests when we're not interested in
|
|||
/// the the AugmentedF logic but in the wrapping of the circuits.
|
|||
#[cfg(test)]
|
|||
pub struct WrapperCircuit<F: PrimeField, FC: FCircuit<F>> {
|
|||
pub FC: FC, // F circuit
|
|||
pub z_i: Option<Vec<F>>,
|
|||
pub z_i1: Option<Vec<F>>,
|
|||
}
|
|||
|
|||
#[cfg(test)]
|
|||
impl<F, FC> ark_relations::r1cs::ConstraintSynthesizer<F> for WrapperCircuit<F, FC>
|
|||
where
|
|||
F: PrimeField,
|
|||
FC: FCircuit<F>,
|
|||
{
|
|||
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
|
|||
let z_i =
|
|||
Vec::<FpVar<F>>::new_witness(cs.clone(), || Ok(self.z_i.unwrap_or(vec![F::zero()])))?;
|
|||
let z_i1 =
|
|||
Vec::<FpVar<F>>::new_input(cs.clone(), || Ok(self.z_i1.unwrap_or(vec![F::zero()])))?;
|
|||
let computed_z_i1 =
|
|||
self.FC
|
|||
.generate_step_constraints(cs.clone(), 0, z_i.clone(), vec![])?;
|
|||
|
|||
use ark_r1cs_std::eq::EqGadget;
|
|||
computed_z_i1.enforce_equal(&z_i1)?;
|
|||
Ok(())
|
|||
}
|
|||
}
|