mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-11 16:31:32 +01:00
Add solidity verifier of the nova+cyclefold (#87)
* Add solidity verifier of the nova+cyclefold, and add method to prepare the calldata from Decider's proof. Missing conversion of the point coordinates into limbs (ark compatible) * chore: adding comments linking to the contract's signature * chore: update .gitignore * chore: add num-bigint as dev dependency * fix: work with abs path for storing generated sol code * chore: update comment * feat: solidity verifier working on single and multi-input circuits * feat: multi-input folding verification working + fixing encoding of additive identity in calldata * chore: make bigint a dependency * refactor: import utils functions from utils.rs and make them available from anywhere * chore: make utils and evm available publicly * fix: pub mod instead * chore: make relevant method public and add `get_decider_template_for_cyclefold_decider` to exported objects * solidity-verifiers: move tests to their corresponding files * small update: Cyclefold -> CycleFold at the missing places * abstract nova-cyclefold solidity verifiers tests to avoid code duplication, and abstract also the computed setup params (FS & Decider) to compute them only once for all related tests to save test time * small polish after rebase to last main branch changes * rm unneeded Option for KZGData::g1_crs_batch_points * add checks modifying z_0 & z_i to nova_cyclefold_solidity_verifier test * add light-test feature to decider_eth_circuit to use it in solidity-verifier tests without the big circuit * solidity-verifiers: groth16 template: port the fix from https://github.com/iden3/snarkjs/pull/480 & https://github.com/iden3/snarkjs/issues/479 * add print warning msg for light-test in DeciderEthCircuit * solidity-verifiers: update limbs logic to nonnative last version, parametrize limbs params solidity-verifiers: * update solidity limbs logic to last nonnative impl version, and to last u_i.x impl * parametrize limbs params * add light-test feature: replace the '#[cfg(not(test))]' by the 'light-test' feature that by default is not enabled, so when running the github actions we enable the feature 'light-tests', and then we can have a full-test that runs the test without the 'light-tests' flag, but we don't run this big test every time. The choice of a feature is to allow us to control this from other-crates tests (for example for the solidity-verifier separated crate tests, to avoid running the full heavy circuit in the solidity tests) * move solidity constants into template constants for auto compute of params * polishing * revm use only needed feature This is to avoid c depencency for c-kzg which is behind the c-kzg flag and not needed. * nova_cyclefold_decider.sol header * rearrange test helpers position, add error for min number of steps * in solidity-verifiers: 'data'->'vk/verifier key' * add From for NovaCycleFoldVerifierKey from original vks to simplify dev flow, also conditionally template the batchCheck related structs and methods from the KZG10 solidity template --------- Co-authored-by: dmpierre <pdaixmoreux@gmail.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr};
|
||||
use crate::utils::encoding::{G1Repr, G2Repr};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{ProtocolData, GPL3_SDPX_IDENTIFIER};
|
||||
use crate::{ProtocolVerifierKey, GPL3_SDPX_IDENTIFIER};
|
||||
use ark_bn254::Bn254;
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
@@ -26,15 +26,15 @@ pub(crate) struct Groth16Verifier {
|
||||
pub(crate) gamma_abc_g1: Vec<G1Repr>,
|
||||
}
|
||||
|
||||
impl From<Groth16Data> for Groth16Verifier {
|
||||
fn from(g16_data: Groth16Data) -> Self {
|
||||
impl From<Groth16VerifierKey> for Groth16Verifier {
|
||||
fn from(g16_vk: Groth16VerifierKey) -> Self {
|
||||
Self {
|
||||
vkey_alpha_g1: g1_to_fq_repr(g16_data.0.alpha_g1),
|
||||
vkey_beta_g2: g2_to_fq_repr(g16_data.0.beta_g2),
|
||||
vkey_gamma_g2: g2_to_fq_repr(g16_data.0.gamma_g2),
|
||||
vkey_delta_g2: g2_to_fq_repr(g16_data.0.delta_g2),
|
||||
gamma_abc_len: g16_data.0.gamma_abc_g1.len(),
|
||||
gamma_abc_g1: g16_data
|
||||
vkey_alpha_g1: g1_to_fq_repr(g16_vk.0.alpha_g1),
|
||||
vkey_beta_g2: g2_to_fq_repr(g16_vk.0.beta_g2),
|
||||
vkey_gamma_g2: g2_to_fq_repr(g16_vk.0.gamma_g2),
|
||||
vkey_delta_g2: g2_to_fq_repr(g16_vk.0.delta_g2),
|
||||
gamma_abc_len: g16_vk.0.gamma_abc_g1.len(),
|
||||
gamma_abc_g1: g16_vk
|
||||
.0
|
||||
.gamma_abc_g1
|
||||
.iter()
|
||||
@@ -45,18 +45,18 @@ impl From<Groth16Data> for Groth16Verifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Ideally I would like to link this to the `Decider` trait in FoldingSchemes.
|
||||
// For now, this is the easiest as NovaCyclefold isn't clear target from where we can get all it's needed arguments.
|
||||
// Ideally this would be linked to the `Decider` trait in FoldingSchemes.
|
||||
// For now, this is the easiest as NovaCycleFold isn't clear target from where we can get all it's needed arguments.
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct Groth16Data(pub(crate) VerifyingKey<Bn254>);
|
||||
pub struct Groth16VerifierKey(pub(crate) VerifyingKey<Bn254>);
|
||||
|
||||
impl From<VerifyingKey<Bn254>> for Groth16Data {
|
||||
impl From<VerifyingKey<Bn254>> for Groth16VerifierKey {
|
||||
fn from(value: VerifyingKey<Bn254>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for Groth16Data {
|
||||
impl ProtocolVerifierKey for Groth16VerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "Groth16";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
@@ -70,3 +70,77 @@ impl ProtocolData for Groth16Data {
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Groth16VerifierKey;
|
||||
use crate::{
|
||||
evm::{compile_solidity, save_solidity, Evm},
|
||||
ProtocolVerifierKey,
|
||||
};
|
||||
use ark_bn254::{Bn254, Fr};
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_ec::AffineRepr;
|
||||
use ark_ff::{BigInt, BigInteger, PrimeField};
|
||||
use ark_groth16::Groth16;
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::test_rng;
|
||||
use askama::Template;
|
||||
use itertools::chain;
|
||||
|
||||
use super::Groth16Verifier;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
|
||||
pub const FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
|
||||
|
||||
#[test]
|
||||
fn groth16_vk_serde_roundtrip() {
|
||||
let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let g16_vk = Groth16VerifierKey::from(vk);
|
||||
let mut bytes = vec![];
|
||||
g16_vk.serialize_protocol_verifier_key(&mut bytes).unwrap();
|
||||
let obtained_g16_vk =
|
||||
Groth16VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(g16_vk, obtained_g16_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_groth16_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_vk = Groth16VerifierKey::from(g16_vk);
|
||||
|
||||
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
let res = Groth16Verifier::from(g16_vk).render().unwrap();
|
||||
save_solidity("groth16_verifier.sol", &res);
|
||||
let groth16_verifier_bytecode = compile_solidity(&res, "Verifier");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(groth16_verifier_bytecode);
|
||||
let (a_x, a_y) = proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = proof.c.xy().unwrap();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SELECTOR_GROTH16_VERIFY_PROOF,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
]
|
||||
.collect();
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr};
|
||||
use crate::utils::encoding::{G1Repr, G2Repr};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{ProtocolData, MIT_SDPX_IDENTIFIER};
|
||||
use crate::{ProtocolVerifierKey, MIT_SDPX_IDENTIFIER};
|
||||
use ark_bn254::{Bn254, G1Affine};
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
@@ -24,16 +24,15 @@ pub(crate) struct KZG10Verifier {
|
||||
pub(crate) g1_crs: Vec<G1Repr>,
|
||||
}
|
||||
|
||||
impl From<KzgData> for KZG10Verifier {
|
||||
fn from(data: KzgData) -> Self {
|
||||
let g1_crs_batch_points = data.g1_crs_batch_points.unwrap_or_default();
|
||||
|
||||
impl From<KZG10VerifierKey> for KZG10Verifier {
|
||||
fn from(data: KZG10VerifierKey) -> Self {
|
||||
Self {
|
||||
g1: g1_to_fq_repr(data.vk.g),
|
||||
g2: g2_to_fq_repr(data.vk.h),
|
||||
vk: g2_to_fq_repr(data.vk.beta_h),
|
||||
g1_crs_len: g1_crs_batch_points.len(),
|
||||
g1_crs: g1_crs_batch_points
|
||||
g1_crs_len: data.g1_crs_batch_points.len(),
|
||||
g1_crs: data
|
||||
.g1_crs_batch_points
|
||||
.iter()
|
||||
.map(|g1| g1_to_fq_repr(*g1))
|
||||
.collect(),
|
||||
@@ -42,13 +41,13 @@ impl From<KzgData> for KZG10Verifier {
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)]
|
||||
pub struct KzgData {
|
||||
pub struct KZG10VerifierKey {
|
||||
pub(crate) vk: VerifierKey<Bn254>,
|
||||
pub(crate) g1_crs_batch_points: Option<Vec<G1Affine>>,
|
||||
pub(crate) g1_crs_batch_points: Vec<G1Affine>,
|
||||
}
|
||||
|
||||
impl From<(VerifierKey<Bn254>, Option<Vec<G1Affine>>)> for KzgData {
|
||||
fn from(value: (VerifierKey<Bn254>, Option<Vec<G1Affine>>)) -> Self {
|
||||
impl From<(VerifierKey<Bn254>, Vec<G1Affine>)> for KZG10VerifierKey {
|
||||
fn from(value: (VerifierKey<Bn254>, Vec<G1Affine>)) -> Self {
|
||||
Self {
|
||||
vk: value.0,
|
||||
g1_crs_batch_points: value.1,
|
||||
@@ -56,7 +55,7 @@ impl From<(VerifierKey<Bn254>, Option<Vec<G1Affine>>)> for KzgData {
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolData for KzgData {
|
||||
impl ProtocolVerifierKey for KZG10VerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "KZG";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
@@ -70,3 +69,118 @@ impl ProtocolData for KzgData {
|
||||
.into_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::KZG10VerifierKey;
|
||||
use crate::{
|
||||
evm::{compile_solidity, Evm},
|
||||
utils::HeaderInclusion,
|
||||
ProtocolVerifierKey,
|
||||
};
|
||||
use ark_bn254::{Bn254, Fr, G1Projective as G1};
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ff::{BigInteger, PrimeField};
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::Zero;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
use askama::Template;
|
||||
use itertools::chain;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{kzg::KZG, CommitmentScheme},
|
||||
transcript::{
|
||||
poseidon::{poseidon_test_config, PoseidonTranscript},
|
||||
Transcript,
|
||||
},
|
||||
};
|
||||
|
||||
use super::KZG10Verifier;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
|
||||
const FUNCTION_SELECTOR_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
|
||||
|
||||
#[test]
|
||||
fn kzg_vk_serde_roundtrip() {
|
||||
let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let kzg_vk = KZG10VerifierKey::from((vk, pk.powers_of_g[0..3].to_vec()));
|
||||
let mut bytes = vec![];
|
||||
kzg_vk.serialize_protocol_verifier_key(&mut bytes).unwrap();
|
||||
let obtained_kzg_vk =
|
||||
KZG10VerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(kzg_vk, obtained_kzg_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(res, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(kzg_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_vk = KZG10VerifierKey::from((kzg_vk.clone(), kzg_pk.powers_of_g[0..3].to_vec()));
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
let template = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(kzg_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SELECTOR_KZG10_CHECK,
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ mod g16;
|
||||
mod kzg;
|
||||
mod nova_cyclefold;
|
||||
|
||||
pub use g16::Groth16Data;
|
||||
pub use kzg::KzgData;
|
||||
pub use nova_cyclefold::NovaCyclefoldData;
|
||||
pub use g16::Groth16VerifierKey;
|
||||
pub use kzg::KZG10VerifierKey;
|
||||
pub use nova_cyclefold::{get_decider_template_for_cyclefold_decider, NovaCycleFoldVerifierKey};
|
||||
|
||||
pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
pub trait ProtocolVerifierKey: CanonicalDeserialize + CanonicalSerialize {
|
||||
const PROTOCOL_NAME: &'static str;
|
||||
|
||||
fn serialize_name<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
@@ -27,11 +27,14 @@ pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
.serialize_uncompressed(writer)
|
||||
}
|
||||
|
||||
fn serialize_protocol_data<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
|
||||
fn serialize_protocol_verifier_key<W: Write>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
) -> Result<(), SerializationError> {
|
||||
self.serialize_name(writer)?;
|
||||
self.serialize_compressed(writer)
|
||||
}
|
||||
fn deserialize_protocol_data<R: Read + Copy>(
|
||||
fn deserialize_protocol_verifier_key<R: Read + Copy>(
|
||||
mut reader: R,
|
||||
) -> Result<Self, SerializationError> {
|
||||
let name: String = String::deserialize_uncompressed(&mut reader)?;
|
||||
@@ -48,51 +51,31 @@ pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::evm::{compile_solidity, save_solidity, Evm};
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
|
||||
pub mod tests {
|
||||
use ark_bn254::{Bn254, Fr, G1Projective as G1};
|
||||
use ark_crypto_primitives::snark::{CircuitSpecificSetupSNARK, SNARK};
|
||||
use ark_ec::{AffineRepr, CurveGroup};
|
||||
use ark_ff::{BigInt, BigInteger, PrimeField};
|
||||
use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_groth16::Groth16;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::eq::EqGadget;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::rand::{RngCore, SeedableRng};
|
||||
use ark_std::Zero;
|
||||
use ark_std::{test_rng, UniformRand};
|
||||
use askama::Template;
|
||||
use folding_schemes::{
|
||||
commitment::{
|
||||
kzg::{ProverKey, KZG},
|
||||
CommitmentScheme,
|
||||
},
|
||||
transcript::{
|
||||
poseidon::{poseidon_test_config, PoseidonTranscript},
|
||||
Transcript,
|
||||
},
|
||||
};
|
||||
use itertools::chain;
|
||||
use ark_std::test_rng;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::g16::Groth16Verifier;
|
||||
use super::kzg::KZG10Verifier;
|
||||
use super::nova_cyclefold::NovaCyclefoldDecider;
|
||||
|
||||
// Function signatures for proof verification on kzg10 and groth16 contracts
|
||||
pub const FUNCTION_SIGNATURE_KZG10_CHECK: [u8; 4] = [0x9e, 0x78, 0xcc, 0xf7];
|
||||
pub const FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF: [u8; 4] = [0x43, 0x75, 0x3b, 0x4d];
|
||||
pub const FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK: [u8; 4] = [0x37, 0x98, 0x0b, 0xb6];
|
||||
use folding_schemes::commitment::{
|
||||
kzg::{ProverKey as KZGProverKey, KZG},
|
||||
CommitmentScheme,
|
||||
};
|
||||
|
||||
/// Default setup length for testing.
|
||||
const DEFAULT_SETUP_LEN: usize = 5;
|
||||
pub const DEFAULT_SETUP_LEN: usize = 5;
|
||||
|
||||
/// Test circuit used to test the Groth16 proof generation
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct TestAddCircuit<F: PrimeField> {
|
||||
pub struct TestAddCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
pub x: u8,
|
||||
pub y: u8,
|
||||
@@ -111,11 +94,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn setup<'a>(
|
||||
pub fn setup<'a>(
|
||||
n: usize,
|
||||
) -> (
|
||||
ProverKey<'a, G1>,
|
||||
VerifierKey<Bn254>,
|
||||
KZGProverKey<'a, G1>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ark_groth16::ProvingKey<Bn254>,
|
||||
ark_groth16::VerifyingKey<Bn254>,
|
||||
TestAddCircuit<Fr>,
|
||||
@@ -130,297 +113,8 @@ mod tests {
|
||||
};
|
||||
let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
|
||||
|
||||
let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
|
||||
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
|
||||
KZG::<Bn254>::setup(&mut rng, n).unwrap();
|
||||
(kzg_pk, kzg_vk, g16_pk, g16_vk, circuit)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn groth16_data_serde_roundtrip() {
|
||||
let (_, _, _, vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let g16_data = Groth16Data::from(vk);
|
||||
let mut bytes = vec![];
|
||||
g16_data.serialize_protocol_data(&mut bytes).unwrap();
|
||||
let obtained_g16_data = Groth16Data::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(g16_data, obtained_g16_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_data_serde_roundtrip() {
|
||||
let (pk, vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let kzg_data = KzgData::from((vk, Some(pk.powers_of_g[0..3].to_vec())));
|
||||
let mut bytes = vec![];
|
||||
kzg_data.serialize_protocol_data(&mut bytes).unwrap();
|
||||
let obtained_kzg_data = KzgData::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(kzg_data, obtained_kzg_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_data_serde_roundtrip() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let mut bytes = vec![];
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
nova_cyclefold_data
|
||||
.serialize_protocol_data(&mut bytes)
|
||||
.unwrap();
|
||||
let obtained_nova_cyclefold_data =
|
||||
NovaCyclefoldData::deserialize_protocol_data(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(nova_cyclefold_data, obtained_nova_cyclefold_data)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_renders() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build();
|
||||
|
||||
save_solidity("NovaDecider.sol", &decider_template.render().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build();
|
||||
let decider_verifier_bytecode =
|
||||
compile_solidity(decider_template.render().unwrap(), "NovaDecider");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(decider_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_groth16_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (_, _, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
|
||||
let proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
let res = Groth16Verifier::from(g16_data).render().unwrap();
|
||||
save_solidity("groth16_verifier.sol", &res);
|
||||
let groth16_verifier_bytecode = compile_solidity(&res, "Verifier");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(groth16_verifier_bytecode);
|
||||
let (a_x, a_y) = proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = proof.c.xy().unwrap();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_GROTH16_VERIFY_PROOF,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
]
|
||||
.collect();
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_template_renders() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
// TODO: Unsure what this is testing. If we want to test correct rendering,
|
||||
// we should first check that it COMPLETELY renders to what we expect.
|
||||
assert!(res.contains(&kzg_vk.g.x.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let res = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(res, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(kzg_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kzg_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let (kzg_pk, kzg_vk, _, _, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let kzg_data = KzgData::from((kzg_vk.clone(), Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
let template = HeaderInclusion::<KZG10Verifier>::builder()
|
||||
.template(kzg_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let kzg_verifier_bytecode = compile_solidity(template, "KZG10");
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(kzg_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_KZG10_CHECK,
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_verifier_compiles() {
|
||||
let (kzg_pk, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
|
||||
let mut evm = Evm::default();
|
||||
_ = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_verifier_accepts_and_rejects_proofs() {
|
||||
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
|
||||
let (kzg_pk, kzg_vk, g16_pk, g16_vk, circuit) = setup(DEFAULT_SETUP_LEN);
|
||||
let g16_data = Groth16Data::from(g16_vk);
|
||||
let kzg_data = KzgData::from((kzg_vk, Some(kzg_pk.powers_of_g[0..3].to_vec())));
|
||||
let nova_cyclefold_data = NovaCyclefoldData::from((g16_data, kzg_data));
|
||||
|
||||
let g16_proof = Groth16::<Bn254>::prove(&g16_pk, circuit, &mut rng).unwrap();
|
||||
|
||||
let (a_x, a_y) = g16_proof.a.xy().unwrap();
|
||||
let (b_x, b_y) = g16_proof.b.xy().unwrap();
|
||||
let (c_x, c_y) = g16_proof.c.xy().unwrap();
|
||||
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let transcript_p = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
let transcript_v = &mut PoseidonTranscript::<G1>::new(&poseidon_config);
|
||||
|
||||
let v: Vec<Fr> = std::iter::repeat_with(|| Fr::rand(&mut rng))
|
||||
.take(DEFAULT_SETUP_LEN)
|
||||
.collect();
|
||||
let cm = KZG::<Bn254>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
|
||||
let proof = KZG::<Bn254>::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap();
|
||||
|
||||
let decider_template = HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
.template(nova_cyclefold_data)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap();
|
||||
|
||||
let nova_cyclefold_verifier_bytecode = compile_solidity(decider_template, "NovaDecider");
|
||||
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
|
||||
let (cm_affine, proof_affine) = (cm.into_affine(), proof.proof.into_affine());
|
||||
let (x_comm, y_comm) = cm_affine.xy().unwrap();
|
||||
let (x_proof, y_proof) = proof_affine.xy().unwrap();
|
||||
let y = proof.eval.into_bigint().to_bytes_be();
|
||||
|
||||
transcript_v.absorb_point(&cm).unwrap();
|
||||
let x = transcript_v.get_challenge();
|
||||
|
||||
let x = x.into_bigint().to_bytes_be();
|
||||
let mut calldata: Vec<u8> = chain![
|
||||
FUNCTION_SIGNATURE_NOVA_CYCLEFOLD_CHECK,
|
||||
a_x.into_bigint().to_bytes_be(),
|
||||
a_y.into_bigint().to_bytes_be(),
|
||||
b_x.c1.into_bigint().to_bytes_be(),
|
||||
b_x.c0.into_bigint().to_bytes_be(),
|
||||
b_y.c1.into_bigint().to_bytes_be(),
|
||||
b_y.c0.into_bigint().to_bytes_be(),
|
||||
c_x.into_bigint().to_bytes_be(),
|
||||
c_y.into_bigint().to_bytes_be(),
|
||||
BigInt::from(Fr::from(circuit.z)).to_bytes_be(),
|
||||
x_comm.into_bigint().to_bytes_be(),
|
||||
y_comm.into_bigint().to_bytes_be(),
|
||||
x_proof.into_bigint().to_bytes_be(),
|
||||
y_proof.into_bigint().to_bytes_be(),
|
||||
x.clone(),
|
||||
y,
|
||||
]
|
||||
.collect();
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change calldata to make it invalid
|
||||
let last_calldata_element = calldata.last_mut().unwrap();
|
||||
*last_calldata_element = 0;
|
||||
let (_, output) = evm.call(verifier_address, calldata);
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,69 @@
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16Data, KzgData, ProtocolData, PRAGMA_GROTH16_VERIFIER};
|
||||
use ark_bn254::{Bn254, G1Affine};
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use ark_bn254::{Bn254, Fq, G1Affine};
|
||||
use ark_groth16::VerifyingKey;
|
||||
use ark_poly_commit::kzg10::VerifierKey;
|
||||
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
|
||||
use askama::Template;
|
||||
|
||||
use folding_schemes::folding::circuits::nonnative::uint::NonNativeUintVar;
|
||||
|
||||
use super::g16::Groth16Verifier;
|
||||
use super::kzg::KZG10Verifier;
|
||||
use crate::utils::HeaderInclusion;
|
||||
use crate::{Groth16VerifierKey, KZG10VerifierKey, ProtocolVerifierKey, PRAGMA_GROTH16_VERIFIER};
|
||||
|
||||
pub fn get_decider_template_for_cyclefold_decider(
|
||||
nova_cyclefold_vk: NovaCycleFoldVerifierKey,
|
||||
) -> String {
|
||||
HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.template(nova_cyclefold_vk)
|
||||
.build()
|
||||
.render()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[derive(Template, Default)]
|
||||
#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")]
|
||||
pub(crate) struct NovaCyclefoldDecider {
|
||||
pub(crate) struct NovaCycleFoldDecider {
|
||||
groth16_verifier: Groth16Verifier,
|
||||
kzg10_verifier: KZG10Verifier,
|
||||
// z_len denotes the FCircuit state (z_i) length
|
||||
z_len: usize,
|
||||
public_inputs_len: usize,
|
||||
num_limbs: usize,
|
||||
bits_per_limb: usize,
|
||||
}
|
||||
|
||||
impl From<NovaCyclefoldData> for NovaCyclefoldDecider {
|
||||
fn from(value: NovaCyclefoldData) -> Self {
|
||||
impl From<NovaCycleFoldVerifierKey> for NovaCycleFoldDecider {
|
||||
fn from(value: NovaCycleFoldVerifierKey) -> Self {
|
||||
let groth16_verifier = Groth16Verifier::from(value.g16_vk);
|
||||
let public_inputs_len = groth16_verifier.gamma_abc_len;
|
||||
let bits_per_limb = NonNativeUintVar::<Fq>::bits_per_limb();
|
||||
Self {
|
||||
groth16_verifier: Groth16Verifier::from(value.g16_data),
|
||||
kzg10_verifier: KZG10Verifier::from(value.kzg_data),
|
||||
groth16_verifier,
|
||||
kzg10_verifier: KZG10Verifier::from(value.kzg_vk),
|
||||
z_len: value.z_len,
|
||||
public_inputs_len,
|
||||
num_limbs: (250_f32 / (bits_per_limb as f32)).ceil() as usize,
|
||||
bits_per_limb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug)]
|
||||
pub struct NovaCyclefoldData {
|
||||
g16_data: Groth16Data,
|
||||
kzg_data: KzgData,
|
||||
#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug, Clone)]
|
||||
pub struct NovaCycleFoldVerifierKey {
|
||||
g16_vk: Groth16VerifierKey,
|
||||
kzg_vk: KZG10VerifierKey,
|
||||
z_len: usize,
|
||||
}
|
||||
|
||||
impl ProtocolData for NovaCyclefoldData {
|
||||
const PROTOCOL_NAME: &'static str = "NovaCyclefold";
|
||||
impl ProtocolVerifierKey for NovaCycleFoldVerifierKey {
|
||||
const PROTOCOL_NAME: &'static str = "NovaCycleFold";
|
||||
|
||||
fn render_as_template(self, pragma: Option<String>) -> Vec<u8> {
|
||||
HeaderInclusion::<NovaCyclefoldDecider>::builder()
|
||||
HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string()))
|
||||
.template(self)
|
||||
.build()
|
||||
@@ -45,25 +73,384 @@ impl ProtocolData for NovaCyclefoldData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Groth16Data, KzgData)> for NovaCyclefoldData {
|
||||
fn from(value: (Groth16Data, KzgData)) -> Self {
|
||||
impl From<(Groth16VerifierKey, KZG10VerifierKey, usize)> for NovaCycleFoldVerifierKey {
|
||||
fn from(value: (Groth16VerifierKey, KZG10VerifierKey, usize)) -> Self {
|
||||
Self {
|
||||
g16_data: value.0,
|
||||
kzg_data: value.1,
|
||||
g16_vk: value.0,
|
||||
kzg_vk: value.1,
|
||||
z_len: value.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NovaCyclefoldData {
|
||||
// implements From assuming that the 'batchCheck' method from the KZG10 template will not be used
|
||||
// in the NovaCycleFoldDecider verifier contract
|
||||
impl From<(VerifyingKey<Bn254>, VerifierKey<Bn254>, usize)> for NovaCycleFoldVerifierKey {
|
||||
fn from(value: (VerifyingKey<Bn254>, VerifierKey<Bn254>, usize)) -> Self {
|
||||
let g16_vk = Groth16VerifierKey::from(value.0);
|
||||
// pass `Vec::new()` since batchCheck will not be used
|
||||
let kzg_vk = KZG10VerifierKey::from((value.1, Vec::new()));
|
||||
Self {
|
||||
g16_vk,
|
||||
kzg_vk,
|
||||
z_len: value.2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NovaCycleFoldVerifierKey {
|
||||
pub fn new(
|
||||
vkey_g16: VerifyingKey<Bn254>,
|
||||
vkey_kzg: VerifierKey<Bn254>,
|
||||
crs_points: Vec<G1Affine>,
|
||||
z_len: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
g16_data: Groth16Data::from(vkey_g16),
|
||||
// TODO: Remove option from crs points
|
||||
kzg_data: KzgData::from((vkey_kzg, Some(crs_points))),
|
||||
g16_vk: Groth16VerifierKey::from(vkey_g16),
|
||||
kzg_vk: KZG10VerifierKey::from((vkey_kzg, crs_points)),
|
||||
z_len,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ark_bn254::{constraints::GVar, Bn254, Fr, G1Projective as G1};
|
||||
use ark_crypto_primitives::snark::SNARK;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_groth16::VerifyingKey as G16VerifierKey;
|
||||
use ark_groth16::{Groth16, ProvingKey};
|
||||
use ark_grumpkin::{constraints::GVar as GVar2, Projective as G2};
|
||||
use ark_poly_commit::kzg10::VerifierKey as KZGVerifierKey;
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::fields::fp::FpVar;
|
||||
use ark_relations::r1cs::{ConstraintSystemRef, SynthesisError};
|
||||
use ark_std::Zero;
|
||||
use askama::Template;
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Instant;
|
||||
|
||||
use folding_schemes::{
|
||||
commitment::{
|
||||
kzg::{ProverKey as KZGProverKey, KZG},
|
||||
pedersen::Pedersen,
|
||||
CommitmentScheme,
|
||||
},
|
||||
folding::nova::{
|
||||
decider_eth::{prepare_calldata, Decider as DeciderEth},
|
||||
decider_eth_circuit::DeciderEthCircuit,
|
||||
get_cs_params_len, Nova, ProverParams,
|
||||
},
|
||||
frontend::FCircuit,
|
||||
transcript::poseidon::poseidon_test_config,
|
||||
Decider, Error, FoldingScheme,
|
||||
};
|
||||
|
||||
use super::NovaCycleFoldDecider;
|
||||
use crate::verifiers::tests::{setup, DEFAULT_SETUP_LEN};
|
||||
use crate::{
|
||||
evm::{compile_solidity, save_solidity, Evm},
|
||||
utils::{get_function_selector_for_nova_cyclefold_verifier, HeaderInclusion},
|
||||
verifiers::nova_cyclefold::get_decider_template_for_cyclefold_decider,
|
||||
NovaCycleFoldVerifierKey, ProtocolVerifierKey,
|
||||
};
|
||||
|
||||
/// Test circuit to be folded
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CubicFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for CubicFCircuit<F> {
|
||||
type Params = ();
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
1
|
||||
}
|
||||
fn step_native(&self, _i: usize, z_i: 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>>,
|
||||
) -> 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])
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the circuit that we want to fold, it implements the FCircuit trait. The parameter z_i
|
||||
/// denotes the current state which contains 5 elements, and z_{i+1} denotes the next state which
|
||||
/// we get by applying the step.
|
||||
/// In this example we set z_i and z_{i+1} to have five elements, and at each step we do different
|
||||
/// operations on each of them.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct MultiInputsFCircuit<F: PrimeField> {
|
||||
_f: PhantomData<F>,
|
||||
}
|
||||
impl<F: PrimeField> FCircuit<F> for MultiInputsFCircuit<F> {
|
||||
type Params = ();
|
||||
|
||||
fn new(_params: Self::Params) -> Self {
|
||||
Self { _f: PhantomData }
|
||||
}
|
||||
fn state_len(&self) -> usize {
|
||||
5
|
||||
}
|
||||
|
||||
/// computes the next state values in place, assigning z_{i+1} into z_i, and computing the new
|
||||
/// z_{i+1}
|
||||
fn step_native(&self, _i: usize, z_i: Vec<F>) -> Result<Vec<F>, Error> {
|
||||
let a = z_i[0] + F::from(4_u32);
|
||||
let b = z_i[1] + F::from(40_u32);
|
||||
let c = z_i[2] * F::from(4_u32);
|
||||
let d = z_i[3] * F::from(40_u32);
|
||||
let e = z_i[4] + F::from(100_u32);
|
||||
|
||||
Ok(vec![a, b, c, d, e])
|
||||
}
|
||||
|
||||
/// generates the constraints for the step of F for the given z_i
|
||||
fn generate_step_constraints(
|
||||
&self,
|
||||
cs: ConstraintSystemRef<F>,
|
||||
_i: usize,
|
||||
z_i: Vec<FpVar<F>>,
|
||||
) -> Result<Vec<FpVar<F>>, SynthesisError> {
|
||||
let four = FpVar::<F>::new_constant(cs.clone(), F::from(4u32))?;
|
||||
let forty = FpVar::<F>::new_constant(cs.clone(), F::from(40u32))?;
|
||||
let onehundred = FpVar::<F>::new_constant(cs.clone(), F::from(100u32))?;
|
||||
let a = z_i[0].clone() + four.clone();
|
||||
let b = z_i[1].clone() + forty.clone();
|
||||
let c = z_i[2].clone() * four;
|
||||
let d = z_i[3].clone() * forty;
|
||||
let e = z_i[4].clone() + onehundred;
|
||||
|
||||
Ok(vec![a, b, c, d, e])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_vk_serde_roundtrip() {
|
||||
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
|
||||
let mut bytes = vec![];
|
||||
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, 1));
|
||||
|
||||
nova_cyclefold_vk
|
||||
.serialize_protocol_verifier_key(&mut bytes)
|
||||
.unwrap();
|
||||
let obtained_nova_cyclefold_vk =
|
||||
NovaCycleFoldVerifierKey::deserialize_protocol_verifier_key(bytes.as_slice()).unwrap();
|
||||
|
||||
assert_eq!(nova_cyclefold_vk, obtained_nova_cyclefold_vk)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_decider_template_renders() {
|
||||
let (_, kzg_vk, _, g16_vk, _) = setup(DEFAULT_SETUP_LEN);
|
||||
let nova_cyclefold_vk = NovaCycleFoldVerifierKey::from((g16_vk, kzg_vk, 1));
|
||||
|
||||
let decider_solidity_code = HeaderInclusion::<NovaCycleFoldDecider>::builder()
|
||||
.template(nova_cyclefold_vk)
|
||||
.build();
|
||||
|
||||
save_solidity("NovaDecider.sol", &decider_solidity_code.render().unwrap());
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn init_test_prover_params<FC: FCircuit<Fr, Params = ()>>() -> (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
) {
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
let f_circuit = FC::new(());
|
||||
let (cs_len, cf_cs_len) =
|
||||
get_cs_params_len::<G1, GVar, G2, GVar2, FC>(&poseidon_config, f_circuit).unwrap();
|
||||
let (kzg_pk, kzg_vk): (KZGProverKey<G1>, KZGVerifierKey<Bn254>) =
|
||||
KZG::<Bn254>::setup(&mut rng, cs_len).unwrap();
|
||||
let (cf_pedersen_params, _) = Pedersen::<G2>::setup(&mut rng, cf_cs_len).unwrap();
|
||||
let fs_prover_params = ProverParams::<G1, G2, KZG<Bn254>, Pedersen<G2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cs_params: kzg_pk.clone(),
|
||||
cf_cs_params: cf_pedersen_params,
|
||||
};
|
||||
(fs_prover_params, kzg_vk)
|
||||
}
|
||||
|
||||
/// Initializes Nova parameters and DeciderEth parameters. Only for test purposes.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn init_params<FC: FCircuit<Fr, Params = ()>>() -> (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ProvingKey<Bn254>,
|
||||
G16VerifierKey<Bn254>,
|
||||
) {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let start = Instant::now();
|
||||
let (fs_prover_params, kzg_vk) = init_test_prover_params::<FC>();
|
||||
println!("generated Nova folding params: {:?}", start.elapsed());
|
||||
let f_circuit = FC::new(());
|
||||
|
||||
pub type NOVA_FCircuit<FC> =
|
||||
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
|
||||
let z_0 = vec![Fr::zero(); f_circuit.state_len()];
|
||||
let nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0.clone()).unwrap();
|
||||
|
||||
let decider_circuit =
|
||||
DeciderEthCircuit::<G1, GVar, G2, GVar2, KZG<Bn254>, Pedersen<G2>>::from_nova::<FC>(
|
||||
nova.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let start = Instant::now();
|
||||
let (g16_pk, g16_vk) =
|
||||
Groth16::<Bn254>::circuit_specific_setup(decider_circuit.clone(), &mut rng).unwrap();
|
||||
println!(
|
||||
"generated G16 (Decider circuit) params: {:?}",
|
||||
start.elapsed()
|
||||
);
|
||||
(fs_prover_params, kzg_vk, g16_pk, g16_vk)
|
||||
}
|
||||
|
||||
/// This function allows to define which FCircuit to use for the test, and how many prove_step
|
||||
/// rounds to perform.
|
||||
/// Actions performed by this test:
|
||||
/// - runs the NovaCycleFold folding scheme for the given FCircuit and n_steps times
|
||||
/// - generates a DeciderEth proof, and executes it through the EVM
|
||||
/// - modifies the calldata and checks that it does not pass the EVM check
|
||||
/// - modifies the z_0 and checks that it does not pass the EVM check
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn nova_cyclefold_solidity_verifier_opt<FC: FCircuit<Fr, Params = ()>>(
|
||||
params: (
|
||||
ProverParams<G1, G2, KZG<'static, Bn254>, Pedersen<G2>>,
|
||||
KZGVerifierKey<Bn254>,
|
||||
ProvingKey<Bn254>,
|
||||
G16VerifierKey<Bn254>,
|
||||
),
|
||||
z_0: Vec<Fr>,
|
||||
n_steps: usize,
|
||||
) {
|
||||
let (fs_prover_params, kzg_vk, g16_pk, g16_vk) = params.clone();
|
||||
|
||||
pub type NOVA_FCircuit<FC> =
|
||||
Nova<G1, GVar, G2, GVar2, FC, KZG<'static, Bn254>, Pedersen<G2>>;
|
||||
pub type DECIDERETH_FCircuit<FC> = DeciderEth<
|
||||
G1,
|
||||
GVar,
|
||||
G2,
|
||||
GVar2,
|
||||
FC,
|
||||
KZG<'static, Bn254>,
|
||||
Pedersen<G2>,
|
||||
Groth16<Bn254>,
|
||||
NOVA_FCircuit<FC>,
|
||||
>;
|
||||
let f_circuit = FC::new(());
|
||||
|
||||
let nova_cyclefold_vk =
|
||||
NovaCycleFoldVerifierKey::from((g16_vk.clone(), kzg_vk.clone(), f_circuit.state_len()));
|
||||
|
||||
let mut nova = NOVA_FCircuit::init(&fs_prover_params, f_circuit, z_0).unwrap();
|
||||
for _ in 0..n_steps {
|
||||
nova.prove_step().unwrap();
|
||||
}
|
||||
|
||||
let rng = rand::rngs::OsRng;
|
||||
let start = Instant::now();
|
||||
let proof = DECIDERETH_FCircuit::prove(
|
||||
(g16_pk, fs_prover_params.cs_params.clone()),
|
||||
rng,
|
||||
nova.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
println!("generated Decider proof: {:?}", start.elapsed());
|
||||
|
||||
let verified = DECIDERETH_FCircuit::<FC>::verify(
|
||||
(g16_vk, kzg_vk),
|
||||
nova.i,
|
||||
nova.z_0.clone(),
|
||||
nova.z_i.clone(),
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
&proof,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(verified);
|
||||
|
||||
let function_selector =
|
||||
get_function_selector_for_nova_cyclefold_verifier(nova.z_0.len() * 2 + 1);
|
||||
|
||||
let calldata: Vec<u8> = prepare_calldata(
|
||||
function_selector,
|
||||
nova.i,
|
||||
nova.z_0,
|
||||
nova.z_i,
|
||||
&nova.U_i,
|
||||
&nova.u_i,
|
||||
proof,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let decider_solidity_code = get_decider_template_for_cyclefold_decider(nova_cyclefold_vk);
|
||||
|
||||
let nova_cyclefold_verifier_bytecode =
|
||||
compile_solidity(decider_solidity_code, "NovaDecider");
|
||||
|
||||
let mut evm = Evm::default();
|
||||
let verifier_address = evm.create(nova_cyclefold_verifier_bytecode);
|
||||
|
||||
let (_, output) = evm.call(verifier_address, calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 1);
|
||||
|
||||
// change i to make calldata invalid, placed between bytes 4 - 35
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[35] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
|
||||
// change z_0 to make the EVM check fail, placed between bytes 35 - 67
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[67] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
|
||||
// change z_i to make the EVM check fail, placed between bytes 68 - 100
|
||||
let mut invalid_calldata = calldata.clone();
|
||||
invalid_calldata[99] += 1;
|
||||
let (_, output) = evm.call(verifier_address, invalid_calldata.clone());
|
||||
assert_eq!(*output.last().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nova_cyclefold_solidity_verifier() {
|
||||
let params = init_params::<CubicFCircuit<Fr>>();
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 2);
|
||||
nova_cyclefold_solidity_verifier_opt::<CubicFCircuit<Fr>>(params.clone(), z_0.clone(), 3);
|
||||
|
||||
let params = init_params::<MultiInputsFCircuit<Fr>>();
|
||||
let z_0 = vec![
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
Fr::from(1_u32),
|
||||
];
|
||||
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
|
||||
params.clone(),
|
||||
z_0.clone(),
|
||||
2,
|
||||
);
|
||||
nova_cyclefold_solidity_verifier_opt::<MultiInputsFCircuit<Fr>>(
|
||||
params.clone(),
|
||||
z_0.clone(),
|
||||
3,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user