diff --git a/.gitignore b/.gitignore index 9102e1e4a87..10988465b29 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,8 @@ pkg/ # Nargo output *.proof *.acir +*.acir.sha256 *.tr +*.pk +*.vk **/Verifier.toml \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5ebacc28b8d..f01f4b54d6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -394,7 +394,7 @@ dependencies = [ [[package]] name = "barretenberg_static_lib" version = "0.1.0" -source = "git+https://github.com/noir-lang/aztec_backend?rev=d0e1257c22618f98f53781faba3c372ef91a0172#d0e1257c22618f98f53781faba3c372ef91a0172" +source = "git+https://github.com/noir-lang/aztec_backend?rev=cff757dca7971161e4bd25e7a744d910c37c22be#cff757dca7971161e4bd25e7a744d910c37c22be" dependencies = [ "barretenberg_wrapper", "blake2", @@ -414,7 +414,7 @@ dependencies = [ [[package]] name = "barretenberg_wasm" version = "0.1.0" -source = "git+https://github.com/noir-lang/aztec_backend?rev=d0e1257c22618f98f53781faba3c372ef91a0172#d0e1257c22618f98f53781faba3c372ef91a0172" +source = "git+https://github.com/noir-lang/aztec_backend?rev=cff757dca7971161e4bd25e7a744d910c37c22be#cff757dca7971161e4bd25e7a744d910c37c22be" dependencies = [ "blake2", "common", @@ -720,7 +720,7 @@ dependencies = [ [[package]] name = "common" version = "0.1.0" -source = "git+https://github.com/noir-lang/aztec_backend?rev=d0e1257c22618f98f53781faba3c372ef91a0172#d0e1257c22618f98f53781faba3c372ef91a0172" +source = "git+https://github.com/noir-lang/aztec_backend?rev=cff757dca7971161e4bd25e7a744d910c37c22be#cff757dca7971161e4bd25e7a744d910c37c22be" dependencies = [ "acvm 0.4.1", "blake2", diff --git a/crates/nargo/Cargo.toml b/crates/nargo/Cargo.toml index 14704c46405..6a4092e33da 100644 --- a/crates/nargo/Cargo.toml +++ b/crates/nargo/Cargo.toml @@ -31,8 +31,8 @@ termcolor = "1.1.2" tempdir = "0.3.7" # Backends -aztec_backend = { optional = true, package = "barretenberg_static_lib", git = "https://github.com/noir-lang/aztec_backend", rev = "d0e1257c22618f98f53781faba3c372ef91a0172" } -aztec_wasm_backend = { optional = true, package = "barretenberg_wasm", git = "https://github.com/noir-lang/aztec_backend", rev = "d0e1257c22618f98f53781faba3c372ef91a0172" } +aztec_backend = { optional = true, package = "barretenberg_static_lib", git = "https://github.com/noir-lang/aztec_backend", rev = "cff757dca7971161e4bd25e7a744d910c37c22be" } +aztec_wasm_backend = { optional = true, package = "barretenberg_wasm", git = "https://github.com/noir-lang/aztec_backend", rev = "cff757dca7971161e4bd25e7a744d910c37c22be" } marlin_arkworks_backend = { optional = true, git = "https://github.com/noir-lang/marlin_arkworks_backend", rev = "144378edad821bfaa52bf2cacca8ecc87514a4fc" } color-eyre = "0.6.2" diff --git a/crates/nargo/src/cli/compile_cmd.rs b/crates/nargo/src/cli/compile_cmd.rs index 603f26d82a0..0c9abc7a17d 100644 --- a/crates/nargo/src/cli/compile_cmd.rs +++ b/crates/nargo/src/cli/compile_cmd.rs @@ -1,13 +1,13 @@ -use clap::ArgMatches; -use std::path::{Path, PathBuf}; - use acvm::ProofSystemCompiler; +use acvm::{acir::circuit::Circuit, hash_constraint_system}; +use clap::ArgMatches; use noirc_abi::input_parser::Format; +use std::path::{Path, PathBuf}; use super::{add_std_lib, create_named_dir, read_inputs_from_file, write_to_file}; use crate::{ cli::execute_cmd::save_witness_to_dir, - constants::{ACIR_EXT, PROVER_INPUT_FILE, TARGET_DIR}, + constants::{ACIR_EXT, PK_EXT, PROVER_INPUT_FILE, TARGET_DIR, VK_EXT}, errors::CliError, resolver::Resolver, }; @@ -33,7 +33,6 @@ pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { .map(|_| ()) } -#[allow(deprecated)] pub fn generate_circuit_and_witness_to_disk>( circuit_name: &str, program_dir: P, @@ -42,13 +41,12 @@ pub fn generate_circuit_and_witness_to_disk>( allow_warnings: bool, ) -> Result { let compiled_program = compile_circuit(program_dir.as_ref(), false, allow_warnings)?; - let serialized = compiled_program.circuit.to_bytes(); - let mut circuit_path = create_named_dir(circuit_dir.as_ref(), "build"); - circuit_path.push(circuit_name); - circuit_path.set_extension(ACIR_EXT); - let path = write_to_file(serialized.as_slice(), &circuit_path); - println!("Generated ACIR code into {path}"); + preprocess_with_path(circuit_name, circuit_dir.as_ref(), compiled_program.circuit.clone())?; + + let mut circuit_path = + save_acir_to_dir(compiled_program.circuit.clone(), circuit_name, circuit_dir.as_ref()); + println!("Generated ACIR code into {}", circuit_path.display()); if generate_witness { // Parse the initial witness values from Prover.toml @@ -82,3 +80,58 @@ pub fn compile_circuit>( .into_compiled_program(backend.np_language(), show_ssa, allow_warnings) .map_err(|_| std::process::exit(1)) } + +pub fn preprocess_with_path>( + key_name: &str, + preprocess_dir: P, + circuit: Circuit, +) -> Result<(PathBuf, PathBuf), CliError> { + let backend = crate::backends::ConcreteBackend; + + let (proving_key, verification_key) = backend.preprocess(circuit); + + let pk_path = save_key_to_dir(proving_key, key_name, &preprocess_dir, true)?; + println!("Proving key saved to {}", pk_path.display()); + let vk_path = save_key_to_dir(verification_key, key_name, preprocess_dir, false)?; + println!("Verification key saved to {}", vk_path.display()); + + Ok((pk_path, vk_path)) +} + +fn save_acir_to_dir>( + circuit: Circuit, + circuit_name: &str, + circuit_dir: P, +) -> PathBuf { + let mut circuit_path = create_named_dir(circuit_dir.as_ref(), "target"); + circuit_path.push(circuit_name); + + // Save a checksum of the circuit to compare against during proving and verification + let acir_hash = hash_constraint_system(&circuit); + circuit_path.set_extension(ACIR_EXT.to_owned() + ".sha256"); + write_to_file(hex::encode(acir_hash).as_bytes(), &circuit_path); + + let mut serialized = Vec::new(); + circuit.write(&mut serialized).expect("could not serialize circuit"); + + circuit_path.set_extension(ACIR_EXT); + write_to_file(serialized.as_slice(), &circuit_path); + + circuit_path +} + +fn save_key_to_dir>( + key: Vec, + key_name: &str, + key_dir: P, + is_proving_key: bool, +) -> Result { + let mut key_path = create_named_dir(key_dir.as_ref(), key_name); + key_path.push(key_name); + let extension = if is_proving_key { PK_EXT } else { VK_EXT }; + key_path.set_extension(extension); + + write_to_file(hex::encode(key).as_bytes(), &key_path); + + Ok(key_path) +} diff --git a/crates/nargo/src/cli/mod.rs b/crates/nargo/src/cli/mod.rs index 30e261564bb..000b6c3c470 100644 --- a/crates/nargo/src/cli/mod.rs +++ b/crates/nargo/src/cli/mod.rs @@ -1,6 +1,9 @@ use acvm::{ - acir::{circuit::PublicInputs, native_types::Witness}, - FieldElement, + acir::{ + circuit::{Circuit, PublicInputs}, + native_types::Witness, + }, + hash_constraint_system, FieldElement, ProofSystemCompiler, }; pub use check_cmd::check_from_path; use clap::{App, AppSettings, Arg}; @@ -20,7 +23,10 @@ use std::{ extern crate tempdir; use tempdir::TempDir; -use crate::errors::CliError; +use crate::{ + constants::{ACIR_EXT, PK_EXT, VK_EXT}, + errors::CliError, +}; mod check_cmd; mod compile_cmd; @@ -79,6 +85,9 @@ pub fn start_cli() { App::new("verify") .about("Given a proof and a program, verify whether the proof is valid") .arg(Arg::with_name("proof").help("The proof to verify").required(true)) + .arg(Arg::with_name("circuit_name").help( + "The name of the circuit build files (ACIR, proving and verification keys)", + )) .arg(allow_warnings.clone()), ) .subcommand( @@ -87,6 +96,9 @@ pub fn start_cli() { "Create proof for this program. The proof is returned as a hex encoded string.", ) .arg(Arg::with_name("proof_name").help("The name of the proof")) + .arg(Arg::with_name("circuit_name").help( + "The name of the circuit build files (ACIR, proving and verification keys)", + )) .arg(Arg::with_name("verify").long("verify").help("Verify proof after proving")) .arg(show_ssa.clone()) .arg(allow_warnings.clone()), @@ -134,7 +146,7 @@ pub fn start_cli() { .takes_value(true), ) .arg(show_ssa) - .arg(allow_warnings), + .arg(allow_warnings.clone()), ) .setting(AppSettings::SubcommandRequiredElseHelp) .get_matches(); @@ -228,6 +240,7 @@ pub fn prove_and_verify(proof_name: &str, prg_dir: &Path, show_ssa: bool) -> boo Some(proof_name), prg_dir, &tmp_dir.into_path(), + None, true, show_ssa, false, @@ -289,6 +302,64 @@ pub(crate) fn dedup_public_input_indices_values( ) } +pub fn load_hex_data>(path: P) -> Result, CliError> { + let hex_data: Vec<_> = + std::fs::read(&path).map_err(|_| CliError::PathNotValid(path.as_ref().to_path_buf()))?; + + let raw_bytes = hex::decode(hex_data).map_err(CliError::HexArtifactNotValid)?; + + Ok(raw_bytes) +} + +fn fetch_pk_and_vk>( + circuit: Circuit, + circuit_build_path: Option

, + prove_circuit: bool, + check_proof: bool, +) -> Result<(Vec, Vec), CliError> { + let backend = crate::backends::ConcreteBackend; + if let Some(circuit_build_path) = circuit_build_path { + let mut acir_hash_path = PathBuf::new(); + acir_hash_path.push(circuit_build_path.as_ref()); + acir_hash_path.set_extension(ACIR_EXT.to_owned() + ".sha256"); + let expected_acir_hash = load_hex_data(acir_hash_path.clone())?; + + let new_acir_hash = hash_constraint_system(&circuit); + + if new_acir_hash[..] != expected_acir_hash { + return Err(CliError::MismatchedAcir(acir_hash_path)); + } + + // This flag exists to avoid an unnecessary read of the proving key during verification + // as this method is used by both `nargo prove` and `nargo verify` + let proving_key = if prove_circuit { + let mut proving_key_path = PathBuf::new(); + proving_key_path.push(circuit_build_path.as_ref()); + proving_key_path.set_extension(PK_EXT); + load_hex_data(proving_key_path)? + } else { + // We can return an empty Vec here as `prove_circuit` should only be false when running `nargo verify` + vec![] + }; + + let verification_key = if check_proof { + let mut verification_key_path = PathBuf::new(); + verification_key_path.push(circuit_build_path); + verification_key_path.set_extension(VK_EXT); + load_hex_data(verification_key_path)? + } else { + // We can return an empty Vec here as the verification key is used only is `check_proof` is true + vec![] + }; + + Ok((proving_key, verification_key)) + } else { + // If a path to the circuit's build dir has not been provided, run preprocess and generate the proving and verification keys + let (proving_key, verification_key) = backend.preprocess(circuit); + Ok((proving_key, verification_key)) + } +} + // FIXME: I not sure that this is the right place for this tests. #[cfg(test)] mod tests { diff --git a/crates/nargo/src/cli/prove_cmd.rs b/crates/nargo/src/cli/prove_cmd.rs index d67cdceffb9..9cb5401064a 100644 --- a/crates/nargo/src/cli/prove_cmd.rs +++ b/crates/nargo/src/cli/prove_cmd.rs @@ -5,21 +5,22 @@ use clap::ArgMatches; use noirc_abi::input_parser::Format; use super::{ - create_named_dir, dedup_public_input_indices, read_inputs_from_file, write_inputs_to_file, - write_to_file, + create_named_dir, dedup_public_input_indices, fetch_pk_and_vk, read_inputs_from_file, + write_inputs_to_file, write_to_file, }; use crate::{ cli::{ execute_cmd::{execute_program, extract_public_inputs}, verify_cmd::verify_proof, }, - constants::{PROOFS_DIR, PROOF_EXT, PROVER_INPUT_FILE, VERIFIER_INPUT_FILE}, + constants::{PROOFS_DIR, PROOF_EXT, PROVER_INPUT_FILE, TARGET_DIR, VERIFIER_INPUT_FILE}, errors::CliError, }; pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { let args = args.subcommand_matches("prove").unwrap(); let proof_name = args.value_of("proof_name"); + let circuit_name = args.value_of("circuit_name"); let check_proof = args.is_present("verify"); let show_ssa = args.is_present("show-ssa"); let allow_warnings = args.is_present("allow-warnings"); @@ -30,7 +31,24 @@ pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { let mut proof_dir = program_dir.clone(); proof_dir.push(PROOFS_DIR); - prove_with_path(proof_name, program_dir, proof_dir, check_proof, show_ssa, allow_warnings)?; + let circuit_build_path = if let Some(circuit_name) = circuit_name { + let mut circuit_build_path = program_dir.clone(); + circuit_build_path.push(TARGET_DIR); + circuit_build_path.push(circuit_name); + Some(circuit_build_path) + } else { + None + }; + + prove_with_path( + proof_name, + program_dir, + proof_dir, + circuit_build_path, + check_proof, + show_ssa, + allow_warnings, + )?; Ok(()) } @@ -39,12 +57,19 @@ pub fn prove_with_path>( proof_name: Option<&str>, program_dir: P, proof_dir: P, + circuit_build_path: Option

, check_proof: bool, show_ssa: bool, allow_warnings: bool, ) -> Result, CliError> { let compiled_program = super::compile_cmd::compile_circuit(program_dir.as_ref(), show_ssa, allow_warnings)?; + let (proving_key, verification_key) = fetch_pk_and_vk( + compiled_program.circuit.clone(), + circuit_build_path.as_ref(), + true, + check_proof, + )?; // Parse the initial witness values from Prover.toml let inputs_map = read_inputs_from_file( @@ -67,11 +92,11 @@ pub fn prove_with_path>( prover_circuit.public_inputs = dedup_public_input_indices(prover_circuit.public_inputs); let backend = crate::backends::ConcreteBackend; - let proof = backend.prove_with_meta(prover_circuit, solved_witness); + let proof = backend.prove_with_pk(prover_circuit, solved_witness, proving_key); println!("Proof successfully created"); if check_proof { - let valid_proof = verify_proof(compiled_program, public_inputs, &proof)?; + let valid_proof = verify_proof(compiled_program, public_inputs, &proof, verification_key)?; println!("Proof verified : {valid_proof}"); if !valid_proof { return Err(CliError::Generic("Could not verify generated proof".to_owned())); diff --git a/crates/nargo/src/cli/verify_cmd.rs b/crates/nargo/src/cli/verify_cmd.rs index d26d26bdc96..44ce251556e 100644 --- a/crates/nargo/src/cli/verify_cmd.rs +++ b/crates/nargo/src/cli/verify_cmd.rs @@ -1,9 +1,9 @@ use super::{ - compile_cmd::compile_circuit, dedup_public_input_indices_values, read_inputs_from_file, - InputMap, + compile_cmd::compile_circuit, dedup_public_input_indices_values, fetch_pk_and_vk, + load_hex_data, read_inputs_from_file, InputMap, }; use crate::{ - constants::{PROOFS_DIR, PROOF_EXT, VERIFIER_INPUT_FILE}, + constants::{PROOFS_DIR, PROOF_EXT, TARGET_DIR, VERIFIER_INPUT_FILE}, errors::CliError, }; use acvm::ProofSystemCompiler; @@ -28,19 +28,37 @@ pub(crate) fn run(args: ArgMatches) -> Result<(), CliError> { proof_path.push(Path::new(proof_name)); proof_path.set_extension(PROOF_EXT); + let circuit_name = args.value_of("circuit_name"); + let allow_warnings = args.is_present("allow-warnings"); - let result = verify_with_path(program_dir, proof_path, false, allow_warnings)?; + + let circuit_build_path = if let Some(circuit_name) = circuit_name { + let mut circuit_build_path = program_dir.clone(); + circuit_build_path.push(TARGET_DIR); + circuit_build_path.push(circuit_name); + Some(circuit_build_path) + } else { + None + }; + + let result = + verify_with_path(program_dir, proof_path, circuit_build_path, false, allow_warnings)?; println!("Proof verified : {result}"); + Ok(()) } pub fn verify_with_path>( program_dir: P, proof_path: P, + circuit_build_path: Option

, show_ssa: bool, allow_warnings: bool, ) -> Result { let compiled_program = compile_circuit(program_dir.as_ref(), show_ssa, allow_warnings)?; + let (_, verification_key) = + fetch_pk_and_vk(compiled_program.circuit.clone(), circuit_build_path, false, true)?; + let mut public_inputs_map: InputMap = BTreeMap::new(); // Load public inputs (if any) from `VERIFIER_INPUT_FILE`. @@ -52,7 +70,12 @@ pub fn verify_with_path>( read_inputs_from_file(current_dir, VERIFIER_INPUT_FILE, Format::Toml, public_abi)?; } - let valid_proof = verify_proof(compiled_program, public_inputs_map, &load_proof(proof_path)?)?; + let valid_proof = verify_proof( + compiled_program, + public_inputs_map, + &load_hex_data(proof_path)?, + verification_key, + )?; Ok(valid_proof) } @@ -61,6 +84,7 @@ pub(crate) fn verify_proof( mut compiled_program: CompiledProgram, public_inputs_map: InputMap, proof: &[u8], + verification_key: Vec, ) -> Result { let public_abi = compiled_program.abi.unwrap().public_abi(); let public_inputs = @@ -78,15 +102,12 @@ pub(crate) fn verify_proof( compiled_program.circuit.public_inputs = dedup_public_indices; let backend = crate::backends::ConcreteBackend; - let valid_proof = backend.verify_from_cs(proof, dedup_public_values, compiled_program.circuit); + let valid_proof = backend.verify_with_vk( + proof, + dedup_public_values, + compiled_program.circuit, + verification_key, + ); Ok(valid_proof) } - -fn load_proof>(proof_path: P) -> Result, CliError> { - let proof_hex: Vec<_> = std::fs::read(&proof_path) - .map_err(|_| CliError::PathNotValid(proof_path.as_ref().to_path_buf()))?; - let proof = hex::decode(proof_hex).map_err(CliError::ProofNotValid)?; - - Ok(proof) -} diff --git a/crates/nargo/src/constants.rs b/crates/nargo/src/constants.rs index 2a74c57487a..a06369352b3 100644 --- a/crates/nargo/src/constants.rs +++ b/crates/nargo/src/constants.rs @@ -23,3 +23,7 @@ pub(crate) const PROOF_EXT: &str = "proof"; pub(crate) const ACIR_EXT: &str = "acir"; /// The extension for files containing proof witnesses. pub(crate) const WITNESS_EXT: &str = "tr"; +/// The extension for proving keys. +pub(crate) const PK_EXT: &str = "pk"; +/// The extension for verification keys. +pub(crate) const VK_EXT: &str = "vk"; diff --git a/crates/nargo/src/errors.rs b/crates/nargo/src/errors.rs index fc3db1c9656..f86e947302c 100644 --- a/crates/nargo/src/errors.rs +++ b/crates/nargo/src/errors.rs @@ -11,14 +11,20 @@ pub enum CliError { Generic(String), #[error("Error: destination {} already exists", .0.display())] DestinationAlreadyExists(PathBuf), - #[error("Error: {} is not a valid path", .0.display())] + #[error("Error: {} is not a valid path\nRun either `nargo compile` to generate missing build artifacts or `nargo prove` to construct a proof", .0.display())] PathNotValid(PathBuf), - #[error("Error: could not parse proof data ({0})")] - ProofNotValid(FromHexError), + #[error("Error: could not parse hex build artifact (proof, proving and/or verification keys, ACIR checksum) ({0})")] + HexArtifactNotValid(FromHexError), #[error( " Error: cannot find {0}.toml file.\n Expected location: {1:?} \n Please generate this file at the expected location." )] MissingTomlFile(String, PathBuf), + #[error("Error: cannot find proving key located at {}\nEither run `nargo compile` to generate the missing proving key or check that the correct file name has been provided", .0.display())] + MissingProvingKey(PathBuf), + #[error("Error: cannot find verification key located at {}\nEither run `nargo compile` to generate the missing verification key or check that the correct file name has been provided", .0.display())] + MissingVerificationkey(PathBuf), + #[error("Error: the circuit you are trying to prove differs from the build artifact at {}\nYou must call `nargo compile` to generate the correct proving and verification keys for this circuit", .0.display())] + MismatchedAcir(PathBuf), } impl From for CliError { diff --git a/cspell.json b/cspell.json index 20750af1753..5126d7a0bdf 100644 --- a/cspell.json +++ b/cspell.json @@ -31,6 +31,7 @@ "monomorphizer", "pedersen", "peekable", + "preprocess", "schnorr", "sdiv", "signedness",