diff --git a/Cargo.lock b/Cargo.lock index c4d231f3778..274eac0bce2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2245,7 +2245,9 @@ name = "nargo" version = "0.3.2" dependencies = [ "acvm 0.8.0", + "iter-extended", "noirc_abi", + "noirc_driver", "rustc_version 0.4.0", "serde", "thiserror", diff --git a/crates/nargo/Cargo.toml b/crates/nargo/Cargo.toml index 17827c6b60f..430d926cc9e 100644 --- a/crates/nargo/Cargo.toml +++ b/crates/nargo/Cargo.toml @@ -13,6 +13,8 @@ rustc_version = "0.4.0" [dependencies] acvm.workspace = true noirc_abi.workspace = true +noirc_driver.workspace = true +iter-extended.workspace = true toml.workspace = true serde.workspace = true thiserror.workspace = true diff --git a/crates/nargo/src/artifacts/contract.rs b/crates/nargo/src/artifacts/contract.rs new file mode 100644 index 00000000000..95f1ce9576d --- /dev/null +++ b/crates/nargo/src/artifacts/contract.rs @@ -0,0 +1,41 @@ +use acvm::acir::circuit::Circuit; +use noirc_abi::Abi; +use noirc_driver::ContractFunctionType; +use serde::{Deserialize, Serialize}; + +/// `PreprocessedContract` represents a Noir contract which has been preprocessed by a particular backend proving system. +/// +/// This differs from a generic Noir contract artifact in that: +/// - The ACIR bytecode has had an optimization pass applied to tailor it for the backend. +/// - Proving and verification keys have been pregenerated based on this ACIR. +#[derive(Serialize, Deserialize)] +pub struct PreprocessedContract { + /// The name of the contract. + pub name: String, + /// The identifier of the proving backend which this contract has been compiled for. + pub backend: String, + /// Each of the contract's functions are compiled into a separate program stored in this `Vec`. + pub functions: Vec, +} + +/// Each function in the contract will be compiled as a separate noir program. +/// +/// A contract function unlike a regular Noir program however can have additional properties. +/// One of these being a function type. +#[derive(Debug, Serialize, Deserialize)] +pub struct PreprocessedContractFunction { + pub name: String, + + pub function_type: ContractFunctionType, + + pub abi: Abi, + + #[serde( + serialize_with = "super::serialize_circuit", + deserialize_with = "super::deserialize_circuit" + )] + pub bytecode: Circuit, + + pub proving_key: Vec, + pub verification_key: Vec, +} diff --git a/crates/nargo/src/artifacts/mod.rs b/crates/nargo/src/artifacts/mod.rs new file mode 100644 index 00000000000..400254bfb0d --- /dev/null +++ b/crates/nargo/src/artifacts/mod.rs @@ -0,0 +1,31 @@ +//! This module defines the structure of Nargo's different compilation artifacts. +//! +//! These artifacts are intended to remain independent of any applications being built on top of Noir. +//! Should any projects require/desire a different artifact format, it's expected that they will write a transformer +//! to generate them using these artifacts as a starting point. + +use acvm::acir::circuit::Circuit; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +pub mod contract; +pub mod program; + +// TODO: move these down into ACVM. +fn serialize_circuit(circuit: &Circuit, s: S) -> Result +where + S: Serializer, +{ + let mut circuit_bytes: Vec = Vec::new(); + circuit.write(&mut circuit_bytes).unwrap(); + + circuit_bytes.serialize(s) +} + +fn deserialize_circuit<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let circuit_bytes = Vec::::deserialize(deserializer)?; + let circuit = Circuit::read(&*circuit_bytes).unwrap(); + Ok(circuit) +} diff --git a/crates/nargo/src/artifacts/program.rs b/crates/nargo/src/artifacts/program.rs new file mode 100644 index 00000000000..288a5dba99b --- /dev/null +++ b/crates/nargo/src/artifacts/program.rs @@ -0,0 +1,23 @@ +use acvm::acir::circuit::Circuit; +use noirc_abi::Abi; +use serde::{Deserialize, Serialize}; + +/// `PreprocessedProgram` represents a Noir program which has been preprocessed by a particular backend proving system. +/// +/// This differs from a generic Noir program artifact in that: +/// - The ACIR bytecode has had an optimization pass applied to tailor it for the backend. +/// - Proving and verification keys have been pregenerated based on this ACIR. +#[derive(Serialize, Deserialize, Debug)] +pub struct PreprocessedProgram { + pub backend: String, + pub abi: Abi, + + #[serde( + serialize_with = "super::serialize_circuit", + deserialize_with = "super::deserialize_circuit" + )] + pub bytecode: Circuit, + + pub proving_key: Vec, + pub verification_key: Vec, +} diff --git a/crates/nargo/src/lib.rs b/crates/nargo/src/lib.rs index 94c24714a0a..24605de7849 100644 --- a/crates/nargo/src/lib.rs +++ b/crates/nargo/src/lib.rs @@ -7,6 +7,7 @@ //! This name was used because it sounds like `cargo` and //! Noir Package Manager abbreviated is npm, which is already taken. +pub mod artifacts; mod errors; pub mod manifest; pub mod ops; diff --git a/crates/nargo/src/ops/mod.rs b/crates/nargo/src/ops/mod.rs index 578fc1ebbef..5d1f096ecf4 100644 --- a/crates/nargo/src/ops/mod.rs +++ b/crates/nargo/src/ops/mod.rs @@ -1,7 +1,7 @@ pub use self::codegen_verifier::codegen_verifier; pub use self::execute::execute_circuit; -pub use self::preprocess::{checksum_acir, preprocess_circuit, PreprocessedData}; -pub use self::prove::prove; +pub use self::preprocess::{preprocess_contract, preprocess_program}; +pub use self::prove::prove_execution; pub use self::verify::verify_proof; mod codegen_verifier; diff --git a/crates/nargo/src/ops/preprocess.rs b/crates/nargo/src/ops/preprocess.rs index 3046f1e7dd0..f8d4eb5a825 100644 --- a/crates/nargo/src/ops/preprocess.rs +++ b/crates/nargo/src/ops/preprocess.rs @@ -1,31 +1,60 @@ -use acvm::acir::circuit::Circuit; -use acvm::{checksum_constraint_system, ProofSystemCompiler}; +use acvm::ProofSystemCompiler; +use iter_extended::vecmap; +use noirc_driver::{CompiledContract, CompiledProgram}; -use crate::NargoError; +use crate::{ + artifacts::{ + contract::{PreprocessedContract, PreprocessedContractFunction}, + program::PreprocessedProgram, + }, + NargoError, +}; -pub fn checksum_acir(circuit: &Circuit) -> [u8; 4] { - checksum_constraint_system(circuit).to_be_bytes() -} +// TODO: pull this from backend. +const BACKEND_IDENTIFIER: &str = "acvm-backend-barretenberg"; + +pub fn preprocess_program( + backend: &impl ProofSystemCompiler, + compiled_program: CompiledProgram, +) -> Result { + // TODO: currently `compiled_program`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = compiled_program.circuit; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); -/// The result of preprocessing the ACIR bytecode. -/// The proving, verification key and circuit are backend specific. -/// -/// The circuit is backend specific because at the end of compilation -/// an optimization pass is applied which will transform the bytecode into -/// a format that the backend will accept; removing unsupported gates -/// is one example of this. -pub struct PreprocessedData { - pub proving_key: Vec, - pub verification_key: Vec, - pub program_checksum: [u8; 4], + Ok(PreprocessedProgram { + backend: String::from(BACKEND_IDENTIFIER), + abi: compiled_program.abi, + bytecode: optimized_bytecode, + proving_key, + verification_key, + }) } -pub fn preprocess_circuit( +pub fn preprocess_contract( backend: &impl ProofSystemCompiler, - circuit: &Circuit, -) -> Result { - let (proving_key, verification_key) = backend.preprocess(circuit); - let program_checksum = checksum_acir(circuit); + compiled_contract: CompiledContract, +) -> Result { + let preprocessed_contract_functions = vecmap(compiled_contract.functions, |func| { + // TODO: currently `func`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = func.bytecode; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedContractFunction { + name: func.name, + function_type: func.function_type, + abi: func.abi, + + bytecode: optimized_bytecode, + proving_key, + verification_key, + } + }); - Ok(PreprocessedData { proving_key, verification_key, program_checksum }) + Ok(PreprocessedContract { + name: compiled_contract.name, + backend: String::from(BACKEND_IDENTIFIER), + functions: preprocessed_contract_functions, + }) } diff --git a/crates/nargo/src/ops/prove.rs b/crates/nargo/src/ops/prove.rs index fc7ddcd4cb6..376220a8a74 100644 --- a/crates/nargo/src/ops/prove.rs +++ b/crates/nargo/src/ops/prove.rs @@ -4,7 +4,7 @@ use noirc_abi::WitnessMap; use crate::NargoError; -pub fn prove( +pub fn prove_execution( backend: &impl ProofSystemCompiler, circuit: &Circuit, solved_witness: WitnessMap, diff --git a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs index a9e233d3c10..3707214102e 100644 --- a/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs +++ b/crates/nargo_cli/src/cli/codegen_verifier_cmd.rs @@ -1,8 +1,8 @@ use super::fs::{create_named_dir, write_to_file}; use super::NargoConfig; use crate::{cli::compile_cmd::compile_circuit, constants::CONTRACT_DIR, errors::CliError}; -use acvm::SmartContract; use clap::Args; +use nargo::ops::{codegen_verifier, preprocess_program}; use noirc_driver::CompileOptions; /// Generates a Solidity verifier smart contract for the program @@ -13,12 +13,12 @@ pub(crate) struct CodegenVerifierCommand { } pub(crate) fn run(args: CodegenVerifierCommand, config: NargoConfig) -> Result<(), CliError> { - let compiled_program = compile_circuit(&config.program_dir, &args.compile_options)?; - - // TODO: replace with `nargo::ops::codegen_verifier` let backend = crate::backends::ConcreteBackend; - #[allow(deprecated)] - let smart_contract_string = backend.eth_contract_from_cs(compiled_program.circuit); + + let compiled_program = compile_circuit(&backend, &config.program_dir, &args.compile_options)?; + let preprocessed_program = preprocess_program(&backend, compiled_program)?; + + let smart_contract_string = codegen_verifier(&backend, &preprocessed_program.verification_key)?; let contract_dir = config.program_dir.join(CONTRACT_DIR); create_named_dir(&contract_dir, "contract"); diff --git a/crates/nargo_cli/src/cli/compile_cmd.rs b/crates/nargo_cli/src/cli/compile_cmd.rs index 7691c3ebc62..0c68de9d58e 100644 --- a/crates/nargo_cli/src/cli/compile_cmd.rs +++ b/crates/nargo_cli/src/cli/compile_cmd.rs @@ -1,15 +1,16 @@ use acvm::ProofSystemCompiler; -use nargo::ops::preprocess_circuit; -use noirc_driver::{CompileOptions, CompiledContract, CompiledProgram, Driver}; +use iter_extended::try_vecmap; +use noirc_driver::{CompileOptions, CompiledProgram, Driver}; use std::path::Path; use clap::Args; +use nargo::ops::{preprocess_contract, preprocess_program}; + use crate::resolver::DependencyResolutionError; use crate::{constants::TARGET_DIR, errors::CliError, resolver::Resolver}; use super::fs::program::{save_contract_to_file, save_program_to_file}; -use super::preprocess_cmd::save_preprocess_data; use super::NargoConfig; /// Compile the program and its secret execution trace into ACIR format @@ -29,99 +30,39 @@ pub(crate) struct CompileCommand { pub(crate) fn run(args: CompileCommand, config: NargoConfig) -> Result<(), CliError> { let circuit_dir = config.program_dir.join(TARGET_DIR); + let backend = crate::backends::ConcreteBackend; + // If contracts is set we're compiling every function in a 'contract' rather than just 'main'. if args.contracts { - let mut driver = setup_driver(&config.program_dir)?; - let mut compiled_contracts = driver + let mut driver = setup_driver(&backend, &config.program_dir)?; + let compiled_contracts = driver .compile_contracts(&args.compile_options) .map_err(|_| CliError::CompilationError)?; - save_and_preprocess_contract(&mut compiled_contracts, &args.circuit_name, &circuit_dir) + let preprocessed_contracts = + try_vecmap(compiled_contracts, |contract| preprocess_contract(&backend, contract))?; + for contract in preprocessed_contracts { + save_contract_to_file(&contract, &args.circuit_name, &circuit_dir); + } } else { - let program = compile_circuit(&config.program_dir, &args.compile_options)?; - save_and_preprocess_program(&program, &args.circuit_name, &circuit_dir) + let program = compile_circuit(&backend, &config.program_dir, &args.compile_options)?; + let preprocessed_program = preprocess_program(&backend, program)?; + save_program_to_file(&preprocessed_program, &args.circuit_name, circuit_dir); } -} - -fn setup_driver(program_dir: &Path) -> Result { - let backend = crate::backends::ConcreteBackend; - Resolver::resolve_root_manifest(program_dir, backend.np_language()) -} - -/// Save a program to disk along with proving and verification keys. -fn save_and_preprocess_program( - compiled_program: &CompiledProgram, - circuit_name: &str, - circuit_dir: &Path, -) -> Result<(), CliError> { - save_program_to_file(compiled_program, circuit_name, circuit_dir); - - let backend = crate::backends::ConcreteBackend; - let preprocessed_data = preprocess_circuit(&backend, &compiled_program.circuit)?; - save_preprocess_data(&preprocessed_data, circuit_name, circuit_dir)?; Ok(()) } -/// Save a contract to disk along with proving and verification keys. -/// - The contract ABI is saved as one file, which contains all of the -/// functions defined in the contract. -/// - The proving and verification keys are namespaced since the file -/// could contain multiple contracts with the same name. The verification key is saved inside -/// of the ABI. -fn save_and_preprocess_contract( - compiled_contracts: &mut [CompiledContract], - circuit_name: &str, - circuit_dir: &Path, -) -> Result<(), CliError> { - for compiled_contract in compiled_contracts { - // Preprocess all contract data - // We are patching the verification key in our contract functions - // so when we save it to disk, the ABI will have the verification key. - let backend = crate::backends::ConcreteBackend; - let mut contract_preprocess_data = Vec::new(); - for contract_function in &mut compiled_contract.functions { - let preprocessed_data = preprocess_circuit(&backend, &contract_function.bytecode)?; - contract_function.verification_key = Some(preprocessed_data.verification_key.clone()); - contract_preprocess_data.push(preprocessed_data); - } - - // Unique identifier for a contract. - let contract_id = format!("{}-{}", circuit_name, &compiled_contract.name); - - // Save contract ABI to file using the contract ID. - // This includes the verification keys for each contract function. - save_contract_to_file(compiled_contract, &contract_id, circuit_dir); - - // Save preprocessed data to disk - // - // TODO: This also includes the verification key, for now we save it in twice - // TODO, once in ABI and once to disk as we did before. - // TODO: A possible fix is to use optional fields in PreprocessedData - // TODO struct. Then make VK None before saving so it is not saved to disk - for (contract_function, preprocessed_data) in - compiled_contract.functions.iter().zip(contract_preprocess_data) - { - // Create a name which uniquely identifies this contract function - // over multiple contracts. - let uniquely_identifying_program_name = - format!("{}-{}", contract_id, contract_function.name); - // Each program in a contract is preprocessed - // Note: This can potentially be quite a long running process - - save_preprocess_data( - &preprocessed_data, - &uniquely_identifying_program_name, - circuit_dir, - )?; - } - } - - Ok(()) +fn setup_driver( + backend: &impl ProofSystemCompiler, + program_dir: &Path, +) -> Result { + Resolver::resolve_root_manifest(program_dir, backend.np_language()) } pub(crate) fn compile_circuit( + backend: &impl ProofSystemCompiler, program_dir: &Path, compile_options: &CompileOptions, ) -> Result { - let mut driver = setup_driver(program_dir)?; + let mut driver = setup_driver(backend, program_dir)?; driver.compile_main(compile_options).map_err(|_| CliError::CompilationError) } diff --git a/crates/nargo_cli/src/cli/execute_cmd.rs b/crates/nargo_cli/src/cli/execute_cmd.rs index e7ecdc543e3..9d1429bbda7 100644 --- a/crates/nargo_cli/src/cli/execute_cmd.rs +++ b/crates/nargo_cli/src/cli/execute_cmd.rs @@ -1,5 +1,6 @@ use std::path::Path; +use acvm::PartialWitnessGenerator; use clap::Args; use noirc_abi::input_parser::{Format, InputValue}; use noirc_abi::{InputMap, WitnessMap}; @@ -45,13 +46,15 @@ fn execute_with_path( program_dir: &Path, compile_options: &CompileOptions, ) -> Result<(Option, WitnessMap), CliError> { - let compiled_program = compile_circuit(program_dir, compile_options)?; + let backend = crate::backends::ConcreteBackend; + + let compiled_program = compile_circuit(&backend, program_dir, compile_options)?; // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file(program_dir, PROVER_INPUT_FILE, Format::Toml, &compiled_program.abi)?; - let solved_witness = execute_program(&compiled_program, &inputs_map)?; + let solved_witness = execute_program(&backend, &compiled_program, &inputs_map)?; let public_abi = compiled_program.abi.public_abi(); let (_, return_value) = public_abi.decode(&solved_witness)?; @@ -60,14 +63,14 @@ fn execute_with_path( } pub(crate) fn execute_program( + backend: &impl PartialWitnessGenerator, compiled_program: &CompiledProgram, inputs_map: &InputMap, ) -> Result { let initial_witness = compiled_program.abi.encode(inputs_map, None)?; - let backend = crate::backends::ConcreteBackend; let solved_witness = - nargo::ops::execute_circuit(&backend, compiled_program.circuit.clone(), initial_witness)?; + nargo::ops::execute_circuit(backend, compiled_program.circuit.clone(), initial_witness)?; Ok(solved_witness) } diff --git a/crates/nargo_cli/src/cli/fs/keys.rs b/crates/nargo_cli/src/cli/fs/keys.rs deleted file mode 100644 index 32920e51406..00000000000 --- a/crates/nargo_cli/src/cli/fs/keys.rs +++ /dev/null @@ -1,88 +0,0 @@ -use super::{create_named_dir, load_hex_data, write_to_file}; -use crate::{ - constants::{ACIR_CHECKSUM, PK_EXT, VK_EXT}, - errors::CliError, -}; -use acvm::acir::circuit::Circuit; -use nargo::ops::checksum_acir; -use std::path::{Path, PathBuf}; - -pub(crate) fn save_key_to_dir>( - key: &[u8], - key_name: &str, - key_dir: P, - is_proving_key: bool, -) -> Result { - create_named_dir(key_dir.as_ref(), key_name); - - let extension = if is_proving_key { PK_EXT } else { VK_EXT }; - let key_path = key_dir.as_ref().join(key_name).with_extension(extension); - - write_to_file(hex::encode(key).as_bytes(), &key_path); - - Ok(key_path) -} - -pub(crate) fn fetch_pk_and_vk>( - circuit: &Circuit, - circuit_build_path: P, - prove_circuit: bool, - check_proof: bool, -) -> Result<(Vec, Vec), CliError> { - let acir_hash_path = circuit_build_path.as_ref().with_extension(ACIR_CHECKSUM); - - let expected_acir_checksum = load_hex_data(acir_hash_path.clone())?; - let new_acir_checksum = checksum_acir(circuit); - - if new_acir_checksum[..] != expected_acir_checksum { - 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 proving_key_path = circuit_build_path.as_ref().with_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 verification_key_path = circuit_build_path.as_ref().with_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)) -} - -#[cfg(test)] -mod tests { - use super::fetch_pk_and_vk; - use crate::cli::fs::{keys::save_key_to_dir, program::save_acir_checksum_to_dir}; - use acvm::acir::circuit::Circuit; - use nargo::ops::checksum_acir; - use tempdir::TempDir; - - #[test] - fn fetching_pk_and_vk_loads_expected_keys() { - let circuit = Circuit::default(); - let circuit_name = "my_circuit"; - let mut circuit_build_path = TempDir::new("temp_circuit_hash_dir").unwrap().into_path(); - - // These values are not meaningful, we just need distinct values. - let pk: Vec = vec![0]; - let vk: Vec = vec![1, 2]; - save_key_to_dir(&pk, circuit_name, &circuit_build_path, true).unwrap(); - save_key_to_dir(&vk, circuit_name, &circuit_build_path, false).unwrap(); - - save_acir_checksum_to_dir(checksum_acir(&circuit), circuit_name, &circuit_build_path); - circuit_build_path.push(circuit_name); - - let loaded_keys = fetch_pk_and_vk(&circuit, circuit_build_path, true, true).unwrap(); - assert_eq!(loaded_keys, (pk, vk)); - } -} diff --git a/crates/nargo_cli/src/cli/fs/mod.rs b/crates/nargo_cli/src/cli/fs/mod.rs index 0e7b643f2c7..d860f722fd1 100644 --- a/crates/nargo_cli/src/cli/fs/mod.rs +++ b/crates/nargo_cli/src/cli/fs/mod.rs @@ -7,7 +7,6 @@ use std::{ use crate::errors::CliError; pub(super) mod inputs; -pub(super) mod keys; pub(super) mod program; pub(super) mod proof; pub(super) mod witness; diff --git a/crates/nargo_cli/src/cli/fs/program.rs b/crates/nargo_cli/src/cli/fs/program.rs index b01455e2833..a3b5f4026bd 100644 --- a/crates/nargo_cli/src/cli/fs/program.rs +++ b/crates/nargo_cli/src/cli/fs/program.rs @@ -1,20 +1,20 @@ use std::path::{Path, PathBuf}; -use noirc_driver::{CompiledContract, CompiledProgram}; +use nargo::artifacts::{contract::PreprocessedContract, program::PreprocessedProgram}; -use crate::{constants::ACIR_CHECKSUM, errors::CliError}; +use crate::errors::CliError; use super::{create_named_dir, write_to_file}; pub(crate) fn save_program_to_file>( - compiled_program: &CompiledProgram, + compiled_program: &PreprocessedProgram, circuit_name: &str, circuit_dir: P, ) -> PathBuf { save_build_artifact_to_file(compiled_program, circuit_name, circuit_dir) } pub(crate) fn save_contract_to_file>( - compiled_contract: &CompiledContract, + compiled_contract: &PreprocessedContract, circuit_name: &str, circuit_dir: P, ) -> PathBuf { @@ -33,20 +33,9 @@ fn save_build_artifact_to_file, T: ?Sized + serde::Serialize>( circuit_path } -pub(crate) fn save_acir_checksum_to_dir>( - acir_checksum: [u8; 4], - hash_name: &str, - hash_dir: P, -) -> PathBuf { - let hash_path = hash_dir.as_ref().join(hash_name).with_extension(ACIR_CHECKSUM); - write_to_file(hex::encode(acir_checksum).as_bytes(), &hash_path); - - hash_path -} - pub(crate) fn read_program_from_file>( circuit_path: P, -) -> Result { +) -> Result { let file_path = circuit_path.as_ref().with_extension("json"); let input_string = std::fs::read(&file_path).map_err(|_| CliError::PathNotValid(file_path))?; diff --git a/crates/nargo_cli/src/cli/gates_cmd.rs b/crates/nargo_cli/src/cli/gates_cmd.rs index 71edd4101fe..a5093b4d775 100644 --- a/crates/nargo_cli/src/cli/gates_cmd.rs +++ b/crates/nargo_cli/src/cli/gates_cmd.rs @@ -23,10 +23,11 @@ fn count_gates_with_path>( program_dir: P, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let compiled_program = compile_circuit(program_dir.as_ref(), compile_options)?; - let num_opcodes = compiled_program.circuit.opcodes.len(); let backend = crate::backends::ConcreteBackend; + let compiled_program = compile_circuit(&backend, program_dir.as_ref(), compile_options)?; + let num_opcodes = compiled_program.circuit.opcodes.len(); + println!( "Total ACIR opcodes generated for language {:?}: {}", backend.np_language(), diff --git a/crates/nargo_cli/src/cli/mod.rs b/crates/nargo_cli/src/cli/mod.rs index 9e21bf472cd..e713bdd47fc 100644 --- a/crates/nargo_cli/src/cli/mod.rs +++ b/crates/nargo_cli/src/cli/mod.rs @@ -16,7 +16,6 @@ mod compile_cmd; mod execute_cmd; mod gates_cmd; mod new_cmd; -mod preprocess_cmd; mod print_acir_cmd; mod prove_cmd; mod test_cmd; @@ -56,7 +55,6 @@ enum NargoCommand { Execute(execute_cmd::ExecuteCommand), Prove(prove_cmd::ProveCommand), Verify(verify_cmd::VerifyCommand), - Preprocess(preprocess_cmd::PreprocessCommand), Test(test_cmd::TestCommand), Gates(gates_cmd::GatesCommand), PrintAcir(print_acir_cmd::PrintAcirCommand), @@ -77,7 +75,6 @@ pub fn start_cli() -> eyre::Result<()> { NargoCommand::Execute(args) => execute_cmd::run(args, config), NargoCommand::Prove(args) => prove_cmd::run(args, config), NargoCommand::Verify(args) => verify_cmd::run(args, config), - NargoCommand::Preprocess(args) => preprocess_cmd::run(args, config), NargoCommand::Test(args) => test_cmd::run(args, config), NargoCommand::Gates(args) => gates_cmd::run(args, config), NargoCommand::CodegenVerifier(args) => codegen_verifier_cmd::run(args, config), diff --git a/crates/nargo_cli/src/cli/preprocess_cmd.rs b/crates/nargo_cli/src/cli/preprocess_cmd.rs deleted file mode 100644 index 95a2ab849e9..00000000000 --- a/crates/nargo_cli/src/cli/preprocess_cmd.rs +++ /dev/null @@ -1,46 +0,0 @@ -use nargo::ops::{preprocess_circuit, PreprocessedData}; -use std::path::{Path, PathBuf}; - -use clap::Args; - -use crate::{constants::TARGET_DIR, errors::CliError}; - -use super::fs::{ - keys::save_key_to_dir, - program::{read_program_from_file, save_acir_checksum_to_dir}, -}; -use super::NargoConfig; - -/// Generate proving and verification keys for a circuit. -#[derive(Debug, Clone, Args)] -pub(crate) struct PreprocessCommand { - /// The name of the program build artifact. - artifact_name: String, -} - -pub(crate) fn run(args: PreprocessCommand, config: NargoConfig) -> Result<(), CliError> { - let circuit_dir = config.program_dir.join(TARGET_DIR); - - let program = read_program_from_file(circuit_dir.join(&args.artifact_name))?; - - let backend = crate::backends::ConcreteBackend; - let preprocess_data = preprocess_circuit(&backend, &program.circuit)?; - save_preprocess_data(&preprocess_data, &args.artifact_name, circuit_dir)?; - - Ok(()) -} - -pub(crate) fn save_preprocess_data>( - data: &PreprocessedData, - key_name: &str, - preprocess_dir: P, -) -> Result<(PathBuf, PathBuf), CliError> { - // Save a checksum of the circuit to compare against during proving and verification. - // If hash doesn't match then the circuit has been updated and keys are stale. - save_acir_checksum_to_dir(data.program_checksum, key_name, &preprocess_dir); - - let pk_path = save_key_to_dir(&data.proving_key, key_name, &preprocess_dir, true)?; - let vk_path = save_key_to_dir(&data.verification_key, key_name, preprocess_dir, false)?; - - Ok((pk_path, vk_path)) -} diff --git a/crates/nargo_cli/src/cli/print_acir_cmd.rs b/crates/nargo_cli/src/cli/print_acir_cmd.rs index dbc0fea86de..589cc490f40 100644 --- a/crates/nargo_cli/src/cli/print_acir_cmd.rs +++ b/crates/nargo_cli/src/cli/print_acir_cmd.rs @@ -22,7 +22,9 @@ fn print_acir_with_path>( program_dir: P, compile_options: &CompileOptions, ) -> Result<(), CliError> { - let compiled_program = compile_circuit(program_dir.as_ref(), compile_options)?; + let backend = crate::backends::ConcreteBackend; + + let compiled_program = compile_circuit(&backend, program_dir.as_ref(), compile_options)?; println!("{}", compiled_program.circuit); Ok(()) diff --git a/crates/nargo_cli/src/cli/prove_cmd.rs b/crates/nargo_cli/src/cli/prove_cmd.rs index 720110a6758..fd60f004e2b 100644 --- a/crates/nargo_cli/src/cli/prove_cmd.rs +++ b/crates/nargo_cli/src/cli/prove_cmd.rs @@ -1,17 +1,20 @@ use std::path::{Path, PathBuf}; use clap::Args; -use nargo::ops::{preprocess_circuit, PreprocessedData}; +use nargo::artifacts::program::PreprocessedProgram; +use nargo::ops::{preprocess_program, prove_execution}; use noirc_abi::input_parser::Format; -use noirc_driver::CompileOptions; +use noirc_driver::{CompileOptions, CompiledProgram}; -use super::fs::{ - inputs::{read_inputs_from_file, write_inputs_to_file}, - keys::fetch_pk_and_vk, - program::read_program_from_file, - proof::save_proof_to_dir, -}; use super::NargoConfig; +use super::{ + compile_cmd::compile_circuit, + fs::{ + inputs::{read_inputs_from_file, write_inputs_to_file}, + program::read_program_from_file, + proof::save_proof_to_dir, + }, +}; use crate::{ cli::{execute_cmd::execute_program, verify_cmd::verify_proof}, constants::{PROOFS_DIR, PROVER_INPUT_FILE, TARGET_DIR, VERIFIER_INPUT_FILE}, @@ -62,25 +65,21 @@ pub(crate) fn prove_with_path>( check_proof: bool, compile_options: &CompileOptions, ) -> Result, CliError> { - let (compiled_program, proving_key, verification_key) = match circuit_build_path { - Some(circuit_build_path) => { - let compiled_program = read_program_from_file(&circuit_build_path)?; + let backend = crate::backends::ConcreteBackend; - let (proving_key, verification_key) = - fetch_pk_and_vk(&compiled_program.circuit, circuit_build_path, true, true)?; - (compiled_program, proving_key, verification_key) - } + let preprocessed_program = match circuit_build_path { + Some(circuit_build_path) => read_program_from_file(circuit_build_path)?, None => { let compiled_program = - super::compile_cmd::compile_circuit(program_dir.as_ref(), compile_options)?; - - let backend = crate::backends::ConcreteBackend; - let PreprocessedData { proving_key, verification_key, .. } = - preprocess_circuit(&backend, &compiled_program.circuit)?; - (compiled_program, proving_key, verification_key) + compile_circuit(&backend, program_dir.as_ref(), compile_options)?; + preprocess_program(&backend, compiled_program)? } }; + let PreprocessedProgram { abi, bytecode, proving_key, verification_key, .. } = + preprocessed_program; + let compiled_program = CompiledProgram { abi, circuit: bytecode }; + // Parse the initial witness values from Prover.toml let (inputs_map, _) = read_inputs_from_file( &program_dir, @@ -89,7 +88,7 @@ pub(crate) fn prove_with_path>( &compiled_program.abi, )?; - let solved_witness = execute_program(&compiled_program, &inputs_map)?; + let solved_witness = execute_program(&backend, &compiled_program, &inputs_map)?; // Write public inputs into Verifier.toml let public_abi = compiled_program.abi.clone().public_abi(); @@ -103,13 +102,12 @@ pub(crate) fn prove_with_path>( Format::Toml, )?; - let backend = crate::backends::ConcreteBackend; - let proof = - nargo::ops::prove(&backend, &compiled_program.circuit, solved_witness, &proving_key)?; + let proof = prove_execution(&backend, &compiled_program.circuit, solved_witness, &proving_key)?; if check_proof { let no_proof_name = "".into(); verify_proof( + &backend, &compiled_program, public_inputs, return_value, diff --git a/crates/nargo_cli/src/cli/verify_cmd.rs b/crates/nargo_cli/src/cli/verify_cmd.rs index 03de9a7e33e..cf2e4859091 100644 --- a/crates/nargo_cli/src/cli/verify_cmd.rs +++ b/crates/nargo_cli/src/cli/verify_cmd.rs @@ -1,14 +1,14 @@ -use super::fs::{ - inputs::read_inputs_from_file, keys::fetch_pk_and_vk, load_hex_data, - program::read_program_from_file, -}; -use super::{compile_cmd::compile_circuit, InputMap, NargoConfig}; +use super::compile_cmd::compile_circuit; +use super::fs::{inputs::read_inputs_from_file, load_hex_data, program::read_program_from_file}; +use super::{InputMap, NargoConfig}; use crate::{ constants::{PROOFS_DIR, PROOF_EXT, TARGET_DIR, VERIFIER_INPUT_FILE}, errors::CliError, }; +use acvm::ProofSystemCompiler; use clap::Args; -use nargo::ops::{preprocess_circuit, PreprocessedData}; +use nargo::artifacts::program::PreprocessedProgram; +use nargo::ops::preprocess_program; use noirc_abi::input_parser::{Format, InputValue}; use noirc_driver::{CompileOptions, CompiledProgram}; use std::path::{Path, PathBuf}; @@ -43,30 +43,27 @@ fn verify_with_path>( circuit_build_path: Option

, compile_options: CompileOptions, ) -> Result<(), CliError> { - let (compiled_program, verification_key) = match circuit_build_path { - Some(circuit_build_path) => { - let compiled_program = read_program_from_file(&circuit_build_path)?; + let backend = crate::backends::ConcreteBackend; - let (_, verification_key) = - fetch_pk_and_vk(&compiled_program.circuit, circuit_build_path, false, true)?; - (compiled_program, verification_key) - } + let preprocessed_program = match circuit_build_path { + Some(circuit_build_path) => read_program_from_file(circuit_build_path)?, None => { - let compiled_program = compile_circuit(program_dir.as_ref(), &compile_options)?; - - let backend = crate::backends::ConcreteBackend; - let PreprocessedData { verification_key, .. } = - preprocess_circuit(&backend, &compiled_program.circuit)?; - (compiled_program, verification_key) + let compiled_program = + compile_circuit(&backend, program_dir.as_ref(), &compile_options)?; + preprocess_program(&backend, compiled_program)? } }; + let PreprocessedProgram { abi, bytecode, verification_key, .. } = preprocessed_program; + let compiled_program = CompiledProgram { abi, circuit: bytecode }; + // Load public inputs (if any) from `VERIFIER_INPUT_FILE`. let public_abi = compiled_program.abi.clone().public_abi(); let (public_inputs_map, return_value) = read_inputs_from_file(program_dir, VERIFIER_INPUT_FILE, Format::Toml, &public_abi)?; verify_proof( + &backend, &compiled_program, public_inputs_map, return_value, @@ -77,6 +74,7 @@ fn verify_with_path>( } pub(crate) fn verify_proof( + backend: &impl ProofSystemCompiler, compiled_program: &CompiledProgram, public_inputs_map: InputMap, return_value: Option, @@ -87,9 +85,8 @@ pub(crate) fn verify_proof( let public_abi = compiled_program.abi.clone().public_abi(); let public_inputs = public_abi.encode(&public_inputs_map, return_value)?; - let backend = crate::backends::ConcreteBackend; let valid_proof = nargo::ops::verify_proof( - &backend, + backend, &compiled_program.circuit, proof, public_inputs, diff --git a/crates/nargo_cli/src/constants.rs b/crates/nargo_cli/src/constants.rs index ba7ba3e7675..d3e6b7f28e1 100644 --- a/crates/nargo_cli/src/constants.rs +++ b/crates/nargo_cli/src/constants.rs @@ -21,9 +21,3 @@ pub(crate) const PKG_FILE: &str = "Nargo.toml"; pub(crate) const PROOF_EXT: &str = "proof"; /// 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"; -/// The extension for ACIR hash files. -pub(crate) const ACIR_CHECKSUM: &str = "json.checksum"; diff --git a/crates/nargo_cli/src/errors.rs b/crates/nargo_cli/src/errors.rs index 1561a5033fa..f6537b550ea 100644 --- a/crates/nargo_cli/src/errors.rs +++ b/crates/nargo_cli/src/errors.rs @@ -20,8 +20,7 @@ pub(crate) enum CliError { " Error: cannot find {0}.toml file.\n Expected location: {1:?} \n Please generate this file at the expected location." )] MissingTomlFile(String, 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), + #[error("Failed to verify proof {}", .0.display())] InvalidProof(PathBuf), diff --git a/crates/nargo_cli/src/preprocess.rs b/crates/nargo_cli/src/preprocess.rs new file mode 100644 index 00000000000..249b6647e30 --- /dev/null +++ b/crates/nargo_cli/src/preprocess.rs @@ -0,0 +1,57 @@ +use acvm::ProofSystemCompiler; +use iter_extended::vecmap; +use noirc_driver::{CompiledContract, CompiledProgram}; + +// TODO: migrate to `nargo_cli` + +use crate::artifacts::{ + contract::{PreprocessedContract, PreprocessedContractFunction}, + program::PreprocessedProgram, +}; + +// TODO: pull this from backend. +const BACKEND_IDENTIFIER: &str = "acvm-backend-barretenberg"; + +pub(crate) fn preprocess_program(compiled_program: CompiledProgram) -> PreprocessedProgram { + let backend = crate::backends::ConcreteBackend; + + // TODO: currently `compiled_program`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = compiled_program.circuit; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedProgram { + backend: String::from(BACKEND_IDENTIFIER), + abi: compiled_program.abi, + bytecode: optimized_bytecode, + proving_key, + verification_key, + } +} + +pub(crate) fn preprocess_contract(compiled_contract: CompiledContract) -> PreprocessedContract { + let backend = crate::backends::ConcreteBackend; + + let preprocessed_contract_functions = vecmap(compiled_contract.functions, |func| { + // TODO: currently `func`'s bytecode is already optimized for the backend. + // In future we'll need to apply those optimizations here. + let optimized_bytecode = func.bytecode; + let (proving_key, verification_key) = backend.preprocess(&optimized_bytecode); + + PreprocessedContractFunction { + name: func.name, + function_type: func.function_type, + abi: func.abi, + + bytecode: optimized_bytecode, + proving_key, + verification_key, + } + }); + + PreprocessedContract { + name: compiled_contract.name, + backend: String::from(BACKEND_IDENTIFIER), + functions: preprocessed_contract_functions, + } +} diff --git a/crates/noirc_driver/src/contract.rs b/crates/noirc_driver/src/contract.rs index ed9bd8d4dcd..a5600c3d215 100644 --- a/crates/noirc_driver/src/contract.rs +++ b/crates/noirc_driver/src/contract.rs @@ -6,8 +6,7 @@ use serde::{Deserialize, Serialize}; /// Unlike the similar enum in noirc_frontend, 'open' and 'unconstrained' /// are mutually exclusive here. In the case a function is both, 'unconstrained' /// takes precedence. -#[derive(serde::Serialize, serde::Deserialize, Debug, Copy, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum ContractFunctionType { /// This function will be executed in a private /// context. @@ -20,8 +19,6 @@ pub enum ContractFunctionType { Unconstrained, } -#[derive(serde::Serialize, serde::Deserialize)] -#[serde(rename_all = "camelCase")] pub struct CompiledContract { /// The name of the contract. pub name: String, @@ -36,27 +33,19 @@ pub struct CompiledContract { /// A contract function unlike a regular Noir program /// however can have additional properties. /// One of these being a function type. -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct ContractFunction { pub name: String, pub function_type: ContractFunctionType, - #[serde(flatten)] pub abi: Abi, - #[serde( - serialize_with = "crate::program::serialize_circuit", - deserialize_with = "crate::program::deserialize_circuit" - )] pub bytecode: Circuit, - - pub verification_key: Option>, } impl ContractFunctionType { - pub fn new(kind: noirc_frontend::ContractFunctionType, is_unconstrained: bool) -> Self { + pub(super) fn new(kind: noirc_frontend::ContractFunctionType, is_unconstrained: bool) -> Self { match (kind, is_unconstrained) { (_, true) => Self::Unconstrained, (noirc_frontend::ContractFunctionType::Secret, false) => Self::Secret, diff --git a/crates/noirc_driver/src/lib.rs b/crates/noirc_driver/src/lib.rs index 4f1ab7fd482..c6d0a08e4d8 100644 --- a/crates/noirc_driver/src/lib.rs +++ b/crates/noirc_driver/src/lib.rs @@ -5,9 +5,9 @@ use acvm::Language; use clap::Args; -use contract::{ContractFunction, ContractFunctionType}; +use contract::ContractFunction; use fm::FileType; -use iter_extended::{try_vecmap, vecmap}; +use iter_extended::try_vecmap; use noirc_abi::FunctionSignature; use noirc_errors::{reporter, ReportedError}; use noirc_evaluator::create_circuit; @@ -22,7 +22,7 @@ use std::path::{Path, PathBuf}; mod contract; mod program; -pub use contract::CompiledContract; +pub use contract::{CompiledContract, ContractFunctionType}; pub use program::CompiledProgram; pub struct Driver { @@ -204,30 +204,24 @@ impl Driver { options: &CompileOptions, ) -> Result { let functions = try_vecmap(&contract.functions, |function_id| { - let function_name = self.function_name(*function_id).to_owned(); + let name = self.function_name(*function_id).to_owned(); let function = self.compile_no_check(options, *function_id)?; let func_meta = self.context.def_interner.function_meta(function_id); let func_type = func_meta .contract_function_type .expect("Expected contract function to have a contract visibility"); - let func_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); + let function_type = ContractFunctionType::new(func_type, func_meta.is_unconstrained); - Ok((function_name, func_type, function)) - })?; - - let converted_functions = - vecmap(functions, |(name, function_type, function)| ContractFunction { + Ok(ContractFunction { name, function_type, abi: function.abi, bytecode: function.circuit, - // Since we have not called the proving system yet - // we do not have a verification key - verification_key: None, - }); + }) + })?; - Ok(CompiledContract { name: contract.name, functions: converted_functions }) + Ok(CompiledContract { name: contract.name, functions }) } /// Returns the FuncId of the 'main' function.