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:
2024-04-25 11:51:59 +02:00
committed by GitHub
parent 8b233031a6
commit 97df224579
24 changed files with 1019 additions and 482 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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,
);
}
}