You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

428 lines
17 KiB

//! Solidity templates for the verifier contracts.
//! We use askama for templating and define which variables are required for each template.
// Pragma statements for verifiers
pub(crate) const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing
pub(crate) const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;";
/// Default SDPX License identifier
pub(crate) const GPL3_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: GPL-3.0";
pub(crate) const MIT_SDPX_IDENTIFIER: &str = "// SPDX-License-Identifier: MIT";
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Read, SerializationError, Write};
mod g16;
mod kzg;
mod nova_cyclefold;
pub use g16::Groth16Data;
pub use kzg::KzgData;
pub use nova_cyclefold::NovaCyclefoldData;
pub trait ProtocolData: CanonicalDeserialize + CanonicalSerialize {
const PROTOCOL_NAME: &'static str;
fn serialize_name<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
Self::PROTOCOL_NAME
.to_string()
.serialize_uncompressed(writer)
}
fn serialize_protocol_data<W: Write>(&self, writer: &mut W) -> Result<(), SerializationError> {
self.serialize_name(writer)?;
self.serialize_compressed(writer)
}
fn deserialize_protocol_data<R: Read + Copy>(
mut reader: R,
) -> Result<Self, SerializationError> {
let name: String = String::deserialize_uncompressed(&mut reader)?;
let data = Self::deserialize_compressed(&mut reader)?;
if name != Self::PROTOCOL_NAME {
return Err(SerializationError::InvalidData);
}
Ok(data)
}
fn render_as_template(self, pragma: Option<String>) -> Vec<u8>;
}
#[cfg(test)]
mod tests {
use crate::evm::{compile_solidity, save_solidity, Evm};
use crate::utils::HeaderInclusion;
use crate::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData};
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_groth16::Groth16;
use ark_poly_commit::kzg10::VerifierKey;
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::{KZGProver, KZGSetup, ProverKey},
CommitmentProver,
},
transcript::{
poseidon::{poseidon_test_config, PoseidonTranscript},
Transcript,
},
};
use itertools::chain;
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];
/// Default setup length for testing.
const DEFAULT_SETUP_LEN: usize = 5;
#[derive(Debug, Clone, Copy)]
struct TestAddCircuit<F: PrimeField> {
_f: PhantomData<F>,
pub x: u8,
pub y: u8,
pub z: u8,
}
impl<F: PrimeField> ConstraintSynthesizer<F> for TestAddCircuit<F> {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> Result<(), SynthesisError> {
let x = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.x)))?;
let y = FpVar::<F>::new_witness(cs.clone(), || Ok(F::from(self.y)))?;
let z = FpVar::<F>::new_input(cs.clone(), || Ok(F::from(self.z)))?;
let comp_z = x.clone() + y.clone();
comp_z.enforce_equal(&z)?;
Ok(())
}
}
#[allow(clippy::type_complexity)]
fn setup<'a>(
n: usize,
) -> (
ProverKey<'a, G1>,
VerifierKey<Bn254>,
ark_groth16::ProvingKey<Bn254>,
ark_groth16::VerifyingKey<Bn254>,
TestAddCircuit<Fr>,
) {
let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64());
let (x, y, z) = (21, 21, 42);
let circuit = TestAddCircuit::<Fr> {
_f: PhantomData,
x,
y,
z,
};
let (g16_pk, g16_vk) = Groth16::<Bn254>::setup(circuit, &mut rng).unwrap();
let (kzg_pk, kzg_vk): (ProverKey<G1>, VerifierKey<Bn254>) =
KZGSetup::<Bn254>::setup(&mut rng, n);
(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 = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
let (eval, proof) =
KZGProver::<G1>::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.into_affine());
let (x_comm, y_comm) = cm_affine.xy().unwrap();
let (x_proof, y_proof) = proof_affine.xy().unwrap();
let y = 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 = KZGProver::<G1>::commit(&kzg_pk, &v, &Fr::zero()).unwrap();
let (eval, proof) =
KZGProver::<G1>::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.into_affine());
let (x_comm, y_comm) = cm_affine.xy().unwrap();
let (x_proof, y_proof) = proof_affine.xy().unwrap();
let y = 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);
}
}