From 46685b1076cd03da8ceddd2da563f5a1b143cc48 Mon Sep 17 00:00:00 2001 From: schaeff Date: Fri, 21 Apr 2023 22:38:23 +0200 Subject: [PATCH] add compressed proof verification, upgrade nova to latest --- .gitignore | 1 + Cargo.lock | 25 +---- zokrates_bellperson/Cargo.toml | 2 +- zokrates_bellperson/examples/10_cubes.rs | 7 +- zokrates_bellperson/src/nova.rs | 32 +++++- zokrates_book/src/toolbox/experimental.md | 6 ++ zokrates_cli/src/ops/nova/compress.rs | 40 ++++++- zokrates_cli/src/ops/nova/mod.rs | 3 + zokrates_cli/src/ops/nova/prove.rs | 10 +- zokrates_cli/src/ops/nova/verify.rs | 125 ++++++++++++++++++++++ zokrates_field/Cargo.toml | 2 +- zokrates_field/src/lib.rs | 6 +- 12 files changed, 225 insertions(+), 34 deletions(-) create mode 100644 zokrates_cli/src/ops/nova/verify.rs diff --git a/.gitignore b/.gitignore index 7228ce2e..81e43048 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ universal_setup.dat witness witness.json nova.params +running_instance.json # ZoKrates source files at the root of the repository /*.zok diff --git a/Cargo.lock b/Cargo.lock index ac48107a..c9fbc1b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1946,18 +1946,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - [[package]] name = "miniz_oxide" version = "0.6.2" @@ -1995,9 +1983,9 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nova-snark" -version = "0.13.0" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77651083c7aecec78b063779fe6271f5f6cf98252f4b584f7f58010dbca7c0ca" +checksum = "b09c8cf02c93500dee9244cd73547f20d133ca6d3fbbe9eae0205e5b2db05f36" dependencies = [ "bellperson", "bincode 1.3.3", @@ -2010,13 +1998,12 @@ dependencies = [ "generic-array 0.14.7", "itertools 0.9.0", "lurk-pasta-msm", - "merlin", "neptune", "num-bigint 0.4.3", "num-integer", "num-traits 0.2.15", "rand_chacha", - "rand_core 0.5.1", + "rand_core 0.6.4", "rayon", "serde", "sha3 0.8.2", @@ -2481,12 +2468,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" diff --git a/zokrates_bellperson/Cargo.toml b/zokrates_bellperson/Cargo.toml index 0d48ae18..12601707 100644 --- a/zokrates_bellperson/Cargo.toml +++ b/zokrates_bellperson/Cargo.toml @@ -15,7 +15,7 @@ getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } hex = "0.4.2" pairing = "0.22" ff = { version = "0.12.0", default-features = false } -nova-snark = { version = "0.13.0" } +nova-snark = { version = "0.20.3" } zokrates_interpreter = { version = "0.1", path = "../zokrates_interpreter" } serde = { version = "1.0", features = ["derive"] } diff --git a/zokrates_bellperson/examples/10_cubes.rs b/zokrates_bellperson/examples/10_cubes.rs index 864df6a0..b817dff5 100644 --- a/zokrates_bellperson/examples/10_cubes.rs +++ b/zokrates_bellperson/examples/10_cubes.rs @@ -100,7 +100,10 @@ fn main() { type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine; type S1 = nova_snark::spartan::RelaxedR1CSSNARK; type S2 = nova_snark::spartan::RelaxedR1CSSNARK; - let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &recursive_snark); + + let (pk, vk) = CompressedSNARK::<_, _, _, _, S1, S2>::setup(&pp).unwrap(); + + let res = CompressedSNARK::<_, _, _, _, S1, S2>::prove(&pp, &pk, &recursive_snark); println!( "CompressedSNARK::prove: {:?}, took {:?}", res.is_ok(), @@ -112,7 +115,7 @@ fn main() { // verify the compressed SNARK println!("Verifying a CompressedSNARK..."); let start = Instant::now(); - let res = compressed_snark.verify(&pp, num_steps, z0_primary, z0_secondary); + let res = compressed_snark.verify(&vk, num_steps, z0_primary, z0_secondary); println!( "CompressedSNARK::verify: {:?}, took {:?}", res.is_ok(), diff --git a/zokrates_bellperson/src/nova.rs b/zokrates_bellperson/src/nova.rs index 3a17a3dd..ad7f51e3 100644 --- a/zokrates_bellperson/src/nova.rs +++ b/zokrates_bellperson/src/nova.rs @@ -11,6 +11,7 @@ use nova_snark::traits::Group; use nova_snark::CompressedSNARK as GCompressedSNARK; pub use nova_snark::PublicParams as GPublicParams; pub use nova_snark::RecursiveSNARK as GRecursiveSNARK; +use nova_snark::VerifierKey as GVerifierKey; use serde::{Deserialize, Serialize}; use std::fmt; use zokrates_ast::ir::*; @@ -116,8 +117,8 @@ pub fn verify( #[derive(Serialize, Debug, Deserialize)] pub struct RecursiveSNARKWithStepCount<'ast, T: NovaField> { #[serde(bound = "T: NovaField")] - proof: RecursiveSNARK<'ast, T>, - steps: usize, + pub proof: RecursiveSNARK<'ast, T>, + pub steps: usize, } type EE1 = nova_snark::provider::ipa_pc::EvaluationEngine>; @@ -125,13 +126,34 @@ type EE2 = nova_snark::provider::ipa_pc::EvaluationEngine>; type S1 = nova_snark::spartan::RelaxedR1CSSNARK, EE1>; type S2 = nova_snark::spartan::RelaxedR1CSSNARK, EE2>; -type CompressedSNARK<'ast, T> = GCompressedSNARK, G2, C1<'ast, T>, C2, S1, S2>; +pub type CompressedSNARK<'ast, T> = + GCompressedSNARK, G2, C1<'ast, T>, C2, S1, S2>; +pub type VerifierKey<'ast, T> = GVerifierKey, G2, C1<'ast, T>, C2, S1, S2>; pub fn compress<'ast, T: NovaField>( public_parameters: &PublicParams<'ast, T>, instance: RecursiveSNARKWithStepCount<'ast, T>, -) -> CompressedSNARK<'ast, T> { - CompressedSNARK::prove(public_parameters, &instance.proof).unwrap() +) -> (CompressedSNARK<'ast, T>, VerifierKey<'ast, T>) { + let (pk, vk) = CompressedSNARK::<'ast, T>::setup(public_parameters).unwrap(); + + ( + CompressedSNARK::prove(public_parameters, &pk, &instance.proof).unwrap(), + vk, + ) +} + +pub fn verify_compressed<'ast, T: NovaField>( + proof: &CompressedSNARK<'ast, T>, + vk: &VerifierKey<'ast, T>, + arguments: Vec, + step_count: usize, +) -> bool { + let z0_primary: Vec<_> = arguments.into_iter().map(|a| a.into_bellperson()).collect(); + let z0_secondary = vec![<::Point as Group>::Base::one()]; + + proof + .verify(vk, step_count, z0_primary, z0_secondary) + .is_ok() } pub fn prove<'ast, T: NovaField>( diff --git a/zokrates_book/src/toolbox/experimental.md b/zokrates_book/src/toolbox/experimental.md index 0f348e7a..953830ba 100644 --- a/zokrates_book/src/toolbox/experimental.md +++ b/zokrates_book/src/toolbox/experimental.md @@ -52,6 +52,12 @@ Once we're done, we compress the proof to a compressed snark: zokrates nova compress ``` +Finally, we can verify this proof + +``` +zokrates nova verify +``` + ### Limitations - The step circuit must be compiled with `--curve pallas` diff --git a/zokrates_cli/src/ops/nova/compress.rs b/zokrates_cli/src/ops/nova/compress.rs index c65983a3..1adc722e 100644 --- a/zokrates_cli/src/ops/nova/compress.rs +++ b/zokrates_cli/src/ops/nova/compress.rs @@ -39,6 +39,16 @@ pub fn subcommand() -> App<'static, 'static> { .required(false) .default_value(cli_constants::JSON_PROOF_PATH), ) + .arg( + Arg::with_name("verification-key-path") + .short("v") + .long("verification-key-path") + .help("Path of the generated verification key file") + .value_name("FILE") + .takes_value(true) + .required(false) + .default_value(cli_constants::VERIFICATION_KEY_DEFAULT_PATH), + ) } pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { @@ -66,8 +76,9 @@ fn cli_nova_compress( .map_err(|why| format!("Could not deserialize {}: {}", params_path.display(), why))?; let proof_path = Path::new(sub_matches.value_of("proof-path").unwrap()); + let verification_key_path = Path::new(sub_matches.value_of("verification-key-path").unwrap()); - let proof = nova::compress(¶ms, instance); + let (proof, vk) = nova::compress(¶ms, instance); let proof_json = serde_json::to_string_pretty(&proof).unwrap(); @@ -78,5 +89,32 @@ fn cli_nova_compress( .write(proof_json.as_bytes()) .map_err(|why| format!("Could not write to {}: {}", proof_path.display(), why))?; + println!("Compressed SNARK written to '{}'", proof_path.display()); + + let verification_key_json = serde_json::to_string_pretty(&vk).unwrap(); + + let mut verification_key_file = File::create(verification_key_path).map_err(|why| { + format!( + "Could not create {}: {}", + verification_key_path.display(), + why + ) + })?; + + verification_key_file + .write(verification_key_json.as_bytes()) + .map_err(|why| { + format!( + "Could not write to {}: {}", + verification_key_path.display(), + why + ) + })?; + + println!( + "Verification key written to '{}'", + verification_key_path.display() + ); + Ok(()) } diff --git a/zokrates_cli/src/ops/nova/mod.rs b/zokrates_cli/src/ops/nova/mod.rs index 51554086..3432fa56 100644 --- a/zokrates_cli/src/ops/nova/mod.rs +++ b/zokrates_cli/src/ops/nova/mod.rs @@ -3,6 +3,7 @@ use clap::{App, AppSettings, ArgMatches, SubCommand}; pub mod compress; pub mod prove; pub mod setup; +pub mod verify; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("nova") @@ -12,6 +13,7 @@ pub fn subcommand() -> App<'static, 'static> { setup::subcommand().display_order(1), prove::subcommand().display_order(2), compress::subcommand().display_order(3), + verify::subcommand().display_order(4), ]) } @@ -20,6 +22,7 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { ("setup", Some(sub_matches)) => setup::exec(sub_matches), ("prove", Some(sub_matches)) => prove::exec(sub_matches), ("compress", Some(sub_matches)) => compress::exec(sub_matches), + ("verify", Some(sub_matches)) => verify::exec(sub_matches), _ => unreachable!(), } } diff --git a/zokrates_cli/src/ops/nova/prove.rs b/zokrates_cli/src/ops/nova/prove.rs index 78739e61..2ab33d65 100644 --- a/zokrates_cli/src/ops/nova/prove.rs +++ b/zokrates_cli/src/ops/nova/prove.rs @@ -13,7 +13,7 @@ use zokrates_bellperson::nova::{self, NovaField}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("prove") - .about("Proves a many steps of an incremental computation") + .about("Proves many steps of an incremental computation") .arg( Arg::with_name("init") .long("init") @@ -99,7 +99,9 @@ fn cli_nova_prove_step<'ast, T: NovaField, I: Iterator Result<(), String> { let proof_path = Path::new(sub_matches.value_of("instance-path").unwrap()); + println!("Reading step program..."); let program = program.collect(); + println!("Done"); let path = Path::new(sub_matches.value_of("abi-spec").unwrap()); let file = @@ -153,6 +155,8 @@ fn cli_nova_prove_step<'ast, T: NovaField, I: Iterator App<'static, 'static> { + SubCommand::with_name("verify") + .about("Verifies a Nova compressed proof") + .arg( + Arg::with_name("init") + .long("init") + .help("Path to the initial value of the public input") + .takes_value(true) + .default_value(NOVA_PUBLIC_INIT), + ) + .arg( + Arg::with_name("proof-path") + .short("j") + .long("proof-path") + .help("Path of the JSON compressed proof path") + .value_name("FILE") + .takes_value(true) + .required(false) + .default_value(cli_constants::JSON_PROOF_PATH), + ) + .arg( + Arg::with_name("verification-key-path") + .short("v") + .long("verification-key-path") + .help("Path of the generated verification key file") + .value_name("FILE") + .takes_value(true) + .required(false) + .default_value(cli_constants::VERIFICATION_KEY_DEFAULT_PATH), + ) + .arg( + Arg::with_name("instance-path") + .long("instance-path") + .help("Path of the JSON running instance file") + .value_name("FILE") + .takes_value(true) + .required(false) + .default_value(cli_constants::JSON_NOVA_RUNNING_INSTANCE), + ) + .arg( + Arg::with_name("abi-spec") + .short("s") + .long("abi-spec") + .help("Path of the ABI specification") + .value_name("FILE") + .takes_value(true) + .required(false) + .default_value(cli_constants::ABI_SPEC_DEFAULT_PATH), + ) +} + +pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { + let proof_path = sub_matches.value_of("proof-path").unwrap(); + + let proof_file = + File::open(proof_path).map_err(|why| format!("Could not open {}: {}", proof_path, why))?; + + let proof_reader = BufReader::new(proof_file); + + let verification_key_path = sub_matches.value_of("verification-key-path").unwrap(); + + let verification_key_file = File::open(verification_key_path) + .map_err(|why| format!("Could not open {}: {}", verification_key_path, why))?; + + let verification_key_reader = BufReader::new(verification_key_file); + + let proof: CompressedSNARK = serde_json::from_reader(proof_reader).unwrap(); + let vk: VerifierKey = serde_json::from_reader(verification_key_reader).unwrap(); + + cli_nova_verify(proof, vk, sub_matches) +} + +fn cli_nova_verify<'ast, T: NovaField>( + proof: CompressedSNARK<'ast, T>, + vk: VerifierKey<'ast, T>, + sub_matches: &ArgMatches, +) -> Result<(), String> { + let path = Path::new(sub_matches.value_of("abi-spec").unwrap()); + let file = + File::open(path).map_err(|why| format!("Could not open {}: {}", path.display(), why))?; + + let mut reader = BufReader::new(file); + + let abi: Abi = from_reader(&mut reader).map_err(|why| why.to_string())?; + let signature = abi.signature(); + + let init_type = signature.inputs[0].clone(); + + let init = { + let path = Path::new(sub_matches.value_of("init").unwrap()); + let file = File::open(path).unwrap(); + let reader = BufReader::new(file); + + parse_value(serde_json::from_reader(reader).unwrap(), init_type) + .unwrap() + .encode() + }; + + let instance_path = Path::new(sub_matches.value_of("instance-path").unwrap()); + let instance: RecursiveSNARKWithStepCount<'ast, T> = + serde_json::from_reader(BufReader::new(File::open(instance_path).unwrap())).unwrap(); + let steps = instance.steps; + + if nova::verify_compressed(&proof, &vk, init, steps) { + println!("Compressed proof succesfully verified"); + } else { + eprintln!("Compressed proof verification failed"); + } + + Ok(()) +} diff --git a/zokrates_field/Cargo.toml b/zokrates_field/Cargo.toml index 3c7db37b..a0c3f4e5 100644 --- a/zokrates_field/Cargo.toml +++ b/zokrates_field/Cargo.toml @@ -28,7 +28,7 @@ bellperson = { version = "0.24", default-features = false, optional = true } pairing = { version = "0.22", default-features = false, optional = true } ff = { version = "0.12.0", default-features = false, optional = true } pasta_curves = { version = "0.5.2", features = ["repr-c", "serde"], package = "fil_pasta_curves", optional = true } -nova-snark = { version = "0.13.0", optional = true } +nova-snark = { version = "0.20.3", optional = true } # ark ark-ff = { version = "^0.3.0", default-features = false } diff --git a/zokrates_field/src/lib.rs b/zokrates_field/src/lib.rs index 5bd69b0f..a0b2def2 100644 --- a/zokrates_field/src/lib.rs +++ b/zokrates_field/src/lib.rs @@ -6,6 +6,7 @@ #[cfg(feature = "bellman_extensions")] use bellman_ce::pairing::{ff::ScalarEngine, Engine}; +use nova_snark::provider::pedersen::CommitmentEngine; use num_bigint::BigUint; use num_traits::{CheckedDiv, One, Zero}; use serde::{Deserialize, Serialize}; @@ -27,7 +28,10 @@ pub trait Pow { #[cfg(feature = "bellperson_extensions")] pub trait Cycle { type Other: Field + BellpersonFieldExtensions + Cycle; - type Point: Group::Point as Group>::Scalar>; + type Point: Group< + Base = <::Point as Group>::Scalar, + CE = CommitmentEngine, + >; } #[cfg(feature = "bellman_extensions")]