Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nargo)!: define preprocessed artifacts for programs/contracts #1126

Merged
merged 5 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
41 changes: 41 additions & 0 deletions crates/nargo_cli/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(crate) struct PreprocessedContract {
/// The name of the contract.
pub(crate) name: String,
/// The identifier of the proving backend which this contract has been compiled for.
pub(crate) backend: String,
/// Each of the contract's functions are compiled into a separate program stored in this `Vec`.
pub(crate) 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(crate) struct PreprocessedContractFunction {
pub(crate) name: String,

pub(crate) function_type: ContractFunctionType,

pub(crate) abi: Abi,

#[serde(
serialize_with = "super::serialize_circuit",
deserialize_with = "super::deserialize_circuit"
)]
pub(crate) bytecode: Circuit,

pub(crate) proving_key: Vec<u8>,
pub(crate) verification_key: Vec<u8>,
}
31 changes: 31 additions & 0 deletions crates/nargo_cli/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(crate) mod contract;
pub(crate) 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_cli/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(crate) struct PreprocessedProgram {
pub(crate) backend: String,
pub(crate) abi: Abi,

#[serde(
serialize_with = "super::serialize_circuit",
deserialize_with = "super::deserialize_circuit"
)]
pub(crate) bytecode: Circuit,

pub(crate) proving_key: Vec<u8>,
pub(crate) verification_key: Vec<u8>,
}
85 changes: 11 additions & 74 deletions crates/nargo_cli/src/cli/compile_cmd.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use acvm::ProofSystemCompiler;
use noirc_driver::{CompileOptions, CompiledContract, CompiledProgram, Driver};
use iter_extended::vecmap;
use noirc_driver::{CompileOptions, CompiledProgram, Driver};
use std::path::Path;

use clap::Args;

use crate::preprocess::{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, PreprocessedData};
use super::NargoConfig;

/// Compile the program and its secret execution trace into ACIR format
Expand All @@ -31,90 +32,26 @@ pub(crate) fn run(args: CompileCommand, config: NargoConfig) -> Result<(), CliEr
// 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 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 = vecmap(compiled_contracts, preprocess_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 preprocessed_program = preprocess_program(program);
save_program_to_file(&preprocessed_program, &args.circuit_name, circuit_dir);
}
Ok(())
}

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 preprocessed_data = PreprocessedData::from(&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 mut contract_preprocess_data = Vec::new();
for contract_function in &mut compiled_contract.functions {
let preprocessed_data = PreprocessedData::from(&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(())
}

pub(crate) fn compile_circuit(
program_dir: &Path,
compile_options: &CompileOptions,
Expand Down
89 changes: 0 additions & 89 deletions crates/nargo_cli/src/cli/fs/keys.rs

This file was deleted.

1 change: 0 additions & 1 deletion crates/nargo_cli/src/cli/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 7 additions & 21 deletions crates/nargo_cli/src/cli/fs/program.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use std::path::{Path, PathBuf};

use acvm::{acir::circuit::Circuit, checksum_constraint_system};
use noirc_driver::{CompiledContract, CompiledProgram};

use crate::{constants::ACIR_CHECKSUM, errors::CliError};
use crate::{
artifacts::{contract::PreprocessedContract, program::PreprocessedProgram},
errors::CliError,
};

use super::{create_named_dir, write_to_file};

pub(crate) fn save_program_to_file<P: AsRef<Path>>(
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<P: AsRef<Path>>(
compiled_contract: &CompiledContract,
compiled_contract: &PreprocessedContract,
circuit_name: &str,
circuit_dir: P,
) -> PathBuf {
Expand All @@ -34,23 +34,9 @@ fn save_build_artifact_to_file<P: AsRef<Path>, T: ?Sized + serde::Serialize>(
circuit_path
}

pub(crate) fn checksum_acir(circuit: &Circuit) -> [u8; 4] {
checksum_constraint_system(circuit).to_be_bytes()
}
pub(crate) fn save_acir_checksum_to_dir<P: AsRef<Path>>(
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<P: AsRef<Path>>(
circuit_path: P,
) -> Result<CompiledProgram, CliError> {
) -> Result<PreprocessedProgram, CliError> {
let file_path = circuit_path.as_ref().with_extension("json");

let input_string = std::fs::read(&file_path).map_err(|_| CliError::PathNotValid(file_path))?;
Expand Down
3 changes: 0 additions & 3 deletions crates/nargo_cli/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
Loading