Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/nargo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions crates/nargo/src/artifacts/contract.rs
Original file line number Diff line number Diff line change
@@ -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<PreprocessedContractFunction>,
}

/// 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<u8>,
pub verification_key: Vec<u8>,
}
31 changes: 31 additions & 0 deletions crates/nargo/src/artifacts/mod.rs
Original file line number Diff line number Diff line change
@@ -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<S>(circuit: &Circuit, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut circuit_bytes: Vec<u8> = Vec::new();
circuit.write(&mut circuit_bytes).unwrap();

circuit_bytes.serialize(s)
}

fn deserialize_circuit<'de, D>(deserializer: D) -> Result<Circuit, D::Error>
where
D: Deserializer<'de>,
{
let circuit_bytes = Vec::<u8>::deserialize(deserializer)?;
let circuit = Circuit::read(&*circuit_bytes).unwrap();
Ok(circuit)
}
23 changes: 23 additions & 0 deletions crates/nargo/src/artifacts/program.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
pub verification_key: Vec<u8>,
}
1 change: 1 addition & 0 deletions crates/nargo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions crates/nargo/src/ops/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
75 changes: 52 additions & 23 deletions crates/nargo/src/ops/preprocess.rs
Original file line number Diff line number Diff line change
@@ -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<PreprocessedProgram, NargoError> {
// 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<u8>,
pub verification_key: Vec<u8>,
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<PreprocessedData, NargoError> {
let (proving_key, verification_key) = backend.preprocess(circuit);
let program_checksum = checksum_acir(circuit);
compiled_contract: CompiledContract,
) -> Result<PreprocessedContract, NargoError> {
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,
})
}
2 changes: 1 addition & 1 deletion crates/nargo/src/ops/prove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 6 additions & 6 deletions crates/nargo_cli/src/cli/codegen_verifier_cmd.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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");
Expand Down
105 changes: 23 additions & 82 deletions crates/nargo_cli/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<Driver, DependencyResolutionError> {
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<Driver, DependencyResolutionError> {
Resolver::resolve_root_manifest(program_dir, backend.np_language())
}

pub(crate) fn compile_circuit(
backend: &impl ProofSystemCompiler,
program_dir: &Path,
compile_options: &CompileOptions,
) -> Result<CompiledProgram, CliError> {
let mut driver = setup_driver(program_dir)?;
let mut driver = setup_driver(backend, program_dir)?;
driver.compile_main(compile_options).map_err(|_| CliError::CompilationError)
}
Loading