diff --git a/.gitignore b/.gitignore index ec388aa..5499980 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ Cargo.lock /src/frontend/circom/test_folder/test_circuit_js/ # generated contracts at test time -folding-schemes-solidity/generated \ No newline at end of file +solidity-verifiers/generated \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 87b06ba..5e95c28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] -resolver = "1" members = [ "folding-schemes", - "folding-schemes-solidity" + "solidity-verifiers", + "cli" ] +resolver = "2" # The following patch is to use a version of ark-r1cs-std compatible with # v0.4.0 but that includes a cherry-picked commit from after v0.4.0 which fixes diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..4179485 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "solidity-verifiers-cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +ark-ec = "0.4" +ark-ff = "0.4" +ark-poly = "0.4" +ark-std = "0.4" +ark-groth16 = "0.4" +askama = { version = "0.12.0", features = ["config"], default-features = false } +ark-bn254 = "0.4.0" +ark-poly-commit = "0.4.0" +solidity-verifiers = { path = "../solidity-verifiers" } +itertools = "0.12.1" +ark-serialize = "0.4.1" +clap = { version = "4.4", features = ["derive", "string"] } +clap-verbosity-flag = "2.1" +log = "0.4" +env_logger = "0.10" + +[dev-dependencies] +revm = "3.5.0" +tracing = { version = "0.1", default-features = false, features = [ "attributes" ] } +tracing-subscriber = { version = "0.2" } + +[features] +default = ["parallel"] + +parallel = [ + "ark-std/parallel", + "ark-ff/parallel", + "ark-poly/parallel", + ] + + + diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..080f524 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,72 @@ +# Solidity Verifier + _____ ______ ______ ______ ______ ______ ______ + | |__| || |__| || |__| || |__| || |__| || |__| || |__| | + | () || () || () || () || () || () || () | + |______||______||______||______||______||______||______| + ______ ______ + | |__| | ____ _ _ _ _ _ | |__| | + | () | / ___| ___ | (_) __| (_) |_ _ _ | () | + |______| \___ \ / _ \| | |/ _` | | __| | | | |______| + ______ ___) | (_) | | | (_| | | |_| |_| | ______ + | |__| | |____/ \___/|_|_|\__,_|_|\__|\__, | | |__| | + | () | __ __ _ __ _ |___/ | () | + |______| \ \ / /__ _ __(_)/ _(_) ___ _ __ |______| + ______ \ \ / / _ \ '__| | |_| |/ _ \ '__| ______ + | |__| | \ V / __/ | | | _| | __/ | | |__| | + | () | \_/ \___|_| |_|_| |_|\___|_| | () | + |______| |______| + ______ ______ ______ ______ ______ ______ ______ + | |__| || |__| || |__| || |__| || |__| || |__| || |__| | + | () || () || () || () || () || () || () | + |______||______||______||______||______||______||______| + +Welcome to Solidity Verifier, a powerful Command-Line Interface (CLI) tool designed to simplify the generation of Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. This tool is developed by the collaborative efforts of the PSE (Privacy & Scaling Explorations) and 0XPARC teams. + +As an open-source project, Solidity Verifier is released under the GPL3 license. + +## Supported Protocols + +Solidity Verifier currently supports the generation of Solidity smart contracts for the verification of proofs in the following Zero Knowledge protocols: + +- **Groth16:** + - Efficient and succinct zero-knowledge proof system. + - Template credit: [Jordi Baylina - Groth16 Verifier Template](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs) + +- **KZG:** + - Uses the Kate-Zaverucha-Goldberg polynomial commitment scheme. + - Example credit: [weijiekoh - KZG10 Verifier Contract](https://github.com/weijiekoh/libkzg/blob/master/sol/KZGVerifier.sol) + +- **Nova + CycleFold Decider:** + - Implements the decider circuit verification for the Nova zero-knowledge proof system in conjunction with the CycleFold protocol optimization. + - Template inspiration and setup credit: [Han - revm/Solidity Contract Testing Functions](https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/tree/main) + +## Usage + +```bash +solidity-verifier [OPTIONS] -p -pd -o +``` + +A real use case (which was used to test the tool itself): +`solidity-verifier -p groth16 -pd ./folding-verifier-solidity/assets/G16_test_vk_data` +This would generate a Groth16 verifier contract for the given G16 data (which consists on the G16_Vkey only) and store this contract in `$pwd`. + +### Options: + -v, --verbose: Increase logging verbosity + -q, --quiet: Decrease logging verbosity + -p, --protocol : Selects the protocol for which to generate the Decider circuit Solidity Verifier (possible values: groth16, kzg, nova-cyclefold) + -o, --out : Sets the output path for all generated artifacts (default: /home/kr0/Desktop/HDD/ethereum/folding-schemes/verifier.sol) + -d, --protocol-data : Sets the input path for the file containing all the data required by the chosen protocol for verification contract generation + --pragma : Selects the Solidity compiler version to be set in the Solidity Verifier contract artifact + -h, --help: Print help (see a summary with '-h') + -V, --version: Print version + +## License +Solidity Verifier is released under the GPL3 license for any of the protocols that include `Groth16`. See the LICENSE file in the project repository for more details. +For the rest of contracts/protocols and the CLI itself, this tooling is released under MIT/Apache license. + +## Contributing +Feel free to explore, use, and contribute to Solidity Verifier as we strive to enhance privacy and scalability in the blockchain space! +We welcome contributions to Solidity Verifier! If you encounter any issues, have feature requests, or want to contribute to the codebase, please check out the GitHub repository and follow the guidelines outlined in the contributing documentation. + + + diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..bd2dfeb --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,36 @@ +use ::clap::Parser; +use ark_serialize::Write; +use settings::Cli; +use std::path::Path; +use std::{fs, io}; + +mod settings; + +fn create_or_open_then_write>(path: &Path, content: &T) -> Result<(), io::Error> { + let mut file = fs::OpenOptions::new().create(true).write(true).open(path)?; + file.write_all(content.as_ref()) +} + +fn main() { + let cli = Cli::parse(); + + // generate a subscriber with the desired log level + env_logger::builder() + .format_timestamp_secs() + .filter_level(cli.verbosity.log_level_filter()) + .init(); + + let out_path = cli.out; + + // Fetch the exact protocol for which we need to generate the Decider verifier contract. + let protocol = cli.protocol; + // Fetch the protocol data passed by the user from the file. + let protocol_data = std::fs::read(cli.protocol_data).unwrap(); + + // Generate the Solidity Verifier contract for the selected protocol with the given data. + create_or_open_then_write( + &out_path, + &protocol.render(&protocol_data, cli.pragma).unwrap(), + ) + .unwrap(); +} diff --git a/cli/src/settings.rs b/cli/src/settings.rs new file mode 100644 index 0000000..99020bb --- /dev/null +++ b/cli/src/settings.rs @@ -0,0 +1,111 @@ +use ark_serialize::SerializationError; +use clap::{Parser, ValueEnum}; +use solidity_verifiers::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData}; +use std::{env, fmt::Display, path::PathBuf}; + +fn get_default_out_path() -> PathBuf { + let mut path = env::current_dir().unwrap(); + path.push("verifier.sol"); + path +} + +#[derive(Debug, Copy, Clone, ValueEnum)] +pub(crate) enum Protocol { + Groth16, + Kzg, + NovaCyclefold, +} + +impl Display for Protocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +// Would be nice to link this to the `Template` or `ProtocolData` traits. +// Sadly, this requires Boxing with `dyn` or similar which would complicate the code more than is actually required. +impl Protocol { + pub(crate) fn render( + &self, + data: &[u8], + pragma: Option, + ) -> Result, SerializationError> { + match self { + Self::Groth16 => { + Ok(Groth16Data::deserialize_protocol_data(data)?.render_as_template(pragma)) + } + + Self::Kzg => Ok(KzgData::deserialize_protocol_data(data)?.render_as_template(pragma)), + Self::NovaCyclefold => { + Ok(NovaCyclefoldData::deserialize_protocol_data(data)?.render_as_template(pragma)) + } + } + } +} + +const ABOUT: &str = "A powerful Command-Line Interface (CLI) tool designed to simplify the generation of Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. +"; + +const LONG_ABOUT: &str = " + _____ ______ ______ ______ ______ ______ ______ +| |__| || |__| || |__| || |__| || |__| || |__| || |__| | +| () || () || () || () || () || () || () | +|______||______||______||______||______||______||______| + ______ ______ +| |__| | ____ _ _ _ _ _ | |__| | +| () | / ___| ___ | (_) __| (_) |_ _ _ | () | +|______| \\___ \\ / _ \\| | |/ _` | | __| | | | |______| + ______ ___) | (_) | | | (_| | | |_| |_| | ______ +| |__| | |____/ \\___/|_|_|\\__,_|_|\\__|\\__, | | |__| | +| () | __ __ _ __ _ |___/ | () | +|______| \\ \\ / /__ _ __(_)/ _(_) ___ _ __ |______| + ______ \\ \\ / / _ \\ '__| | |_| |/ _ \\ '__| ______ +| |__| | \\ V / __/ | | | _| | __/ | | |__| | +| () | \\_/ \\___|_| |_|_| |_|\\___|_| | () | +|______| |______| + ______ ______ ______ ______ ______ ______ ______ +| |__| || |__| || |__| || |__| || |__| || |__| || |__| | +| () || () || () || () || () || () || () | +|______||______||______||______||______||______||______| + +Welcome to Solidity Verifier, a powerful Command-Line Interface (CLI) tool designed to simplify the generation of Solidity smart contracts that verify proofs of Zero Knowledge cryptographic protocols. +for Zero Knowledge protocols. This tool is developed by the collaborative efforts of the PSE (Privacy & Scaling Explorations) and 0XPARC teams. + +As an open-source project, Solidity Verifier is released under the GPL3 license. + +Solidity Verifier currently supports the generation of Solidity smart contracts for the verification of proofs in the following Zero Knowledge protocols: + + Groth16: + Efficient and succinct zero-knowledge proof system. + + KZG: + Uses the Kate-Zaverucha-Goldberg polynomial commitment scheme. + + Nova + CycleFold Decider: + Implements the decider circuit verification for the Nova zero-knowledge proof system in conjunction with the CycleFold protocol optimization. +"; +#[derive(Debug, Parser)] +#[command(author = "0XPARC & PSE", version, about = ABOUT, long_about = Some(LONG_ABOUT))] +#[command(propagate_version = true)] +/// A tool to create Solidity Contracts which act as verifiers for the major Folding Schemes implemented +/// within the `folding-schemes` repo. +pub(crate) struct Cli { + #[command(flatten)] + pub verbosity: clap_verbosity_flag::Verbosity, + + /// Selects the protocol for which we want to generate the Solidity Verifier contract. + #[arg(short = 'p', long, value_enum, rename_all = "lower")] + pub protocol: Protocol, + + #[arg(short = 'o', long, default_value=get_default_out_path().into_os_string())] + /// Sets the output path for all the artifacts generated by the command. + pub out: PathBuf, + + #[arg(short = 'd', long)] + /// Sets the input path for the file containing all the data required by the protocol chosen such that the verification contract can be generated. + pub protocol_data: PathBuf, + + /// Selects the Solidity compiler version to be set in the Solidity Verifier contract artifact. + #[arg(long, default_value=None)] + pub pragma: Option, +} diff --git a/folding-schemes-solidity/README.md b/folding-schemes-solidity/README.md deleted file mode 100644 index e72cc83..0000000 --- a/folding-schemes-solidity/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `folding-schemes-solidity` - -This crate implements templating logic to output verifier contracts for `folding-schemes`-generated decider proofs. \ No newline at end of file diff --git a/folding-schemes-solidity/src/lib.rs b/folding-schemes-solidity/src/lib.rs deleted file mode 100644 index 7bbeeb4..0000000 --- a/folding-schemes-solidity/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use evm::*; -pub use verifiers::templates::*; -mod evm; -mod utils; -mod verifiers; diff --git a/folding-schemes-solidity/src/utils/mod.rs b/folding-schemes-solidity/src/utils/mod.rs deleted file mode 100644 index 2956942..0000000 --- a/folding-schemes-solidity/src/utils/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod encoding; diff --git a/folding-schemes-solidity/src/verifiers/mod.rs b/folding-schemes-solidity/src/verifiers/mod.rs deleted file mode 100644 index d6246b5..0000000 --- a/folding-schemes-solidity/src/verifiers/mod.rs +++ /dev/null @@ -1,287 +0,0 @@ -pub mod templates; - -#[cfg(test)] -mod tests { - use crate::evm::{compile_solidity, save_solidity, Evm}; - use crate::verifiers::templates::{Groth16Verifier, KZG10Verifier}; - 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; - - // 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]; - - // Pragma statements for verifiers - pub const PRAGMA_GROTH16_VERIFIER: &str = "pragma solidity >=0.7.0 <0.9.0;"; // from snarkjs, avoid changing - pub const PRAGMA_KZG10_VERIFIER: &str = "pragma solidity >=0.8.1 <=0.8.4;"; - - struct TestAddCircuit { - _f: PhantomData, - pub x: u8, - pub y: u8, - pub z: u8, - } - - impl ConstraintSynthesizer for TestAddCircuit { - fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { - let x = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.x)))?; - let y = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.y)))?; - let z = FpVar::::new_input(cs.clone(), || Ok(F::from(self.z)))?; - let comp_z = x.clone() + y.clone(); - comp_z.enforce_equal(&z)?; - Ok(()) - } - } - - #[test] - fn test_groth16_kzg10_decider_template_renders() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (x, y, z) = (21, 21, 42); - let (_, vk) = { - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - Groth16::::setup(c, &mut rng).unwrap() - }; - let groth16_template = Groth16Verifier::from(vk, None); - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(&mut rng, 5); - let kzg10_template = KZG10Verifier::from(&vk, &pk.powers_of_g[..5], None, None); - let decider_template = super::templates::Groth16KZG10DeciderVerifier { - groth16_verifier: groth16_template, - kzg10_verifier: kzg10_template, - }; - save_solidity("decider.sol", &decider_template.render().unwrap()); - } - - #[test] - fn test_groth16_kzg10_decider_template_compiles() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (x, y, z) = (21, 21, 42); - let (_, vk) = { - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - Groth16::::setup(c, &mut rng).unwrap() - }; - // we dont specify any pragma values for both verifiers, the pragma from the decider takes over - let groth16_template = Groth16Verifier::from(vk, None); - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(&mut rng, 5); - let kzg10_template = KZG10Verifier::from(&vk, &pk.powers_of_g[..5], None, None); - let decider_template = super::templates::Groth16KZG10DeciderVerifier { - groth16_verifier: groth16_template, - kzg10_verifier: kzg10_template, - }; - 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_template_renders() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (x, y, z) = (21, 21, 42); - let (_, vk) = { - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - Groth16::::setup(c, &mut rng).unwrap() - }; - let template = Groth16Verifier::from(vk, Some(PRAGMA_GROTH16_VERIFIER.to_string())); - save_solidity("groth16_verifier.sol", &template.render().unwrap()); - _ = template.render().unwrap(); - } - - #[test] - fn test_groth16_verifier_template_compiles() { - let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(test_rng().next_u64()); - let (x, y, z) = (21, 21, 42); - let (_, vk) = { - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - Groth16::::setup(c, &mut rng).unwrap() - }; - let res = Groth16Verifier::from(vk, Some(PRAGMA_GROTH16_VERIFIER.to_string())) - .render() - .unwrap(); - let groth16_verifier_bytecode = compile_solidity(res, "Verifier"); - let mut evm = Evm::default(); - _ = evm.create(groth16_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 (x, y, z) = (21, 21, 42); - let (pk, vk) = { - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - Groth16::::setup(c, &mut rng).unwrap() - }; - let c = TestAddCircuit:: { - _f: PhantomData, - x, - y, - z, - }; - let proof = Groth16::::prove(&pk, c, &mut rng).unwrap(); - let res = Groth16Verifier::from(vk, Some(PRAGMA_GROTH16_VERIFIER.to_string())) - .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 = 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(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 test_kzg_verifier_template_renders() { - let rng = &mut test_rng(); - let n = 10; - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(rng, n); - let template = KZG10Verifier::from( - &vk, - &pk.powers_of_g[..5], - Some(PRAGMA_KZG10_VERIFIER.to_string()), - None, - ); - let res = template.render().unwrap(); - assert!(res.contains(&vk.g.x.to_string())); - } - - #[test] - fn test_kzg_verifier_compiles() { - let rng = &mut test_rng(); - let n = 10; - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(rng, n); - let template = KZG10Verifier::from( - &vk, - &pk.powers_of_g[..5], - Some(PRAGMA_KZG10_VERIFIER.to_string()), - None, - ); - let res = template.render().unwrap(); - let kzg_verifier_bytecode = compile_solidity(res, "KZG10"); - let mut evm = Evm::default(); - _ = evm.create(kzg_verifier_bytecode); - } - - #[test] - fn test_kzg_verifier_accepts_and_rejects_proofs() { - let rng = &mut test_rng(); - let poseidon_config = poseidon_test_config::(); - let transcript_p = &mut PoseidonTranscript::::new(&poseidon_config); - let transcript_v = &mut PoseidonTranscript::::new(&poseidon_config); - - let n = 10; - let (pk, vk): (ProverKey, VerifierKey) = KZGSetup::::setup(rng, n); - let v: Vec = std::iter::repeat_with(|| Fr::rand(rng)).take(n).collect(); - let cm = KZGProver::::commit(&pk, &v, &Fr::zero()).unwrap(); - let (eval, proof) = - KZGProver::::prove(&pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); - let template = KZG10Verifier::from( - &vk, - &pk.powers_of_g[..5], - Some(PRAGMA_KZG10_VERIFIER.to_string()), - None, - ); - let res = template.render().unwrap(); - let kzg_verifier_bytecode = compile_solidity(res, "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 = 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); - } -} diff --git a/folding-schemes-solidity/src/verifiers/templates.rs b/folding-schemes-solidity/src/verifiers/templates.rs deleted file mode 100644 index 8431d58..0000000 --- a/folding-schemes-solidity/src/verifiers/templates.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::ops::Deref; - -use crate::utils::encoding::{g1_to_fq_repr, g2_to_fq_repr}; -/// Solidity templates for the verifier contracts. -/// We use askama for templating and define which variables are required for each template. -use crate::utils::encoding::{G1Repr, G2Repr}; -use ark_bn254::{Bn254, G1Affine}; -use ark_groth16::VerifyingKey; -use ark_poly_commit::kzg10::VerifierKey; -use askama::Template; - -#[derive(Template, Default)] -#[template(path = "groth16_verifier.askama.sol", ext = "sol")] -pub struct Groth16Verifier { - /// SPDX-License-Identifier - pub sdpx: String, - /// The `pragma` statement. - pub pragma_version: String, - /// The `alpha * G`, where `G` is the generator of `G1`. - pub vkey_alpha_g1: G1Repr, - /// The `alpha * H`, where `H` is the generator of `G2`. - pub vkey_beta_g2: G2Repr, - /// The `gamma * H`, where `H` is the generator of `G2`. - pub vkey_gamma_g2: G2Repr, - /// The `delta * H`, where `H` is the generator of `G2`. - pub vkey_delta_g2: G2Repr, - /// Length of the `gamma_abc_g1` vector. - pub gamma_abc_len: usize, - /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is the generator of `E::G1`. - pub gamma_abc_g1: Vec, -} - -impl Groth16Verifier { - pub fn from(value: VerifyingKey, pragma: Option) -> Self { - let pragma_version = pragma.unwrap_or_default(); - let sdpx = "// SPDX-License-Identifier: GPL-3.0".to_string(); - Self { - pragma_version, - sdpx, - vkey_alpha_g1: g1_to_fq_repr(value.alpha_g1), - vkey_beta_g2: g2_to_fq_repr(value.beta_g2), - vkey_gamma_g2: g2_to_fq_repr(value.gamma_g2), - vkey_delta_g2: g2_to_fq_repr(value.delta_g2), - gamma_abc_len: value.gamma_abc_g1.len(), - gamma_abc_g1: value - .gamma_abc_g1 - .iter() - .copied() - .map(g1_to_fq_repr) - .collect(), - } - } -} - -#[derive(Template, Default)] -#[template(path = "kzg10_verifier.askama.sol", ext = "sol")] -pub struct KZG10Verifier { - /// SPDX-License-Identifier - pub sdpx: String, - /// The `pragma` statement. - pub pragma_version: String, - /// The generator of `G1`. - pub g1: G1Repr, - /// The generator of `G2`. - pub g2: G2Repr, - /// The verification key - pub vk: G2Repr, - /// Length of the trusted setup vector. - pub g1_crs_len: usize, - /// The trusted setup vector. - pub g1_crs: Vec, -} - -impl KZG10Verifier { - pub fn from( - vk: &VerifierKey, - crs: &[G1Affine], - pragma: Option, - sdpx: Option, - ) -> KZG10Verifier { - let g1_string_repr = g1_to_fq_repr(vk.g); - let g2_string_repr = g2_to_fq_repr(vk.h); - let vk_string_repr = g2_to_fq_repr(vk.beta_h); - let g1_crs_len = crs.len(); - let g1_crs = crs.iter().map(|g1| g1_to_fq_repr(*g1)).collect(); - let sdpx = sdpx.unwrap_or_default(); - let pragma_version = pragma.unwrap_or_default(); - KZG10Verifier { - sdpx, - pragma_version, - g1: g1_string_repr, - g2: g2_string_repr, - vk: vk_string_repr, - g1_crs, - g1_crs_len, - } - } -} - -#[derive(Template)] -#[template(path = "kzg10_groth16_decider_verifier.askama.sol", ext = "sol")] -pub struct Groth16KZG10DeciderVerifier { - pub groth16_verifier: Groth16Verifier, - pub kzg10_verifier: KZG10Verifier, -} - -impl Deref for Groth16KZG10DeciderVerifier { - type Target = Groth16Verifier; - - fn deref(&self) -> &Self::Target { - &self.groth16_verifier - } -} diff --git a/folding-schemes-solidity/templates/kzg10_groth16_decider_verifier.askama.sol b/folding-schemes-solidity/templates/kzg10_groth16_decider_verifier.askama.sol deleted file mode 100644 index a46461e..0000000 --- a/folding-schemes-solidity/templates/kzg10_groth16_decider_verifier.askama.sol +++ /dev/null @@ -1,20 +0,0 @@ -pragma solidity ^0.8.4; - -{{ groth16_verifier }} - -{{ kzg10_verifier }} - -/** - * @author PSE & 0xPARC - * @title NovaDecider contract, for verifying zk-snarks Nova IVC proofs. - * @dev This is an askama template. It will feature a snarkjs groth16 and a kzg10 verifier, from which this contract inherits. - */ -contract NovaDecider is Groth16Verifier, KZG10Verifier { - function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[{{ gamma_abc_len - 1 }}] calldata _pubSignals, uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y) public view returns (bool) { - require(super.verifyProof(_pA, _pB, _pC, _pubSignals) == true, "Groth16 verification failed"); - // for now, we do not relate the Groth16 and KZG10 proofs - // this will done in the future, by computing challenge points from the groth16 proof's data - require(super.check(c, pi, x, y) == true, "KZG10 verification failed"); - return true; - } -} \ No newline at end of file diff --git a/folding-schemes/src/commitment/kzg.rs b/folding-schemes/src/commitment/kzg.rs index 79dad50..8e4e25b 100644 --- a/folding-schemes/src/commitment/kzg.rs +++ b/folding-schemes/src/commitment/kzg.rs @@ -35,6 +35,7 @@ pub struct ProverKey<'a, C: CurveGroup> { pub powers_of_g: Cow<'a, [C::Affine]>, } +#[derive(Debug)] pub struct KZGSetup { _p: PhantomData

, } diff --git a/rust-toolchain b/rust-toolchain index 7c7053a..dc87e8a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.75.0 +1.74.0 diff --git a/folding-schemes-solidity/Cargo.toml b/solidity-verifiers/Cargo.toml similarity index 95% rename from folding-schemes-solidity/Cargo.toml rename to solidity-verifiers/Cargo.toml index f92154d..eb2d519 100644 --- a/folding-schemes-solidity/Cargo.toml +++ b/solidity-verifiers/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "folding-schemes-solidity" +name = "solidity-verifiers" version = "0.1.0" edition = "2021" diff --git a/solidity-verifiers/README.md b/solidity-verifiers/README.md new file mode 100644 index 0000000..9318de2 --- /dev/null +++ b/solidity-verifiers/README.md @@ -0,0 +1,3 @@ +# `solidity-verifiers` + +This crate implements templating logic to output verifier contracts for `folding-schemes`-generated decider proofs. \ No newline at end of file diff --git a/folding-schemes-solidity/askama.toml b/solidity-verifiers/askama.toml similarity index 100% rename from folding-schemes-solidity/askama.toml rename to solidity-verifiers/askama.toml diff --git a/folding-schemes-solidity/src/evm.rs b/solidity-verifiers/src/evm.rs similarity index 86% rename from folding-schemes-solidity/src/evm.rs rename to solidity-verifiers/src/evm.rs index 8afc9f6..3a1fd79 100644 --- a/folding-schemes-solidity/src/evm.rs +++ b/solidity-verifiers/src/evm.rs @@ -12,7 +12,7 @@ use std::{ }; // from: https://github.com/privacy-scaling-explorations/halo2-solidity-verifier/blob/85cb77b171ce3ee493628007c7a1cfae2ea878e6/examples/separately.rs#L56 -pub fn save_solidity(name: impl AsRef, solidity: &str) { +pub(crate) fn save_solidity(name: impl AsRef, solidity: &str) { const DIR_GENERATED: &str = "./generated"; create_dir_all(DIR_GENERATED).unwrap(); File::create(format!("{DIR_GENERATED}/{}", name.as_ref())) @@ -25,7 +25,7 @@ pub fn save_solidity(name: impl AsRef, solidity: &str) { /// /// # Panics /// Panics if executable `solc` can not be found, or compilation fails. -pub fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec { +pub(crate) fn compile_solidity(solidity: impl AsRef<[u8]>, contract_name: &str) -> Vec { let mut process = match Command::new("solc") .stdin(Stdio::piped()) .stdout(Stdio::piped()) @@ -72,7 +72,7 @@ fn find_binary(stdout: &str, contract_name: &str) -> Option> { } /// Evm runner. -pub struct Evm { +pub(crate) struct Evm { evm: EVM, } @@ -98,25 +98,12 @@ impl Default for Evm { } impl Evm { - /// Return code_size of given address. - /// - /// # Panics - /// Panics if given address doesn't have bytecode. - pub fn code_size(&mut self, address: Address) -> usize { - self.evm.db.as_ref().unwrap().accounts[&address] - .info - .code - .as_ref() - .unwrap() - .len() - } - /// Apply create transaction with given `bytecode` as creation bytecode. /// Return created `address`. /// /// # Panics /// Panics if execution reverts or halts unexpectedly. - pub fn create(&mut self, bytecode: Vec) -> Address { + pub(crate) fn create(&mut self, bytecode: Vec) -> Address { let (_, output) = self.transact_success_or_panic(TxEnv { gas_limit: u64::MAX, transact_to: TransactTo::Create(CreateScheme::Create), @@ -134,7 +121,7 @@ impl Evm { /// /// # Panics /// Panics if execution reverts or halts unexpectedly. - pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { + pub(crate) fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { let (gas_used, output) = self.transact_success_or_panic(TxEnv { gas_limit: u64::MAX, transact_to: TransactTo::Call(address), @@ -170,9 +157,7 @@ impl Evm { } (gas_used, output) } - ExecutionResult::Revert { gas_used, output } => { - panic!("Transaction reverts with gas_used {gas_used} and output {output:#x}") - } + ExecutionResult::Revert { gas_used, output } => (gas_used, Output::Call(output)), ExecutionResult::Halt { reason, gas_used } => panic!( "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}" ), diff --git a/solidity-verifiers/src/lib.rs b/solidity-verifiers/src/lib.rs new file mode 100644 index 0000000..e0ebd60 --- /dev/null +++ b/solidity-verifiers/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(test)] +mod evm; +mod utils; +mod verifiers; + +pub use verifiers::*; +pub use verifiers::{Groth16Data, KzgData, NovaCyclefoldData, ProtocolData}; diff --git a/folding-schemes-solidity/src/utils/encoding.rs b/solidity-verifiers/src/utils/encoding.rs similarity index 100% rename from folding-schemes-solidity/src/utils/encoding.rs rename to solidity-verifiers/src/utils/encoding.rs diff --git a/solidity-verifiers/src/utils/mod.rs b/solidity-verifiers/src/utils/mod.rs new file mode 100644 index 0000000..c43df95 --- /dev/null +++ b/solidity-verifiers/src/utils/mod.rs @@ -0,0 +1,66 @@ +use crate::{GPL3_SDPX_IDENTIFIER, PRAGMA_GROTH16_VERIFIER}; +use askama::Template; + +pub mod encoding; + +#[derive(Template)] +#[template(path = "header_template.askama.sol", ext = "sol")] +pub(crate) struct HeaderInclusion { + /// SPDX-License-Identifier + pub sdpx: String, + /// The `pragma` statement. + pub pragma_version: String, + /// The template to render alongside the header. + pub template: T, +} + +impl HeaderInclusion { + pub fn builder() -> HeaderInclusionBuilder { + HeaderInclusionBuilder::default() + } +} + +#[derive(Debug)] +pub struct HeaderInclusionBuilder { + /// SPDX-License-Identifier + sdpx: String, + /// The `pragma` statement. + pragma_version: String, + /// The template to render alongside the header. + template: T, +} + +impl Default for HeaderInclusionBuilder { + fn default() -> Self { + Self { + sdpx: GPL3_SDPX_IDENTIFIER.to_string(), + pragma_version: PRAGMA_GROTH16_VERIFIER.to_string(), + template: T::default(), + } + } +} + +impl HeaderInclusionBuilder { + pub fn sdpx>(mut self, sdpx: S) -> Self { + self.sdpx = sdpx.into(); + self + } + + pub fn pragma_version>(mut self, pragma_version: S) -> Self { + self.pragma_version = pragma_version.into(); + self + } + + pub fn template(mut self, template: impl Into) -> Self { + self.template = template.into(); + self + } + + pub fn build(self) -> HeaderInclusion { + HeaderInclusion { + sdpx: self.sdpx, + pragma_version: self.pragma_version, + template: self.template, + } + } +} diff --git a/solidity-verifiers/src/verifiers/g16.rs b/solidity-verifiers/src/verifiers/g16.rs new file mode 100644 index 0000000..af619f0 --- /dev/null +++ b/solidity-verifiers/src/verifiers/g16.rs @@ -0,0 +1,72 @@ +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 ark_bn254::Bn254; +use ark_groth16::VerifyingKey; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use askama::Template; + +use super::PRAGMA_GROTH16_VERIFIER; + +#[derive(Template, Default)] +#[template(path = "groth16_verifier.askama.sol", ext = "sol")] +pub(crate) struct Groth16Verifier { + /// The `alpha * G`, where `G` is the generator of `G1`. + pub(crate) vkey_alpha_g1: G1Repr, + /// The `alpha * H`, where `H` is the generator of `G2`. + pub(crate) vkey_beta_g2: G2Repr, + /// The `gamma * H`, where `H` is the generator of `G2`. + pub(crate) vkey_gamma_g2: G2Repr, + /// The `delta * H`, where `H` is the generator of `G2`. + pub(crate) vkey_delta_g2: G2Repr, + /// Length of the `gamma_abc_g1` vector. + pub(crate) gamma_abc_len: usize, + /// The `gamma^{-1} * (beta * a_i + alpha * b_i + c_i) * H`, where `H` is the generator of `E::G1`. + pub(crate) gamma_abc_g1: Vec, +} + +impl From for Groth16Verifier { + fn from(g16_data: Groth16Data) -> 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 + .0 + .gamma_abc_g1 + .iter() + .copied() + .map(g1_to_fq_repr) + .collect(), + } + } +} + +// 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. +#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)] +pub struct Groth16Data(pub(crate) VerifyingKey); + +impl From> for Groth16Data { + fn from(value: VerifyingKey) -> Self { + Self(value) + } +} + +impl ProtocolData for Groth16Data { + const PROTOCOL_NAME: &'static str = "Groth16"; + + fn render_as_template(self, pragma: Option) -> Vec { + HeaderInclusion::::builder() + .sdpx(GPL3_SDPX_IDENTIFIER.to_string()) + .pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string())) + .template(self) + .build() + .render() + .unwrap() + .into_bytes() + } +} diff --git a/solidity-verifiers/src/verifiers/kzg.rs b/solidity-verifiers/src/verifiers/kzg.rs new file mode 100644 index 0000000..f42c7c0 --- /dev/null +++ b/solidity-verifiers/src/verifiers/kzg.rs @@ -0,0 +1,72 @@ +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 ark_bn254::{Bn254, G1Affine}; +use ark_poly_commit::kzg10::VerifierKey; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use askama::Template; + +use super::PRAGMA_KZG10_VERIFIER; + +#[derive(Template, Default)] +#[template(path = "kzg10_verifier.askama.sol", ext = "sol")] +pub(crate) struct KZG10Verifier { + /// The generator of `G1`. + pub(crate) g1: G1Repr, + /// The generator of `G2`. + pub(crate) g2: G2Repr, + /// The verification key + pub(crate) vk: G2Repr, + /// Length of the trusted setup vector. + pub(crate) g1_crs_len: usize, + /// The trusted setup vector. + pub(crate) g1_crs: Vec, +} + +impl From for KZG10Verifier { + fn from(data: KzgData) -> Self { + let g1_crs_batch_points = data.g1_crs_batch_points.unwrap_or_default(); + + 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 + .iter() + .map(|g1| g1_to_fq_repr(*g1)) + .collect(), + } + } +} + +#[derive(CanonicalDeserialize, CanonicalSerialize, Clone, PartialEq, Debug)] +pub struct KzgData { + pub(crate) vk: VerifierKey, + pub(crate) g1_crs_batch_points: Option>, +} + +impl From<(VerifierKey, Option>)> for KzgData { + fn from(value: (VerifierKey, Option>)) -> Self { + Self { + vk: value.0, + g1_crs_batch_points: value.1, + } + } +} + +impl ProtocolData for KzgData { + const PROTOCOL_NAME: &'static str = "KZG"; + + fn render_as_template(self, pragma: Option) -> Vec { + HeaderInclusion::::builder() + .sdpx(MIT_SDPX_IDENTIFIER.to_string()) + .pragma_version(pragma.unwrap_or(PRAGMA_KZG10_VERIFIER.to_string())) + .template(self) + .build() + .render() + .unwrap() + .into_bytes() + } +} diff --git a/solidity-verifiers/src/verifiers/mod.rs b/solidity-verifiers/src/verifiers/mod.rs new file mode 100644 index 0000000..dddd3e1 --- /dev/null +++ b/solidity-verifiers/src/verifiers/mod.rs @@ -0,0 +1,428 @@ +//! 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(&self, writer: &mut W) -> Result<(), SerializationError> { + Self::PROTOCOL_NAME + .to_string() + .serialize_uncompressed(writer) + } + + fn serialize_protocol_data(&self, writer: &mut W) -> Result<(), SerializationError> { + self.serialize_name(writer)?; + self.serialize_compressed(writer) + } + fn deserialize_protocol_data( + mut reader: R, + ) -> Result { + 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) -> Vec; +} + +#[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: PhantomData, + pub x: u8, + pub y: u8, + pub z: u8, + } + + impl ConstraintSynthesizer for TestAddCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let x = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.x)))?; + let y = FpVar::::new_witness(cs.clone(), || Ok(F::from(self.y)))?; + let z = FpVar::::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, + ark_groth16::ProvingKey, + ark_groth16::VerifyingKey, + TestAddCircuit, + ) { + 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:: { + _f: PhantomData, + x, + y, + z, + }; + let (g16_pk, g16_vk) = Groth16::::setup(circuit, &mut rng).unwrap(); + + let (kzg_pk, kzg_vk): (ProverKey, VerifierKey) = + KZGSetup::::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::::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::::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::::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 = 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::::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::::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::(); + let transcript_p = &mut PoseidonTranscript::::new(&poseidon_config); + let transcript_v = &mut PoseidonTranscript::::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 = std::iter::repeat_with(|| Fr::rand(&mut rng)) + .take(DEFAULT_SETUP_LEN) + .collect(); + let cm = KZGProver::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); + let (eval, proof) = + KZGProver::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); + let template = HeaderInclusion::::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 = 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::::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::::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::(); + let transcript_p = &mut PoseidonTranscript::::new(&poseidon_config); + let transcript_v = &mut PoseidonTranscript::::new(&poseidon_config); + + let v: Vec = std::iter::repeat_with(|| Fr::rand(&mut rng)) + .take(DEFAULT_SETUP_LEN) + .collect(); + let cm = KZGProver::::commit(&kzg_pk, &v, &Fr::zero()).unwrap(); + let (eval, proof) = + KZGProver::::prove(&kzg_pk, transcript_p, &cm, &v, &Fr::zero(), None).unwrap(); + + let decider_template = HeaderInclusion::::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 = 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); + } +} diff --git a/solidity-verifiers/src/verifiers/nova_cyclefold.rs b/solidity-verifiers/src/verifiers/nova_cyclefold.rs new file mode 100644 index 0000000..67af0fe --- /dev/null +++ b/solidity-verifiers/src/verifiers/nova_cyclefold.rs @@ -0,0 +1,69 @@ +use crate::utils::HeaderInclusion; +use crate::{Groth16Data, KzgData, ProtocolData, PRAGMA_GROTH16_VERIFIER}; +use ark_bn254::{Bn254, G1Affine}; +use ark_groth16::VerifyingKey; +use ark_poly_commit::kzg10::VerifierKey; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use askama::Template; + +use super::g16::Groth16Verifier; +use super::kzg::KZG10Verifier; + +#[derive(Template, Default)] +#[template(path = "nova_cyclefold_decider.askama.sol", ext = "sol")] +pub(crate) struct NovaCyclefoldDecider { + groth16_verifier: Groth16Verifier, + kzg10_verifier: KZG10Verifier, +} + +impl From for NovaCyclefoldDecider { + fn from(value: NovaCyclefoldData) -> Self { + Self { + groth16_verifier: Groth16Verifier::from(value.g16_data), + kzg10_verifier: KZG10Verifier::from(value.kzg_data), + } + } +} + +#[derive(CanonicalDeserialize, CanonicalSerialize, PartialEq, Debug)] +pub struct NovaCyclefoldData { + g16_data: Groth16Data, + kzg_data: KzgData, +} + +impl ProtocolData for NovaCyclefoldData { + const PROTOCOL_NAME: &'static str = "NovaCyclefold"; + + fn render_as_template(self, pragma: Option) -> Vec { + HeaderInclusion::::builder() + .pragma_version(pragma.unwrap_or(PRAGMA_GROTH16_VERIFIER.to_string())) + .template(self) + .build() + .render() + .unwrap() + .into_bytes() + } +} + +impl From<(Groth16Data, KzgData)> for NovaCyclefoldData { + fn from(value: (Groth16Data, KzgData)) -> Self { + Self { + g16_data: value.0, + kzg_data: value.1, + } + } +} + +impl NovaCyclefoldData { + pub fn new( + vkey_g16: VerifyingKey, + vkey_kzg: VerifierKey, + crs_points: Vec, + ) -> Self { + Self { + g16_data: Groth16Data::from(vkey_g16), + // TODO: Remove option from crs points + kzg_data: KzgData::from((vkey_kzg, Some(crs_points))), + } + } +} diff --git a/folding-schemes-solidity/templates/groth16_verifier.askama.sol b/solidity-verifiers/templates/groth16_verifier.askama.sol similarity index 98% rename from folding-schemes-solidity/templates/groth16_verifier.askama.sol rename to solidity-verifiers/templates/groth16_verifier.askama.sol index 86cf648..5d466bd 100644 --- a/folding-schemes-solidity/templates/groth16_verifier.askama.sol +++ b/solidity-verifiers/templates/groth16_verifier.askama.sol @@ -1,8 +1,7 @@ -{{ sdpx }} /* Copyright 2021 0KIMS association. - * `folding-schemes-solidity` added comment + * `solidity-verifiers` added comment This file is a template built out of [snarkJS](https://github.com/iden3/snarkjs) groth16 verifier. See the original ejs template [here](https://github.com/iden3/snarkjs/blob/master/templates/verifier_groth16.sol.ejs) * @@ -21,8 +20,6 @@ along with snarkJS. If not, see . */ -{{ pragma_version }} - contract Groth16Verifier { // Scalar field size uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; diff --git a/solidity-verifiers/templates/header_template.askama.sol b/solidity-verifiers/templates/header_template.askama.sol new file mode 100644 index 0000000..a9f6683 --- /dev/null +++ b/solidity-verifiers/templates/header_template.askama.sol @@ -0,0 +1,4 @@ +{{ sdpx }} +{{ pragma_version }} + +{{template}} \ No newline at end of file diff --git a/folding-schemes-solidity/templates/kzg10_verifier.askama.sol b/solidity-verifiers/templates/kzg10_verifier.askama.sol similarity index 99% rename from folding-schemes-solidity/templates/kzg10_verifier.askama.sol rename to solidity-verifiers/templates/kzg10_verifier.askama.sol index e1096ba..c708dcf 100644 --- a/folding-schemes-solidity/templates/kzg10_verifier.askama.sol +++ b/solidity-verifiers/templates/kzg10_verifier.askama.sol @@ -1,7 +1,3 @@ -{{ sdpx }} - -{{ pragma_version }} - /** * @author Privacy and Scaling Explorations team - pse.dev * @dev Contains utility functions for ops in BN254; in G_1 mostly. diff --git a/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol new file mode 100644 index 0000000..5f93c10 --- /dev/null +++ b/solidity-verifiers/templates/nova_cyclefold_decider.askama.sol @@ -0,0 +1,24 @@ +{{ groth16_verifier }} + +{{ kzg10_verifier }} + +/** + * @author PSE & 0xPARC + * @title NovaDecider contract, for verifying zk-snarks Nova IVC proofs. + * @dev This is an askama template. It will feature a snarkjs groth16 and a kzg10 verifier, from which this contract inherits. + * WARNING: This contract is not complete nor finished. It lacks checks to ensure that no soundness issues can happen. + * Indeed, we know some of the checks that are missing. And we're working on the solution + * but for now, it's good enough for testing and benchmarking. + */ +contract NovaDecider is Groth16Verifier, KZG10Verifier { + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals, uint256[2] calldata c, uint256[2] calldata pi, uint256 x, uint256 y) public view returns (bool) { + bool success_kzg = super.check(c, pi, x, y); + require(success_kzg == true, "KZG Failed"); + + // for now, we do not relate the Groth16 and KZG10 proofs + bool success_g16 = super.verifyProof(_pA, _pB, _pC, _pubSignals); + require(success_g16 == true, "G16 Failed"); + + return(true); + } +} \ No newline at end of file