diff --git a/.github/workflows/publish-nargo.yml b/.github/workflows/publish-nargo.yml index e18dac52ca4..a0c9247f0a4 100644 --- a/.github/workflows/publish-nargo.yml +++ b/.github/workflows/publish-nargo.yml @@ -54,6 +54,8 @@ jobs: cargo build --package nargo_cli --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_profiler --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" cargo build --package noir_inspector --release --target ${{ matrix.target }} --no-default-features --features "${{ inputs.features }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | @@ -237,5 +239,3 @@ jobs: make_latest: false overwrite: true tag: ${{ format('{0}-{1}', 'nightly', steps.date.outputs.date) }} - - diff --git a/.github/workflows/reports.yml b/.github/workflows/reports.yml index d458f8998f0..e5d6c0d7b13 100644 --- a/.github/workflows/reports.yml +++ b/.github/workflows/reports.yml @@ -42,6 +42,8 @@ jobs: - name: Build Nargo run: cargo build --package nargo_cli --release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | diff --git a/.github/workflows/test-js-packages.yml b/.github/workflows/test-js-packages.yml index f88e64fa1a5..4fd7b5d352a 100644 --- a/.github/workflows/test-js-packages.yml +++ b/.github/workflows/test-js-packages.yml @@ -64,6 +64,8 @@ jobs: - name: Build Nargo run: cargo build --package nargo_cli --release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Package artifacts run: | diff --git a/Cargo.lock b/Cargo.lock index 995f43567fc..3aab222d493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,14 +6,23 @@ version = 4 name = "acir" version = "1.0.0-beta.3" dependencies = [ + "acir", "acir_field", "base64 0.21.7", "bincode", "brillig", + "color-eyre", "criterion", "flate2", "fxhash", + "noir_protobuf", + "num-bigint", "pprof", + "proptest", + "proptest-derive", + "prost", + "prost-build", + "protoc-prebuilt", "serde", "serde-big-array", "serde-generate", @@ -789,6 +798,8 @@ name = "brillig" version = "1.0.0-beta.3" dependencies = [ "acir_field", + "proptest", + "proptest-derive", "serde", ] @@ -3000,6 +3011,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" + [[package]] name = "nargo" version = "1.0.0-beta.3" @@ -3300,6 +3317,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "noir_protobuf" +version = "1.0.0-beta.3" +dependencies = [ + "color-eyre", + "prost", +] + [[package]] name = "noir_wasm" version = "1.0.0-beta.3" @@ -3961,6 +3986,16 @@ dependencies = [ "termtree", ] +[[package]] +name = "prettyplease" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" +dependencies = [ + "proc-macro2", + "syn 2.0.98", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -4030,6 +4065,68 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.98", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "protoc-prebuilt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d85d4641fe3b8c6e853dfd09fe35379bc6b6e66bd692ac29ed4f7087de69ed5" +dependencies = [ + "ureq", + "zip", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -5548,6 +5645,21 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.4" @@ -6130,6 +6242,18 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] + [[package]] name = "zkhash" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 9c5bf1351d1..308609aa511 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "acvm-repo/bn254_blackbox_solver", # Utility crates "utils/iter-extended", + "utils/protobuf", ] default-members = [ "tooling/nargo_cli", @@ -94,6 +95,7 @@ noirc_abi = { path = "tooling/noirc_abi" } noirc_artifacts = { path = "tooling/noirc_artifacts" } noirc_artifacts_info = { path = "tooling/noirc_artifacts_info" } noir_artifact_cli = { path = "tooling/artifact_cli" } +noir_protobuf = { path = "utils/protobuf" } # Arkworks ark-bn254 = { version = "^0.5.0", default-features = false, features = [ @@ -137,6 +139,11 @@ criterion = "0.5.0" # https://github.com/tikv/pprof-rs/pull/172 pprof = { version = "0.14", features = ["flamegraph", "criterion"] } +# Protobuf +prost = "0.13" +prost-build = "0.13" +protoc-prebuilt = "0.3" + cfg-if = "1.0.0" dirs = "4" serde = { version = "1.0.136", features = ["derive"] } diff --git a/acvm-repo/acir/Cargo.toml b/acvm-repo/acir/Cargo.toml index 2b15c2abf09..5d7b347c511 100644 --- a/acvm-repo/acir/Cargo.toml +++ b/acvm-repo/acir/Cargo.toml @@ -18,15 +18,26 @@ workspace = true [dependencies] acir_field.workspace = true brillig.workspace = true +noir_protobuf.workspace = true + +color-eyre.workspace = true serde.workspace = true thiserror.workspace = true flate2.workspace = true bincode.workspace = true base64.workspace = true +prost.workspace = true serde-big-array = "0.5.1" strum = { workspace = true } strum_macros = { workspace = true } +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } + +[build-dependencies] +prost-build.workspace = true +protoc-prebuilt.workspace = true + [dev-dependencies] serde_json = "1.0" serde-reflection = "0.3.6" @@ -34,10 +45,15 @@ serde-generate = "0.25.1" fxhash.workspace = true criterion.workspace = true pprof.workspace = true +num-bigint.workspace = true + +acir = { path = ".", features = ["arb"] } # Self to turn on `arb`. [features] +default = [] bn254 = ["acir_field/bn254"] bls12_381 = ["acir_field/bls12_381"] +arb = ["proptest", "proptest-derive", "brillig/arb"] [[bench]] name = "serialization" diff --git a/acvm-repo/acir/build.rs b/acvm-repo/acir/build.rs new file mode 100644 index 00000000000..e52a06fbce4 --- /dev/null +++ b/acvm-repo/acir/build.rs @@ -0,0 +1,24 @@ +use std::path::Path; + +fn main() { + let (protoc_bin, include_dir) = + protoc_prebuilt::init("29.3").expect("failed to initialize protoc"); + + unsafe { + std::env::set_var("PROTOC", protoc_bin); + } + + prost_build::compile_protos( + &[ + // DTOs for a `Program`, which work with the types in `acir.cpp` + "./src/proto/program.proto", + // DTOs for the `WitnessStack`, which work with the types in `witness.cpp` + "./src/proto/acir/witness.proto", + // A pared down DTO for `Program`, so Barretenberg can ignore the Brillig part. + // This is only included to make sure it compiles. + "./src/proto/acir/program.proto", + ], + &[Path::new("./src/proto"), include_dir.as_path()], + ) + .expect("failed to compile .proto schemas"); +} diff --git a/acvm-repo/acir/src/circuit/brillig.rs b/acvm-repo/acir/src/circuit/brillig.rs index ef75d088f8c..972a08dd32a 100644 --- a/acvm-repo/acir/src/circuit/brillig.rs +++ b/acvm-repo/acir/src/circuit/brillig.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; /// Inputs for the Brillig VM. These are the initial inputs /// that the Brillig VM will use to start. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligInputs { Single(Expression), Array(Vec>), @@ -15,6 +16,7 @@ pub enum BrilligInputs { /// Outputs for the Brillig VM. Once the VM has completed /// execution, this will be the object that is returned. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligOutputs { Simple(Witness), Array(Vec), @@ -24,6 +26,7 @@ pub enum BrilligOutputs { /// a full Brillig function to be executed by the Brillig VM. /// This is stored separately on a program and accessed through a [BrilligPointer]. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct BrilligBytecode { pub bytecode: Vec>, } @@ -32,6 +35,7 @@ pub struct BrilligBytecode { #[derive( Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default, PartialOrd, Ord, )] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] #[serde(transparent)] pub struct BrilligFunctionId(pub u32); diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index 68c3c832b5c..2f7bb92b184 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -2,8 +2,12 @@ pub mod black_box_functions; pub mod brillig; pub mod opcodes; -use crate::native_types::{Expression, Witness}; +use crate::{ + native_types::{Expression, Witness}, + proto::convert::ProtoSchema, +}; use acir_field::AcirField; +use noir_protobuf::ProtoCodec as _; pub use opcodes::Opcode; use thiserror::Error; @@ -26,6 +30,7 @@ use self::{brillig::BrilligBytecode, opcodes::BlockId}; /// into a proving system which supports PLONK, where arithmetic expressions have a /// finite fan-in. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ExpressionWidth { #[default] Unbounded, @@ -37,13 +42,15 @@ pub enum ExpressionWidth { /// A program represented by multiple ACIR circuits. The execution trace of these /// circuits is dictated by construction of the [crate::native_types::WitnessStack]. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] -pub struct Program { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub struct Program { pub functions: Vec>, pub unconstrained_functions: Vec>, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] -pub struct Circuit { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub struct Circuit { // current_witness_index is the highest witness index in the circuit. The next witness to be added to this circuit // will take on this value. (The value is cached here as an optimization.) pub current_witness_index: u32, @@ -70,12 +77,14 @@ pub struct Circuit { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ExpressionOrMemory { Expression(Expression), Memory(BlockId), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct AssertionPayload { pub error_selector: u64, pub payload: Vec>, @@ -137,6 +146,7 @@ pub struct ResolvedOpcodeLocation { } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] /// Opcodes are locatable so that callers can /// map opcodes to debug information related to their context. pub enum OpcodeLocation { @@ -218,7 +228,7 @@ impl std::fmt::Display for BrilligOpcodeLocation { } } -impl Circuit { +impl Circuit { pub fn num_vars(&self) -> u32 { self.current_witness_index + 1 } @@ -237,9 +247,9 @@ impl Circuit { } } -impl Program { +impl Program { fn write(&self, writer: W) -> std::io::Result<()> { - let buf = bincode::serialize(self).unwrap(); + let buf = self.bincode_serialize()?; let mut encoder = flate2::write::GzEncoder::new(writer, Compression::default()); encoder.write_all(&buf)?; encoder.finish()?; @@ -263,13 +273,39 @@ impl Program { } } -impl Deserialize<'a>> Program { +impl Program { + /// Serialize the program using `bincode`, which is what we have to use until Barretenberg can read another format. + pub(crate) fn bincode_serialize(&self) -> std::io::Result> { + bincode::serialize(self).map_err(std::io::Error::other) + } +} + +impl Deserialize<'a>> Program { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> std::io::Result { + bincode::deserialize(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +#[allow(dead_code)] // TODO: Remove once we switch to protobuf +impl Program { + /// Serialize the program using `protobuf`, which is what we try to replace `bincode` with. + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + pub(crate) fn proto_deserialize(buf: &[u8]) -> std::io::Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +impl Deserialize<'a>> Program { fn read(reader: R) -> std::io::Result { let mut gz_decoder = flate2::read::GzDecoder::new(reader); - let mut buf_d = Vec::new(); - gz_decoder.read_to_end(&mut buf_d)?; - bincode::deserialize(&buf_d) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err)) + let mut buf = Vec::new(); + gz_decoder.read_to_end(&mut buf)?; + let program = Self::bincode_deserialize(&buf)?; + Ok(program) } /// Deserialize bytecode. @@ -357,6 +393,7 @@ impl std::fmt::Debug for Program { } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct PublicInputs(pub BTreeSet); impl PublicInputs { @@ -477,4 +514,153 @@ mod tests { Program::deserialize_program(&zipped_bad_circuit); assert!(deserialization_result.is_err()); } + + /// Property based testing for serialization + mod props { + use acir_field::FieldElement; + use proptest::prelude::*; + use proptest::test_runner::{TestCaseResult, TestRunner}; + + use crate::circuit::Program; + use crate::native_types::{WitnessMap, WitnessStack}; + + // It's not possible to set the maximum size of collections via `ProptestConfig`, only an env var, + // because e.g. the `VecStrategy` uses `Config::default().max_default_size_range`. On top of that, + // `Config::default()` reads a static `DEFAULT_CONFIG`, which gets the env vars only once at the + // beginning, so we can't override this on a test-by-test basis, unless we use `fork`, + // which is a feature that is currently disabled, because it doesn't work with Wasm. + // We could add it as a `dev-dependency` just for this crate, but when I tried it just crashed. + // For now using a const so it's obvious we can't set it to different values for different tests. + const MAX_SIZE_RANGE: usize = 5; + const SIZE_RANGE_KEY: &str = "PROPTEST_MAX_DEFAULT_SIZE_RANGE"; + + // Define a wrapper around field so we can implement `Arbitrary`. + // NB there are other methods like `arbitrary_field_elements` around the codebase, + // but for `proptest_derive::Arbitrary` we need `F: AcirField + Arbitrary`. + acir_field::field_wrapper!(TestField, FieldElement); + + impl Arbitrary for TestField { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::().prop_map(|v| Self(FieldElement::from(v))).boxed() + } + } + + /// Override the maximum size of collections created by `proptest`. + fn run_with_max_size_range(cases: u32, f: F) + where + T: Arbitrary, + F: Fn(T) -> TestCaseResult, + { + let orig_size_range = std::env::var(SIZE_RANGE_KEY).ok(); + // The defaults are only read once. If they are already set, leave them be. + if orig_size_range.is_none() { + unsafe { + std::env::set_var(SIZE_RANGE_KEY, MAX_SIZE_RANGE.to_string()); + } + } + + let mut runner = TestRunner::new(ProptestConfig { cases, ..Default::default() }); + let result = runner.run(&any::(), f); + + // Restore the original. + unsafe { + std::env::set_var(SIZE_RANGE_KEY, orig_size_range.unwrap_or_default()); + } + + result.unwrap(); + } + + #[test] + fn prop_program_proto_roundtrip() { + run_with_max_size_range(100, |program: Program| { + let bz = Program::proto_serialize(&program); + let de = Program::proto_deserialize(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_program_bincode_roundtrip() { + run_with_max_size_range(100, |program: Program| { + let bz = Program::bincode_serialize(&program)?; + let de = Program::bincode_deserialize(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_program_roundtrip() { + run_with_max_size_range(10, |program: Program| { + let bz = Program::serialize_program(&program); + let de = Program::deserialize_program(&bz)?; + prop_assert_eq!(program, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_proto_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = WitnessStack::proto_serialize(&witness); + let de = WitnessStack::proto_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_bincode_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = WitnessStack::bincode_serialize(&witness)?; + let de = WitnessStack::bincode_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_stack_roundtrip() { + run_with_max_size_range(10, |witness: WitnessStack| { + let bz = Vec::::try_from(&witness)?; + let de = WitnessStack::try_from(bz.as_slice())?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_proto_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = WitnessMap::proto_serialize(&witness); + let de = WitnessMap::proto_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_bincode_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = WitnessMap::bincode_serialize(&witness)?; + let de = WitnessMap::bincode_deserialize(&bz)?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + + #[test] + fn prop_witness_map_roundtrip() { + run_with_max_size_range(10, |witness: WitnessMap| { + let bz = Vec::::try_from(witness.clone())?; + let de = WitnessMap::try_from(bz.as_slice())?; + prop_assert_eq!(witness, de); + Ok(()) + }); + } + } } diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index dec58b5f90b..41a42a14684 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -15,7 +15,8 @@ pub use black_box_function_call::{ }; pub use memory_operation::{BlockId, MemOp}; -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BlockType { Memory, CallData(u32), @@ -30,7 +31,8 @@ impl BlockType { #[allow(clippy::large_enum_variant)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub enum Opcode { +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] +pub enum Opcode { /// An `AssertZero` opcode adds the constraint that `P(w) = 0`, where /// `w=(w_1,..w_n)` is a tuple of `n` witnesses, and `P` is a multi-variate /// polynomial of total degree at most `2`. diff --git a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 9cf31e94eb4..8c5ada3ee6e 100644 --- a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -10,12 +10,14 @@ use thiserror::Error; // So we need to supply how many bits of the witness is needed #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ConstantOrWitnessEnum { Constant(F), Witness(Witness), } #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct FunctionInput { input: ConstantOrWitnessEnum, num_bits: u32, @@ -80,7 +82,7 @@ impl std::fmt::Display for FunctionInput { } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] -pub enum BlackBoxFuncCall { +pub enum BlackBoxFuncCall { AES128Encrypt { inputs: Vec>, iv: Box<[FunctionInput; 16]>, @@ -214,7 +216,7 @@ pub enum BlackBoxFuncCall { }, } -impl BlackBoxFuncCall { +impl BlackBoxFuncCall { pub fn get_black_box_func(&self) -> BlackBoxFunc { match self { BlackBoxFuncCall::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt, @@ -427,7 +429,7 @@ fn get_outputs_string(outputs: &[Witness]) -> String { } } -impl std::fmt::Display for BlackBoxFuncCall { +impl std::fmt::Display for BlackBoxFuncCall { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let uppercase_name = self.name().to_uppercase(); write!(f, "BLACKBOX::{uppercase_name} ")?; @@ -452,7 +454,7 @@ impl std::fmt::Display for BlackBoxFuncCall { } } -impl std::fmt::Debug for BlackBoxFuncCall { +impl std::fmt::Debug for BlackBoxFuncCall { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self, f) } @@ -506,3 +508,207 @@ mod tests { assert_eq!(opcode, recovered_opcode); } } + +#[cfg(feature = "arb")] +mod arb { + use acir_field::AcirField; + use proptest::prelude::*; + + use crate::native_types::Witness; + + use super::{BlackBoxFuncCall, FunctionInput}; + + // Implementing this separately because trying to derive leads to stack overflow. + impl Arbitrary for BlackBoxFuncCall + where + F: AcirField + Arbitrary, + { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let input = any::>(); + let input_vec = any::>>(); + let input_arr_3 = any::; 3]>>(); + let input_arr_8 = any::; 8]>>(); + let input_arr_16 = any::; 16]>>(); + let input_arr_25 = any::; 25]>>(); + let input_arr_32 = any::; 32]>>(); + let input_arr_64 = any::; 64]>>(); + let witness = any::(); + let witness_vec = any::>(); + let witness_arr_8 = any::>(); + let witness_arr_25 = any::>(); + let witness_arr_32 = any::>(); + + let case_aes128_encrypt = ( + input_vec.clone(), + input_arr_16.clone(), + input_arr_16.clone(), + witness_vec.clone(), + ) + .prop_map(|(inputs, iv, key, outputs)| { + BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs } + }); + + let case_and = (input.clone(), input.clone(), witness.clone()) + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::AND { lhs, rhs, output }); + + let case_xor = (input.clone(), input.clone(), witness.clone()) + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::XOR { lhs, rhs, output }); + + let case_range = input.clone().prop_map(|input| BlackBoxFuncCall::RANGE { input }); + + let case_blake2s = (input_vec.clone(), witness_arr_32.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Blake2s { inputs, outputs }); + + let case_blake3 = (input_vec.clone(), witness_arr_32.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Blake3 { inputs, outputs }); + + let case_ecdsa_secp256k1 = ( + input_arr_32.clone(), + input_arr_32.clone(), + input_arr_64.clone(), + input_arr_32.clone(), + witness.clone(), + ) + .prop_map( + |(public_key_x, public_key_y, signature, hashed_message, output)| { + BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } + }, + ); + + let case_ecdsa_secp256r1 = ( + input_arr_32.clone(), + input_arr_32.clone(), + input_arr_64.clone(), + input_arr_32.clone(), + witness.clone(), + ) + .prop_map( + |(public_key_x, public_key_y, signature, hashed_message, output)| { + BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } + }, + ); + + let case_multi_scalar_mul = ( + input_vec.clone(), + input_vec.clone(), + witness.clone(), + witness.clone(), + witness.clone(), + ) + .prop_map(|(points, scalars, w1, w2, w3)| { + BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs: (w1, w2, w3) } + }); + + let case_embedded_curve_add = ( + input_arr_3.clone(), + input_arr_3.clone(), + witness.clone(), + witness.clone(), + witness.clone(), + ) + .prop_map(|(input1, input2, w1, w2, w3)| { + BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs: (w1, w2, w3) } + }); + + let case_keccakf1600 = (input_arr_25.clone(), witness_arr_25.clone()) + .prop_map(|(inputs, outputs)| BlackBoxFuncCall::Keccakf1600 { inputs, outputs }); + + let case_recursive_aggregation = ( + input_vec.clone(), + input_vec.clone(), + input_vec.clone(), + input.clone(), + any::(), + ) + .prop_map( + |(verification_key, proof, public_inputs, key_hash, proof_type)| { + BlackBoxFuncCall::RecursiveAggregation { + verification_key, + proof, + public_inputs, + key_hash, + proof_type, + } + }, + ); + + let big_int_args = (any::(), any::(), any::()); + + let case_big_int_add = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntAdd { lhs, rhs, output }); + + let case_big_int_sub = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntSub { lhs, rhs, output }); + + let case_big_int_mul = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntMul { lhs, rhs, output }); + + let case_big_int_div = big_int_args + .prop_map(|(lhs, rhs, output)| BlackBoxFuncCall::BigIntDiv { lhs, rhs, output }); + + let case_big_int_from_le_bytes = (input_vec.clone(), any::>(), any::()) + .prop_map(|(inputs, modulus, output)| BlackBoxFuncCall::BigIntFromLeBytes { + inputs, + modulus, + output, + }); + + let case_big_int_to_le_bytes = (any::(), witness_vec.clone()) + .prop_map(|(input, outputs)| BlackBoxFuncCall::BigIntToLeBytes { input, outputs }); + + let case_poseidon2_permutation = (input_vec.clone(), witness_vec.clone(), any::()) + .prop_map(|(inputs, outputs, len)| BlackBoxFuncCall::Poseidon2Permutation { + inputs, + outputs, + len, + }); + + let case_sha256_compression = (input_arr_16, input_arr_8, witness_arr_8).prop_map( + |(inputs, hash_values, outputs)| BlackBoxFuncCall::Sha256Compression { + inputs, + hash_values, + outputs, + }, + ); + + prop_oneof![ + case_aes128_encrypt, + case_and, + case_xor, + case_range, + case_blake2s, + case_blake3, + case_ecdsa_secp256k1, + case_ecdsa_secp256r1, + case_multi_scalar_mul, + case_embedded_curve_add, + case_keccakf1600, + case_recursive_aggregation, + case_big_int_add, + case_big_int_sub, + case_big_int_mul, + case_big_int_div, + case_big_int_from_le_bytes, + case_big_int_to_le_bytes, + case_poseidon2_permutation, + case_sha256_compression, + ] + .boxed() + } + } +} diff --git a/acvm-repo/acir/src/circuit/opcodes/function_id.rs b/acvm-repo/acir/src/circuit/opcodes/function_id.rs index b5abb1b3942..e87e52ce967 100644 --- a/acvm-repo/acir/src/circuit/opcodes/function_id.rs +++ b/acvm-repo/acir/src/circuit/opcodes/function_id.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] #[serde(transparent)] pub struct AcirFunctionId(pub u32); diff --git a/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs b/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs index c9a78983204..66034166b23 100644 --- a/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs +++ b/acvm-repo/acir/src/circuit/opcodes/memory_operation.rs @@ -3,11 +3,13 @@ use acir_field::AcirField; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash, Copy, Default)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct BlockId(pub u32); /// Operation on a block of memory /// We can either write or read at an index in memory #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct MemOp { /// A constant expression that can be 0 (read) or 1 (write) pub operation: Expression, diff --git a/acvm-repo/acir/src/lib.rs b/acvm-repo/acir/src/lib.rs index e49ab60f9e0..63a1253cbe1 100644 --- a/acvm-repo/acir/src/lib.rs +++ b/acvm-repo/acir/src/lib.rs @@ -1,4 +1,4 @@ -#![forbid(unsafe_code)] +#![cfg_attr(not(test), forbid(unsafe_code))] // `std::env::set_var` is used in tests. #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] @@ -7,6 +7,7 @@ pub mod circuit; pub mod native_types; +mod proto; pub use acir_field; pub use acir_field::{AcirField, FieldElement}; diff --git a/acvm-repo/acir/src/native_types/expression/mod.rs b/acvm-repo/acir/src/native_types/expression/mod.rs index cdb8974526f..1d1f12b7eea 100644 --- a/acvm-repo/acir/src/native_types/expression/mod.rs +++ b/acvm-repo/acir/src/native_types/expression/mod.rs @@ -13,6 +13,7 @@ mod ordering; // In the multiplication polynomial // XXX: If we allow the degree of the quotient polynomial to be arbitrary, then we will need a vector of wire values #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct Expression { // To avoid having to create intermediate variables pre-optimization // We collect all of the multiplication terms in the assert-zero opcode diff --git a/acvm-repo/acir/src/native_types/witness.rs b/acvm-repo/acir/src/native_types/witness.rs index a570968f948..d97702174b8 100644 --- a/acvm-repo/acir/src/native_types/witness.rs +++ b/acvm-repo/acir/src/native_types/witness.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; #[derive( Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize, )] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct Witness(pub u32); impl Witness { diff --git a/acvm-repo/acir/src/native_types/witness_map.rs b/acvm-repo/acir/src/native_types/witness_map.rs index 77745c714a3..cbaef49049f 100644 --- a/acvm-repo/acir/src/native_types/witness_map.rs +++ b/acvm-repo/acir/src/native_types/witness_map.rs @@ -4,18 +4,24 @@ use std::{ ops::Index, }; +use acir_field::AcirField; use flate2::Compression; use flate2::bufread::GzDecoder; use flate2::bufread::GzEncoder; +use noir_protobuf::ProtoCodec as _; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::native_types::Witness; +use crate::{native_types::Witness, proto::convert::ProtoSchema}; #[derive(Debug, Error)] enum SerializationError { #[error(transparent)] Deflate(#[from] std::io::Error), + + #[allow(dead_code)] + #[error("error deserializing witness map: {0}")] + Deserialize(String), } #[derive(Debug, Error)] @@ -24,6 +30,7 @@ pub struct WitnessMapError(#[from] SerializationError); /// A map from the witnesses in a constraint system to the field element values #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct WitnessMap(BTreeMap); impl WitnessMap { @@ -77,26 +84,50 @@ impl From> for WitnessMap { } } -impl TryFrom> for Vec { +impl WitnessMap { + pub(crate) fn bincode_serialize(&self) -> Result, WitnessMapError> { + bincode::serialize(self).map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl Deserialize<'a>> WitnessMap { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> Result { + bincode::deserialize(buf).map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +#[allow(dead_code)] +impl WitnessMap { + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + + pub(crate) fn proto_deserialize(buf: &[u8]) -> Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl TryFrom> for Vec { type Error = WitnessMapError; fn try_from(val: WitnessMap) -> Result { - let buf = bincode::serialize(&val).unwrap(); + let buf = val.bincode_serialize()?; let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best()); - let mut buf_c = Vec::new(); - deflater.read_to_end(&mut buf_c).map_err(|err| WitnessMapError(err.into()))?; - Ok(buf_c) + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessMapError(err.into()))?; + Ok(buf) } } -impl Deserialize<'a>> TryFrom<&[u8]> for WitnessMap { +impl Deserialize<'a>> TryFrom<&[u8]> for WitnessMap { type Error = WitnessMapError; fn try_from(bytes: &[u8]) -> Result { let mut deflater = GzDecoder::new(bytes); - let mut buf_d = Vec::new(); - deflater.read_to_end(&mut buf_d).map_err(|err| WitnessMapError(err.into()))?; - let witness_map = bincode::deserialize(&buf_d).unwrap(); - Ok(Self(witness_map)) + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessMapError(err.into()))?; + let witness_map = Self::bincode_deserialize(&buf)?; + Ok(witness_map) } } diff --git a/acvm-repo/acir/src/native_types/witness_stack.rs b/acvm-repo/acir/src/native_types/witness_stack.rs index 6338ad630d6..d8c0eb14773 100644 --- a/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/acvm-repo/acir/src/native_types/witness_stack.rs @@ -1,11 +1,15 @@ use std::io::Read; +use acir_field::AcirField; use flate2::Compression; use flate2::bufread::GzDecoder; use flate2::bufread::GzEncoder; +use noir_protobuf::ProtoCodec as _; use serde::{Deserialize, Serialize}; use thiserror::Error; +use crate::proto::convert::ProtoSchema; + use super::WitnessMap; #[derive(Debug, Error)] @@ -15,6 +19,10 @@ enum SerializationError { #[error(transparent)] BincodeError(#[from] bincode::Error), + + #[allow(dead_code)] + #[error("error deserializing witness stack: {0}")] + Deserialize(String), } #[derive(Debug, Error)] @@ -23,11 +31,13 @@ pub struct WitnessStackError(#[from] SerializationError); /// An ordered set of witness maps for separate circuits #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct WitnessStack { stack: Vec>, } #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct StackItem { /// Index into a [crate::circuit::Program] function list for which we have an associated witness pub index: u32, @@ -60,11 +70,35 @@ impl From> for WitnessStack { } } -impl TryFrom<&WitnessStack> for Vec { +impl WitnessStack { + pub(crate) fn bincode_serialize(&self) -> Result, WitnessStackError> { + bincode::serialize(self).map_err(|e| WitnessStackError(e.into())) + } +} + +impl Deserialize<'a>> WitnessStack { + pub(crate) fn bincode_deserialize(buf: &[u8]) -> Result { + bincode::deserialize(buf).map_err(|e| WitnessStackError(e.into())) + } +} + +#[allow(dead_code)] +impl WitnessStack { + pub(crate) fn proto_serialize(&self) -> Vec { + ProtoSchema::::serialize_to_vec(self) + } + + pub(crate) fn proto_deserialize(buf: &[u8]) -> Result { + ProtoSchema::::deserialize_from_vec(buf) + .map_err(|e| SerializationError::Deserialize(e.to_string()).into()) + } +} + +impl TryFrom<&WitnessStack> for Vec { type Error = WitnessStackError; fn try_from(val: &WitnessStack) -> Result { - let buf = bincode::serialize(val).map_err(|e| WitnessStackError(e.into()))?; + let buf = val.bincode_serialize()?; let mut deflater = GzEncoder::new(buf.as_slice(), Compression::best()); let mut buf_c = Vec::new(); deflater.read_to_end(&mut buf_c).map_err(|err| WitnessStackError(err.into()))?; @@ -72,7 +106,7 @@ impl TryFrom<&WitnessStack> for Vec { } } -impl TryFrom> for Vec { +impl TryFrom> for Vec { type Error = WitnessStackError; fn try_from(val: WitnessStack) -> Result { @@ -80,15 +114,14 @@ impl TryFrom> for Vec { } } -impl Deserialize<'a>> TryFrom<&[u8]> for WitnessStack { +impl Deserialize<'a>> TryFrom<&[u8]> for WitnessStack { type Error = WitnessStackError; fn try_from(bytes: &[u8]) -> Result { let mut deflater = GzDecoder::new(bytes); - let mut buf_d = Vec::new(); - deflater.read_to_end(&mut buf_d).map_err(|err| WitnessStackError(err.into()))?; - let witness_stack = - bincode::deserialize(&buf_d).map_err(|e| WitnessStackError(e.into()))?; + let mut buf = Vec::new(); + deflater.read_to_end(&mut buf).map_err(|err| WitnessStackError(err.into()))?; + let witness_stack = Self::bincode_deserialize(&buf)?; Ok(witness_stack) } } diff --git a/acvm-repo/acir/src/proto/acir/circuit.proto b/acvm-repo/acir/src/proto/acir/circuit.proto new file mode 100644 index 00000000000..c0981d6e30c --- /dev/null +++ b/acvm-repo/acir/src/proto/acir/circuit.proto @@ -0,0 +1,255 @@ +syntax = "proto3"; + +package acvm.acir.circuit; + +import "acir/native.proto"; + +message Circuit { + uint32 current_witness_index = 1; + repeated Opcode opcodes = 2; + ExpressionWidth expression_width = 3; + repeated native.Witness private_parameters = 4; + repeated native.Witness public_parameters = 5; + repeated native.Witness return_values = 6; + repeated AssertMessage assert_messages = 7; +} + +message ExpressionWidth { + oneof value { + Unbounded unbounded = 1; + Bounded bounded = 2; + } + message Unbounded {} + message Bounded { uint64 width = 1; } +} + +message AssertMessage { + OpcodeLocation location = 1; + AssertionPayload payload = 2; +} + +message OpcodeLocation { + oneof value { + uint64 acir = 1; + BrilligLocation brillig = 2; + } + message BrilligLocation { + uint64 acir_index = 1; + uint64 brillig_index = 2; + } +} + +message AssertionPayload { + uint64 error_selector = 1; + repeated ExpressionOrMemory payload = 2; +} + +message ExpressionOrMemory { + oneof value { + native.Expression expression = 1; + uint32 memory = 2; + } +} + +message Opcode { + oneof value { + native.Expression assert_zero = 1; + BlackBoxFuncCall blackbox_func_call = 2; + MemoryOp memory_op = 3; + MemoryInit memory_init = 4; + BrilligCall brillig_call = 5; + Call call = 6; + } + message MemoryOp { + uint32 block_id = 1; + MemOp op = 2; + optional native.Expression predicate = 3; + } + message MemoryInit { + uint32 block_id = 1; + repeated native.Witness init = 2; + BlockType block_type = 3; + } + message BrilligCall { + uint32 id = 1; + repeated BrilligInputs inputs = 2; + repeated BrilligOutputs outputs = 3; + optional native.Expression predicate = 4; + } + message Call { + uint32 id = 1; + repeated native.Witness inputs = 2; + repeated native.Witness outputs = 3; + optional native.Expression predicate = 4; + } +} + +message BlackBoxFuncCall { + oneof value { + AES128Encrypt aes128_encrypt = 1; + AND and = 2; + XOR xor = 3; + RANGE range = 4; + Blake2s blake2s = 5; + Blake3 blake3 = 6; + EcdsaSecp256k1 ecdsa_secp256k1 = 7; + EcdsaSecp256r1 ecdsa_secp256r1 = 8; + MultiScalarMul multi_scalar_mul = 9; + EmbeddedCurveAdd embedded_curve_add = 10; + Keccakf1600 keccak_f1600 = 11; + RecursiveAggregation recursive_aggregation = 12; + BigIntAdd big_int_add = 13; + BigIntSub big_int_sub = 14; + BigIntMul big_int_mul = 15; + BigIntDiv big_int_div = 16; + BigIntFromLeBytes big_int_from_le_bytes = 17; + BigIntToLeBytes big_int_to_le_bytes = 18; + Poseidon2Permutation poseidon2_permutation = 19; + Sha256Compression sha256_compression = 20; + } + message AES128Encrypt { + repeated FunctionInput inputs = 1; + repeated FunctionInput iv = 2; + repeated FunctionInput key = 3; + repeated native.Witness outputs = 4; + } + message AND { + FunctionInput lhs = 1; + FunctionInput rhs = 2; + native.Witness output = 3; + } + message XOR { + FunctionInput lhs = 1; + FunctionInput rhs = 2; + native.Witness output = 3; + } + message RANGE { FunctionInput input = 1; } + message Blake2s { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message Blake3 { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message EcdsaSecp256k1 { + repeated FunctionInput public_key_x = 1; + repeated FunctionInput public_key_y = 2; + repeated FunctionInput signature = 3; + repeated FunctionInput hashed_message = 4; + native.Witness output = 5; + } + message EcdsaSecp256r1 { + repeated FunctionInput public_key_x = 1; + repeated FunctionInput public_key_y = 2; + repeated FunctionInput signature = 3; + repeated FunctionInput hashed_message = 4; + native.Witness output = 5; + } + message MultiScalarMul { + repeated FunctionInput points = 1; + repeated FunctionInput scalars = 2; + repeated native.Witness outputs = 3; + } + message EmbeddedCurveAdd { + repeated FunctionInput input1 = 1; + repeated FunctionInput input2 = 2; + repeated native.Witness outputs = 3; + } + message Keccakf1600 { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + } + message RecursiveAggregation { + repeated FunctionInput verification_key = 1; + repeated FunctionInput proof = 2; + repeated FunctionInput public_inputs = 3; + FunctionInput key_hash = 4; + uint32 proof_type = 5; + } + message BigIntAdd { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntSub { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntMul { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntDiv { + uint32 lhs = 1; + uint32 rhs = 2; + uint32 output = 3; + } + message BigIntFromLeBytes { + repeated FunctionInput inputs = 1; + bytes modulus = 2; + uint32 output = 3; + } + message BigIntToLeBytes { + uint32 input = 1; + repeated native.Witness outputs = 2; + } + message Poseidon2Permutation { + repeated FunctionInput inputs = 1; + repeated native.Witness outputs = 2; + uint32 len = 3; + } + message Sha256Compression { + repeated FunctionInput inputs = 1; + repeated FunctionInput hash_values = 2; + repeated native.Witness outputs = 3; + } +} + +message FunctionInput { + ConstantOrWitnessEnum input = 1; + uint32 num_bits = 2; +} + +message ConstantOrWitnessEnum { + oneof value { + native.Field constant = 1; + native.Witness witness = 2; + } +} + +message MemOp { + native.Expression operation = 1; + native.Expression index = 2; + native.Expression value = 3; +} + +message BlockType { + oneof value { + Memory memory = 1; + CallData call_data = 2; + ReturnData return_data = 3; + } + message Memory {} + message CallData { uint32 value = 1; } + message ReturnData {} +} + +message BrilligInputs { + oneof value { + native.Expression single = 1; + Array array = 2; + uint32 memory_array = 3; + } + message Array { repeated native.Expression values = 2; } +} + +message BrilligOutputs { + oneof value { + native.Witness simple = 1; + Array array = 2; + } + message Array { repeated native.Witness values = 1; } +} diff --git a/acvm-repo/acir/src/proto/acir/native.proto b/acvm-repo/acir/src/proto/acir/native.proto new file mode 100644 index 00000000000..561a79c5701 --- /dev/null +++ b/acvm-repo/acir/src/proto/acir/native.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package acvm.acir.native; + +message Field { bytes value = 1; } + +message Witness { uint32 index = 1; } + +message Expression { + repeated MulTerm mul_terms = 1; + repeated LinearCombination linear_combinations = 2; + Field q_c = 3; + + message MulTerm { + Field q_m = 1; + Witness witness_left = 2; + Witness witness_right = 3; + } + + message LinearCombination { + Field q_l = 1; + Witness witness = 2; + } +} \ No newline at end of file diff --git a/acvm-repo/acir/src/proto/acir/program.proto b/acvm-repo/acir/src/proto/acir/program.proto new file mode 100644 index 00000000000..0032966d0cb --- /dev/null +++ b/acvm-repo/acir/src/proto/acir/program.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package acvm.acir.program; + +// Only including the ACIR types for Berratenberg, not Brillig. +import "acir/circuit.proto"; + +// Same as the top level `program.proto` but ignores the +// `unconstrained_functions` field, so that Berratenberg doesn't need to +// deserialize it. +message Program { + // ACIR circuits + repeated acvm.acir.circuit.Circuit functions = 1; + + reserved 2; + reserved "unconstrained_functions"; +} \ No newline at end of file diff --git a/acvm-repo/acir/src/proto/acir/witness.proto b/acvm-repo/acir/src/proto/acir/witness.proto new file mode 100644 index 00000000000..bb6a29341ee --- /dev/null +++ b/acvm-repo/acir/src/proto/acir/witness.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package acvm.acir.witness; + +import "acir/native.proto"; + +message WitnessMap { + repeated WitnessValue values = 1; + + message WitnessValue { + native.Witness witness = 1; + native.Field field = 2; + } +} + +message WitnessStack { + repeated StackItem stack = 1; + + message StackItem { + uint32 index = 1; + WitnessMap witness = 2; + } +} \ No newline at end of file diff --git a/acvm-repo/acir/src/proto/brillig.proto b/acvm-repo/acir/src/proto/brillig.proto new file mode 100644 index 00000000000..cba8c797889 --- /dev/null +++ b/acvm-repo/acir/src/proto/brillig.proto @@ -0,0 +1,308 @@ +syntax = "proto3"; + +package acvm.brillig; + +import "acir/native.proto"; + +message BrilligBytecode { repeated BrilligOpcode bytecode = 1; } + +message BrilligOpcode { + oneof value { + BinaryFieldOp binary_field_op = 1; + BinaryIntOp binary_int_op = 2; + Not not = 3; + Cast cast = 4; + JumpIfNot jump_if_not = 5; + JumpIf jump_if = 6; + Jump jump = 7; + CalldataCopy calldata_copy = 8; + Call call = 9; + Const const = 10; + IndirectConst indirect_const = 11; + Return return = 12; + ForeignCall foreign_call = 13; + Mov mov = 14; + ConditionalMov conditional_mov = 15; + Load load = 16; + Store store = 17; + BlackBox black_box = 18; + Trap trap = 19; + Stop stop = 20; + } + message BinaryFieldOp { + MemoryAddress destination = 1; + BinaryFieldOpKind op = 2; + MemoryAddress lhs = 3; + MemoryAddress rhs = 4; + } + message BinaryIntOp { + MemoryAddress destination = 1; + BinaryIntOpKind op = 2; + IntegerBitSize bit_size = 3; + MemoryAddress lhs = 4; + MemoryAddress rhs = 5; + } + message Not { + MemoryAddress destination = 1; + MemoryAddress source = 2; + IntegerBitSize bit_size = 3; + } + message Cast { + MemoryAddress destination = 1; + MemoryAddress source = 2; + BitSize bit_size = 3; + } + message JumpIfNot { + MemoryAddress condition = 1; + uint64 location = 2; + } + message JumpIf { + MemoryAddress condition = 1; + uint64 location = 2; + } + message Jump { uint64 location = 1; } + message CalldataCopy { + MemoryAddress destination_address = 1; + MemoryAddress size_address = 2; + MemoryAddress offset_address = 3; + } + message Call { uint64 location = 1; } + message Const { + MemoryAddress destination = 1; + BitSize bit_size = 2; + acir.native.Field value = 3; + } + message IndirectConst { + MemoryAddress destination_pointer = 1; + BitSize bit_size = 2; + acir.native.Field value = 3; + } + message Return {} + message ForeignCall { + string function = 1; + repeated ValueOrArray destinations = 2; + repeated HeapValueType destination_value_types = 3; + repeated ValueOrArray inputs = 4; + repeated HeapValueType input_value_types = 5; + } + message Mov { + MemoryAddress destination = 1; + MemoryAddress source = 2; + } + message ConditionalMov { + MemoryAddress destination = 1; + MemoryAddress source_a = 2; + MemoryAddress source_b = 3; + MemoryAddress condition = 4; + } + message Load { + MemoryAddress destination = 1; + MemoryAddress source_pointer = 2; + } + message Store { + MemoryAddress destination_pointer = 1; + MemoryAddress source = 2; + } + message BlackBox { BlackBoxOp op = 1; } + message Trap { HeapVector revert_data = 1; } + message Stop { HeapVector return_data = 1; } +} + +message MemoryAddress { + oneof value { + uint64 direct = 1; + uint64 relative = 2; + } +} + +message ValueOrArray { + oneof value { + MemoryAddress memory_address = 1; + HeapArray heap_array = 2; + HeapVector heap_vector = 3; + } +} + +message HeapArray { + MemoryAddress pointer = 1; + uint64 size = 2; +} + +message HeapVector { + MemoryAddress pointer = 1; + MemoryAddress size = 2; +} + +message HeapValueType { + oneof value { + BitSize simple = 1; + Array array = 2; + Vector vector = 3; + } + message Array { + repeated HeapValueType value_types = 1; + uint64 size = 2; + } + message Vector { repeated HeapValueType value_types = 1; } +} + +enum BinaryFieldOpKind { + BFO_UNSPECIFIED = 0; + BFO_ADD = 1; + BFO_SUB = 2; + BFO_MUL = 3; + BFO_DIV = 4; + BFO_INTEGER_DIV = 5; + BFO_EQUALS = 6; + BFO_LESS_THAN = 7; + BFO_LESS_THAN_EQUALS = 8; +} + +enum BinaryIntOpKind { + BIO_UNSPECIFIED = 0; + BIO_ADD = 1; + BIO_SUB = 2; + BIO_MUL = 3; + BIO_DIV = 4; + BIO_EQUALS = 5; + BIO_LESS_THAN = 6; + BIO_LESS_THAN_EQUALS = 7; + BIO_AND = 8; + BIO_OR = 9; + BIO_XOR = 10; + BIO_SHL = 11; + BIO_SHR = 12; +} + +enum IntegerBitSize { + IBS_UNSPECIFIED = 0; + IBS_U1 = 1; + IBS_U8 = 8; + IBS_U16 = 16; + IBS_U32 = 32; + IBS_U64 = 64; + IBS_U128 = 128; +} + +message BitSize { + oneof value { + Field field = 1; + IntegerBitSize integer = 2; + } + message Field {} +} + +message BlackBoxOp { + oneof value { + AES128Encrypt aes128_encrypt = 1; + Blake2s blake2s = 2; + Blake3 blake3 = 3; + Keccakf1600 keccak_f1600 = 4; + EcdsaSecp256k1 ecdsa_secp256k1 = 5; + EcdsaSecp256r1 ecdsa_secp256r1 = 6; + MultiScalarMul multi_scalar_mul = 7; + EmbeddedCurveAdd embedded_curve_add = 8; + BigIntAdd big_int_add = 9; + BigIntSub big_int_sub = 10; + BigIntMul big_int_mul = 11; + BigIntDiv big_int_div = 12; + BigIntFromLeBytes big_int_from_le_bytes = 13; + BigIntToLeBytes big_int_to_le_bytes = 14; + Poseidon2Permutation poseidon2_permutation = 15; + Sha256Compression sha256_compression = 16; + ToRadix to_radix = 17; + } + message AES128Encrypt { + HeapVector inputs = 1; + HeapArray iv = 2; + HeapArray key = 3; + HeapVector outputs = 4; + } + message Blake2s { + HeapVector message = 1; + HeapArray output = 2; + } + message Blake3 { + HeapVector message = 1; + HeapArray output = 2; + } + message Keccakf1600 { + HeapArray input = 1; + HeapArray output = 2; + } + message EcdsaSecp256k1 { + HeapVector hashed_msg = 1; + HeapArray public_key_x = 2; + HeapArray public_key_y = 3; + HeapArray signature = 4; + MemoryAddress result = 5; + } + message EcdsaSecp256r1 { + HeapVector hashed_msg = 1; + HeapArray public_key_x = 2; + HeapArray public_key_y = 3; + HeapArray signature = 4; + MemoryAddress result = 5; + } + + message MultiScalarMul { + HeapVector points = 1; + HeapVector scalars = 2; + HeapArray outputs = 3; + } + message EmbeddedCurveAdd { + MemoryAddress input1_x = 1; + MemoryAddress input1_y = 2; + MemoryAddress input1_infinite = 3; + MemoryAddress input2_x = 4; + MemoryAddress input2_y = 5; + MemoryAddress input2_infinite = 6; + HeapArray result = 7; + } + message BigIntAdd { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntSub { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntMul { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntDiv { + MemoryAddress lhs = 1; + MemoryAddress rhs = 2; + MemoryAddress output = 3; + } + message BigIntFromLeBytes { + HeapVector inputs = 1; + HeapVector modulus = 2; + MemoryAddress output = 3; + } + message BigIntToLeBytes { + MemoryAddress input = 1; + HeapVector output = 2; + } + message Poseidon2Permutation { + HeapVector message = 1; + HeapArray output = 2; + MemoryAddress len = 3; + } + message Sha256Compression { + HeapArray input = 1; + HeapArray hash_values = 2; + HeapArray output = 3; + } + message ToRadix { + MemoryAddress input = 1; + MemoryAddress radix = 2; + MemoryAddress output_pointer = 3; + MemoryAddress num_limbs = 4; + MemoryAddress output_bits = 5; + } +} \ No newline at end of file diff --git a/acvm-repo/acir/src/proto/convert/acir.rs b/acvm-repo/acir/src/proto/convert/acir.rs new file mode 100644 index 00000000000..1a6d9169b30 --- /dev/null +++ b/acvm-repo/acir/src/proto/convert/acir.rs @@ -0,0 +1,682 @@ +use crate::{ + circuit::{ + self, + brillig::BrilligFunctionId, + opcodes::{self, AcirFunctionId, BlockId}, + }, + proto::acir::circuit::{ + AssertMessage, AssertionPayload, BlackBoxFuncCall, BlockType, BrilligInputs, + BrilligOutputs, Circuit, ConstantOrWitnessEnum, ExpressionOrMemory, ExpressionWidth, + FunctionInput, MemOp, Opcode, OpcodeLocation, + }, +}; +use acir_field::AcirField; +use color_eyre::eyre::{self, Context}; +use noir_protobuf::{ProtoCodec, decode_oneof_map}; + +use super::ProtoSchema; + +impl ProtoCodec, Circuit> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::Circuit) -> Circuit { + Circuit { + current_witness_index: value.current_witness_index, + opcodes: Self::encode_vec(&value.opcodes), + expression_width: Self::encode_some(&value.expression_width), + private_parameters: Self::encode_vec(value.private_parameters.iter()), + public_parameters: Self::encode_vec(value.public_parameters.0.iter()), + return_values: Self::encode_vec(value.return_values.0.iter()), + assert_messages: Self::encode_vec(&value.assert_messages), + } + } + + fn decode(value: &Circuit) -> eyre::Result> { + Ok(circuit::Circuit { + current_witness_index: value.current_witness_index, + opcodes: Self::decode_vec_wrap(&value.opcodes, "opcodes")?, + expression_width: Self::decode_some_wrap(&value.expression_width, "expression_width")?, + private_parameters: Self::decode_vec_wrap( + &value.private_parameters, + "private_parameters", + )? + .into_iter() + .collect(), + public_parameters: circuit::PublicInputs( + Self::decode_vec_wrap(&value.public_parameters, "public_parameters")? + .into_iter() + .collect(), + ), + return_values: circuit::PublicInputs( + Self::decode_vec_wrap(&value.return_values, "return_values")?.into_iter().collect(), + ), + assert_messages: Self::decode_vec_wrap(&value.assert_messages, "assert_messages")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::ExpressionWidth) -> ExpressionWidth { + use crate::proto::acir::circuit::expression_width::*; + let value = match value { + circuit::ExpressionWidth::Unbounded => Value::Unbounded(Unbounded {}), + circuit::ExpressionWidth::Bounded { width } => { + Value::Bounded(Bounded { width: Self::encode(width) }) + } + }; + ExpressionWidth { value: Some(value) } + } + + fn decode(value: &ExpressionWidth) -> eyre::Result { + use crate::proto::acir::circuit::expression_width::*; + decode_oneof_map(&value.value, |value| match value { + Value::Unbounded(_) => Ok(circuit::ExpressionWidth::Unbounded), + Value::Bounded(v) => Ok(circuit::ExpressionWidth::Bounded { + width: Self::decode_wrap(&v.width, "width")?, + }), + }) + } +} + +impl ProtoCodec<(circuit::OpcodeLocation, circuit::AssertionPayload), AssertMessage> + for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &(circuit::OpcodeLocation, circuit::AssertionPayload)) -> AssertMessage { + AssertMessage { + location: Self::encode_some(&value.0), + payload: Self::encode_some(&value.1), + } + } + + fn decode( + value: &AssertMessage, + ) -> eyre::Result<(circuit::OpcodeLocation, circuit::AssertionPayload)> { + let location = Self::decode_some_wrap(&value.location, "location")?; + let payload = Self::decode_some_wrap(&value.payload, "payload")?; + Ok((location, payload)) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::OpcodeLocation) -> OpcodeLocation { + use crate::proto::acir::circuit::opcode_location::*; + let value = match value { + circuit::OpcodeLocation::Acir(size) => Value::Acir(Self::encode(size)), + circuit::OpcodeLocation::Brillig { acir_index, brillig_index } => { + Value::Brillig(BrilligLocation { + acir_index: Self::encode(acir_index), + brillig_index: Self::encode(brillig_index), + }) + } + }; + OpcodeLocation { value: Some(value) } + } + + fn decode(value: &OpcodeLocation) -> eyre::Result { + use crate::proto::acir::circuit::opcode_location::*; + decode_oneof_map(&value.value, |value| match value { + Value::Acir(location) => { + Ok(circuit::OpcodeLocation::Acir(Self::decode_wrap(location, "location")?)) + } + Value::Brillig(location) => Ok(circuit::OpcodeLocation::Brillig { + acir_index: Self::decode_wrap(&location.acir_index, "acir_index")?, + brillig_index: Self::decode_wrap(&location.brillig_index, "brillig_index")?, + }), + }) + } +} + +impl ProtoCodec, AssertionPayload> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::AssertionPayload) -> AssertionPayload { + AssertionPayload { + error_selector: value.error_selector, + payload: Self::encode_vec(&value.payload), + } + } + + fn decode(value: &AssertionPayload) -> eyre::Result> { + Ok(circuit::AssertionPayload { + error_selector: value.error_selector, + payload: Self::decode_vec_wrap(&value.payload, "payload")?, + }) + } +} + +impl ProtoCodec, ExpressionOrMemory> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::ExpressionOrMemory) -> ExpressionOrMemory { + use crate::proto::acir::circuit::expression_or_memory::*; + let value = match value { + circuit::ExpressionOrMemory::Expression(expression) => { + Value::Expression(Self::encode(expression)) + } + circuit::ExpressionOrMemory::Memory(block_id) => Value::Memory(block_id.0), + }; + ExpressionOrMemory { value: Some(value) } + } + + fn decode(value: &ExpressionOrMemory) -> eyre::Result> { + use crate::proto::acir::circuit::expression_or_memory::*; + decode_oneof_map(&value.value, |value| match value { + Value::Expression(expression) => Ok(circuit::ExpressionOrMemory::Expression( + Self::decode_wrap(expression, "expression")?, + )), + Value::Memory(id) => Ok(circuit::ExpressionOrMemory::Memory(BlockId(*id))), + }) + } +} + +impl ProtoCodec, Opcode> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::Opcode) -> Opcode { + use crate::proto::acir::circuit::opcode::*; + let value = match value { + circuit::Opcode::AssertZero(expression) => Value::AssertZero(Self::encode(expression)), + circuit::Opcode::BlackBoxFuncCall(black_box_func_call) => { + Value::BlackboxFuncCall(Self::encode(black_box_func_call)) + } + circuit::Opcode::MemoryOp { block_id, op, predicate } => Value::MemoryOp(MemoryOp { + block_id: block_id.0, + op: Self::encode_some(op), + predicate: predicate.as_ref().map(Self::encode), + }), + circuit::Opcode::MemoryInit { block_id, init, block_type } => { + Value::MemoryInit(MemoryInit { + block_id: block_id.0, + init: Self::encode_vec(init), + block_type: Self::encode_some(block_type), + }) + } + circuit::Opcode::BrilligCall { id, inputs, outputs, predicate } => { + Value::BrilligCall(BrilligCall { + id: id.0, + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + predicate: predicate.as_ref().map(Self::encode), + }) + } + circuit::Opcode::Call { id, inputs, outputs, predicate } => Value::Call(Call { + id: id.0, + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + predicate: predicate.as_ref().map(Self::encode), + }), + }; + Opcode { value: Some(value) } + } + + fn decode(value: &Opcode) -> eyre::Result> { + use crate::proto::acir::circuit::opcode::*; + decode_oneof_map(&value.value, |value| match value { + Value::AssertZero(expression) => { + Ok(circuit::Opcode::AssertZero(Self::decode_wrap(expression, "assert_zero")?)) + } + Value::BlackboxFuncCall(black_box_func_call) => Ok(circuit::Opcode::BlackBoxFuncCall( + Self::decode_wrap(black_box_func_call, "blackbox_func_call")?, + )), + Value::MemoryOp(memory_op) => Ok(circuit::Opcode::MemoryOp { + block_id: BlockId(memory_op.block_id), + op: Self::decode_some_wrap(&memory_op.op, "op")?, + predicate: Self::decode_opt_wrap(&memory_op.predicate, "predicate")?, + }), + Value::MemoryInit(memory_init) => Ok(circuit::Opcode::MemoryInit { + block_id: BlockId(memory_init.block_id), + init: Self::decode_vec_wrap(&memory_init.init, "init")?, + block_type: Self::decode_some_wrap(&memory_init.block_type, "block_type")?, + }), + Value::BrilligCall(brillig_call) => Ok(circuit::Opcode::BrilligCall { + id: BrilligFunctionId(brillig_call.id), + inputs: Self::decode_vec_wrap(&brillig_call.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&brillig_call.outputs, "outputs")?, + predicate: Self::decode_opt_wrap(&brillig_call.predicate, "predicate")?, + }), + Value::Call(call) => Ok(circuit::Opcode::Call { + id: AcirFunctionId(call.id), + inputs: Self::decode_vec_wrap(&call.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&call.outputs, "outputs")?, + predicate: Self::decode_opt_wrap(&call.predicate, "predicate")?, + }), + }) + } +} + +impl ProtoCodec, MemOp> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::MemOp) -> MemOp { + MemOp { + operation: Self::encode_some(&value.operation), + index: Self::encode_some(&value.index), + value: Self::encode_some(&value.value), + } + } + + fn decode(value: &MemOp) -> eyre::Result> { + Ok(opcodes::MemOp { + operation: Self::decode_some_wrap(&value.operation, "operation")?, + index: Self::decode_some_wrap(&value.index, "index")?, + value: Self::decode_some_wrap(&value.value, "value")?, + }) + } +} + +impl ProtoCodec, BlackBoxFuncCall> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::BlackBoxFuncCall) -> BlackBoxFuncCall { + use crate::proto::acir::circuit::black_box_func_call::*; + let value = match value { + opcodes::BlackBoxFuncCall::AES128Encrypt { inputs, iv, key, outputs } => { + Value::Aes128Encrypt(Aes128Encrypt { + inputs: Self::encode_vec(inputs), + iv: Self::encode_vec(iv.as_ref()), + key: Self::encode_vec(key.as_ref()), + outputs: Self::encode_vec(outputs), + }) + } + opcodes::BlackBoxFuncCall::AND { lhs, rhs, output } => Value::And(And { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::XOR { lhs, rhs, output } => Value::Xor(Xor { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::RANGE { input } => { + Value::Range(Range { input: Self::encode_some(input) }) + } + opcodes::BlackBoxFuncCall::Blake2s { inputs, outputs } => Value::Blake2s(Blake2s { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs.as_ref()), + }), + opcodes::BlackBoxFuncCall::Blake3 { inputs, outputs } => Value::Blake3(Blake3 { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs.as_ref()), + }), + opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } => Value::EcdsaSecp256k1(EcdsaSecp256k1 { + public_key_x: Self::encode_vec(public_key_x.as_ref()), + public_key_y: Self::encode_vec(public_key_y.as_ref()), + signature: Self::encode_vec(signature.as_ref()), + hashed_message: Self::encode_vec(hashed_message.as_ref()), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x, + public_key_y, + signature, + hashed_message, + output, + } => Value::EcdsaSecp256r1(EcdsaSecp256r1 { + public_key_x: Self::encode_vec(public_key_x.as_ref()), + public_key_y: Self::encode_vec(public_key_y.as_ref()), + signature: Self::encode_vec(signature.as_ref()), + hashed_message: Self::encode_vec(hashed_message.as_ref()), + output: Self::encode_some(output), + }), + opcodes::BlackBoxFuncCall::MultiScalarMul { points, scalars, outputs } => { + let (w1, w2, w3) = outputs; + Value::MultiScalarMul(MultiScalarMul { + points: Self::encode_vec(points), + scalars: Self::encode_vec(scalars), + outputs: Self::encode_vec([w1, w2, w3]), + }) + } + opcodes::BlackBoxFuncCall::EmbeddedCurveAdd { input1, input2, outputs } => { + let (w1, w2, w3) = outputs; + Value::EmbeddedCurveAdd(EmbeddedCurveAdd { + input1: Self::encode_vec(input1.as_ref()), + input2: Self::encode_vec(input2.as_ref()), + outputs: Self::encode_vec([w1, w2, w3]), + }) + } + opcodes::BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => { + Value::KeccakF1600(Keccakf1600 { + inputs: Self::encode_vec(inputs.as_ref()), + outputs: Self::encode_vec(outputs.as_ref()), + }) + } + opcodes::BlackBoxFuncCall::RecursiveAggregation { + verification_key, + proof, + public_inputs, + key_hash, + proof_type, + } => Value::RecursiveAggregation(RecursiveAggregation { + verification_key: Self::encode_vec(verification_key), + proof: Self::encode_vec(proof), + public_inputs: Self::encode_vec(public_inputs), + key_hash: Self::encode_some(key_hash), + proof_type: *proof_type, + }), + opcodes::BlackBoxFuncCall::BigIntAdd { lhs, rhs, output } => { + Value::BigIntAdd(BigIntAdd { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntSub { lhs, rhs, output } => { + Value::BigIntSub(BigIntSub { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntMul { lhs, rhs, output } => { + Value::BigIntMul(BigIntMul { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntDiv { lhs, rhs, output } => { + Value::BigIntDiv(BigIntDiv { lhs: *lhs, rhs: *rhs, output: *output }) + } + opcodes::BlackBoxFuncCall::BigIntFromLeBytes { inputs, modulus, output } => { + Value::BigIntFromLeBytes(BigIntFromLeBytes { + inputs: Self::encode_vec(inputs), + modulus: modulus.clone(), + output: *output, + }) + } + opcodes::BlackBoxFuncCall::BigIntToLeBytes { input, outputs } => { + Value::BigIntToLeBytes(BigIntToLeBytes { + input: *input, + outputs: Self::encode_vec(outputs), + }) + } + opcodes::BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs, len } => { + Value::Poseidon2Permutation(Poseidon2Permutation { + inputs: Self::encode_vec(inputs), + outputs: Self::encode_vec(outputs), + len: *len, + }) + } + opcodes::BlackBoxFuncCall::Sha256Compression { inputs, hash_values, outputs } => { + Value::Sha256Compression(Sha256Compression { + inputs: Self::encode_vec(inputs.as_ref()), + hash_values: Self::encode_vec(hash_values.as_ref()), + outputs: Self::encode_vec(outputs.as_ref()), + }) + } + }; + BlackBoxFuncCall { value: Some(value) } + } + + fn decode(value: &BlackBoxFuncCall) -> eyre::Result> { + use crate::proto::acir::circuit::black_box_func_call::*; + decode_oneof_map( + &value.value, + |value| -> Result, eyre::Error> { + match value { + Value::Aes128Encrypt(v) => Ok(opcodes::BlackBoxFuncCall::AES128Encrypt { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + iv: Self::decode_box_arr_wrap(&v.iv, "iv")?, + key: Self::decode_box_arr_wrap(&v.key, "key")?, + outputs: Self::decode_vec_wrap(&v.outputs, "witness")?, + }), + Value::And(v) => Ok(opcodes::BlackBoxFuncCall::AND { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Xor(v) => Ok(opcodes::BlackBoxFuncCall::XOR { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Range(v) => Ok(opcodes::BlackBoxFuncCall::RANGE { + input: Self::decode_some_wrap(&v.input, "input")?, + }), + Value::Blake2s(v) => Ok(opcodes::BlackBoxFuncCall::Blake2s { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::Blake3(v) => Ok(opcodes::BlackBoxFuncCall::Blake3 { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::EcdsaSecp256k1(v) => Ok(opcodes::BlackBoxFuncCall::EcdsaSecp256k1 { + public_key_x: Self::decode_box_arr_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_box_arr_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_box_arr_wrap(&v.signature, "signature")?, + hashed_message: Self::decode_box_arr_wrap( + &v.hashed_message, + "hashed_message", + )?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::EcdsaSecp256r1(v) => Ok(opcodes::BlackBoxFuncCall::EcdsaSecp256r1 { + public_key_x: Self::decode_box_arr_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_box_arr_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_box_arr_wrap(&v.signature, "signature")?, + hashed_message: Self::decode_box_arr_wrap( + &v.hashed_message, + "hashed_message", + )?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::MultiScalarMul(v) => Ok(opcodes::BlackBoxFuncCall::MultiScalarMul { + points: Self::decode_vec_wrap(&v.points, "points")?, + scalars: Self::decode_vec_wrap(&v.scalars, "scalars")?, + outputs: Self::decode_arr_wrap(&v.outputs, "outputs") + .map(|[w1, w2, w3]| (w1, w2, w3))?, + }), + Value::EmbeddedCurveAdd(v) => Ok(opcodes::BlackBoxFuncCall::EmbeddedCurveAdd { + input1: Self::decode_box_arr_wrap(&v.input1, "input1")?, + input2: Self::decode_box_arr_wrap(&v.input2, "input2")?, + outputs: Self::decode_arr_wrap(&v.outputs, "outputs") + .map(|[w1, w2, w3]| (w1, w2, w3))?, + }), + Value::KeccakF1600(v) => Ok(opcodes::BlackBoxFuncCall::Keccakf1600 { + inputs: Self::decode_box_arr_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }), + Value::RecursiveAggregation(v) => { + Ok(opcodes::BlackBoxFuncCall::RecursiveAggregation { + verification_key: Self::decode_vec_wrap( + &v.verification_key, + "verification_key", + )?, + proof: Self::decode_vec_wrap(&v.proof, "proof")?, + public_inputs: Self::decode_vec_wrap( + &v.public_inputs, + "public_inputs", + )?, + key_hash: Self::decode_some_wrap(&v.key_hash, "key_hash")?, + proof_type: v.proof_type, + }) + } + Value::BigIntAdd(v) => Ok(opcodes::BlackBoxFuncCall::BigIntAdd { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntSub(v) => Ok(opcodes::BlackBoxFuncCall::BigIntSub { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntMul(v) => Ok(opcodes::BlackBoxFuncCall::BigIntMul { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntDiv(v) => Ok(opcodes::BlackBoxFuncCall::BigIntDiv { + lhs: v.lhs, + rhs: v.rhs, + output: v.output, + }), + Value::BigIntFromLeBytes(v) => { + Ok(opcodes::BlackBoxFuncCall::BigIntFromLeBytes { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + modulus: v.modulus.clone(), + output: v.output, + }) + } + Value::BigIntToLeBytes(v) => Ok(opcodes::BlackBoxFuncCall::BigIntToLeBytes { + input: v.input, + outputs: Self::decode_vec_wrap(&v.outputs, "outputs")?, + }), + Value::Poseidon2Permutation(v) => { + Ok(opcodes::BlackBoxFuncCall::Poseidon2Permutation { + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + outputs: Self::decode_vec_wrap(&v.outputs, "outputs")?, + len: v.len, + }) + } + Value::Sha256Compression(v) => { + Ok(opcodes::BlackBoxFuncCall::Sha256Compression { + inputs: Self::decode_box_arr_wrap(&v.inputs, "inputs")?, + hash_values: Self::decode_box_arr_wrap(&v.hash_values, "hash_values")?, + outputs: Self::decode_box_arr_wrap(&v.outputs, "outputs")?, + }) + } + } + }, + ) + } +} + +impl ProtoCodec, FunctionInput> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::FunctionInput) -> FunctionInput { + FunctionInput { input: Self::encode_some(value.input_ref()), num_bits: value.num_bits() } + } + + fn decode(value: &FunctionInput) -> eyre::Result> { + let input = Self::decode_some_wrap(&value.input, "input")?; + + match input { + opcodes::ConstantOrWitnessEnum::Constant(c) => { + opcodes::FunctionInput::constant(c, value.num_bits).wrap_err("constant") + } + opcodes::ConstantOrWitnessEnum::Witness(w) => { + Ok(opcodes::FunctionInput::witness(w, value.num_bits)) + } + } + } +} + +impl ProtoCodec, ConstantOrWitnessEnum> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &opcodes::ConstantOrWitnessEnum) -> ConstantOrWitnessEnum { + use crate::proto::acir::circuit::constant_or_witness_enum::*; + let value = match value { + opcodes::ConstantOrWitnessEnum::Constant(field) => Value::Constant(Self::encode(field)), + opcodes::ConstantOrWitnessEnum::Witness(witness) => { + Value::Witness(Self::encode(witness)) + } + }; + ConstantOrWitnessEnum { value: Some(value) } + } + + fn decode(value: &ConstantOrWitnessEnum) -> eyre::Result> { + use crate::proto::acir::circuit::constant_or_witness_enum::*; + decode_oneof_map(&value.value, |value| match value { + Value::Constant(field) => { + Ok(opcodes::ConstantOrWitnessEnum::Constant(Self::decode_wrap(field, "constant")?)) + } + Value::Witness(witness) => { + Ok(opcodes::ConstantOrWitnessEnum::Witness(Self::decode_wrap(witness, "witness")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &opcodes::BlockType) -> BlockType { + use crate::proto::acir::circuit::block_type::*; + let value = match value { + opcodes::BlockType::Memory => Value::Memory(Memory {}), + opcodes::BlockType::CallData(value) => Value::CallData(CallData { value: *value }), + opcodes::BlockType::ReturnData => Value::ReturnData(ReturnData {}), + }; + BlockType { value: Some(value) } + } + + fn decode(value: &BlockType) -> eyre::Result { + use crate::proto::acir::circuit::block_type::*; + decode_oneof_map(&value.value, |value| match value { + Value::Memory(_) => Ok(opcodes::BlockType::Memory), + Value::CallData(v) => Ok(opcodes::BlockType::CallData(v.value)), + Value::ReturnData(_) => Ok(opcodes::BlockType::ReturnData), + }) + } +} + +impl ProtoCodec, BrilligInputs> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::brillig::BrilligInputs) -> BrilligInputs { + use crate::proto::acir::circuit::brillig_inputs::*; + let value = match value { + circuit::brillig::BrilligInputs::Single(expression) => { + Value::Single(Self::encode(expression)) + } + circuit::brillig::BrilligInputs::Array(expressions) => { + Value::Array(Array { values: Self::encode_vec(expressions) }) + } + circuit::brillig::BrilligInputs::MemoryArray(block_id) => { + Value::MemoryArray(block_id.0) + } + }; + BrilligInputs { value: Some(value) } + } + + fn decode(value: &BrilligInputs) -> eyre::Result> { + use crate::proto::acir::circuit::brillig_inputs::*; + decode_oneof_map(&value.value, |value| match value { + Value::Single(expression) => Ok(circuit::brillig::BrilligInputs::Single( + Self::decode_wrap(expression, "single")?, + )), + Value::Array(array) => Ok(circuit::brillig::BrilligInputs::Array( + Self::decode_vec_wrap(&array.values, "array")?, + )), + Value::MemoryArray(id) => { + Ok(circuit::brillig::BrilligInputs::MemoryArray(BlockId(*id))) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &circuit::brillig::BrilligOutputs) -> BrilligOutputs { + use crate::proto::acir::circuit::brillig_outputs::*; + let value = match value { + circuit::brillig::BrilligOutputs::Simple(witness) => { + Value::Simple(Self::encode(witness)) + } + circuit::brillig::BrilligOutputs::Array(witnesses) => { + Value::Array(Array { values: Self::encode_vec(witnesses) }) + } + }; + BrilligOutputs { value: Some(value) } + } + + fn decode(value: &BrilligOutputs) -> eyre::Result { + use crate::proto::acir::circuit::brillig_outputs::*; + + decode_oneof_map(&value.value, |value| match value { + Value::Simple(witness) => { + Ok(circuit::brillig::BrilligOutputs::Simple(Self::decode_wrap(witness, "simple")?)) + } + Value::Array(array) => Ok(circuit::brillig::BrilligOutputs::Array( + Self::decode_vec_wrap(&array.values, "array")?, + )), + }) + } +} diff --git a/acvm-repo/acir/src/proto/convert/brillig.rs b/acvm-repo/acir/src/proto/convert/brillig.rs new file mode 100644 index 00000000000..d07ba462782 --- /dev/null +++ b/acvm-repo/acir/src/proto/convert/brillig.rs @@ -0,0 +1,721 @@ +use crate::{ + circuit, + proto::brillig::{BitSize, BlackBoxOp, HeapArray, HeapValueType, HeapVector, ValueOrArray}, +}; +use acir_field::AcirField; +use color_eyre::eyre::{self, bail}; +use noir_protobuf::{ProtoCodec, decode_oneof_map}; + +use crate::proto::brillig::{ + BinaryFieldOpKind, BinaryIntOpKind, BrilligBytecode, BrilligOpcode, IntegerBitSize, + MemoryAddress, brillig_opcode, +}; + +use super::ProtoSchema; + +impl ProtoCodec, BrilligBytecode> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::brillig::BrilligBytecode) -> BrilligBytecode { + BrilligBytecode { bytecode: Self::encode_vec(&value.bytecode) } + } + + fn decode(value: &BrilligBytecode) -> eyre::Result> { + Ok(circuit::brillig::BrilligBytecode { + bytecode: Self::decode_vec_wrap(&value.bytecode, "bytecode")?, + }) + } +} + +impl ProtoCodec, BrilligOpcode> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &brillig::Opcode) -> BrilligOpcode { + use brillig_opcode::*; + + let value = match value { + brillig::Opcode::BinaryFieldOp { destination, op, lhs, rhs } => { + Value::BinaryFieldOp(BinaryFieldOp { + destination: Self::encode_some(destination), + op: Self::encode_enum(op), + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + }) + } + brillig::Opcode::BinaryIntOp { destination, op, bit_size, lhs, rhs } => { + Value::BinaryIntOp(BinaryIntOp { + destination: Self::encode_some(destination), + op: Self::encode_enum(op), + bit_size: Self::encode_enum(bit_size), + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + }) + } + brillig::Opcode::Not { destination, source, bit_size } => Value::Not(Not { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + bit_size: Self::encode_enum(bit_size), + }), + brillig::Opcode::Cast { destination, source, bit_size } => Value::Cast(Cast { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + bit_size: Self::encode_some(bit_size), + }), + brillig::Opcode::JumpIfNot { condition, location } => Value::JumpIfNot(JumpIfNot { + condition: Self::encode_some(condition), + location: Self::encode(location), + }), + brillig::Opcode::JumpIf { condition, location } => Value::JumpIf(JumpIf { + condition: Self::encode_some(condition), + location: Self::encode(location), + }), + brillig::Opcode::Jump { location } => { + Value::Jump(Jump { location: Self::encode(location) }) + } + brillig::Opcode::CalldataCopy { destination_address, size_address, offset_address } => { + Value::CalldataCopy(CalldataCopy { + destination_address: Self::encode_some(destination_address), + size_address: Self::encode_some(size_address), + offset_address: Self::encode_some(offset_address), + }) + } + brillig::Opcode::Call { location } => { + Value::Call(Call { location: Self::encode(location) }) + } + brillig::Opcode::Const { destination, bit_size, value } => Value::Const(Const { + destination: Self::encode_some(destination), + bit_size: Self::encode_some(bit_size), + value: Self::encode_some(value), + }), + brillig::Opcode::IndirectConst { destination_pointer, bit_size, value } => { + Value::IndirectConst(IndirectConst { + destination_pointer: Self::encode_some(destination_pointer), + bit_size: Self::encode_some(bit_size), + value: Self::encode_some(value), + }) + } + brillig::Opcode::Return => Value::Return(Return {}), + brillig::Opcode::ForeignCall { + function, + destinations, + destination_value_types, + inputs, + input_value_types, + } => Value::ForeignCall(ForeignCall { + function: function.to_string(), + destinations: Self::encode_vec(destinations), + destination_value_types: Self::encode_vec(destination_value_types), + inputs: Self::encode_vec(inputs), + input_value_types: Self::encode_vec(input_value_types), + }), + brillig::Opcode::Mov { destination, source } => Value::Mov(Mov { + destination: Self::encode_some(destination), + source: Self::encode_some(source), + }), + brillig::Opcode::ConditionalMov { destination, source_a, source_b, condition } => { + Value::ConditionalMov(ConditionalMov { + destination: Self::encode_some(destination), + source_a: Self::encode_some(source_a), + source_b: Self::encode_some(source_b), + condition: Self::encode_some(condition), + }) + } + brillig::Opcode::Load { destination, source_pointer } => Value::Load(Load { + destination: Self::encode_some(destination), + source_pointer: Self::encode_some(source_pointer), + }), + brillig::Opcode::Store { destination_pointer, source } => Value::Store(Store { + destination_pointer: Self::encode_some(destination_pointer), + source: Self::encode_some(source), + }), + brillig::Opcode::BlackBox(black_box_op) => { + Value::BlackBox(BlackBox { op: Self::encode_some(black_box_op) }) + } + brillig::Opcode::Trap { revert_data } => { + Value::Trap(Trap { revert_data: Self::encode_some(revert_data) }) + } + brillig::Opcode::Stop { return_data } => { + Value::Stop(Stop { return_data: Self::encode_some(return_data) }) + } + }; + BrilligOpcode { value: Some(value) } + } + + fn decode(value: &BrilligOpcode) -> eyre::Result> { + use brillig_opcode::*; + + decode_oneof_map(&value.value, |value| match value { + Value::BinaryFieldOp(v) => Ok(brillig::Opcode::BinaryFieldOp { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + op: Self::decode_enum_wrap(v.op, "op")?, + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + }), + Value::BinaryIntOp(v) => Ok(brillig::Opcode::BinaryIntOp { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + op: Self::decode_enum_wrap(v.op, "op")?, + bit_size: Self::decode_enum_wrap(v.bit_size, "bit_size")?, + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + }), + Value::Not(v) => Ok(brillig::Opcode::Not { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + bit_size: Self::decode_enum_wrap(v.bit_size, "bit_size")?, + }), + Value::Cast(v) => Ok(brillig::Opcode::Cast { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + }), + Value::JumpIfNot(v) => Ok(brillig::Opcode::JumpIfNot { + condition: Self::decode_some_wrap(&v.condition, "condition")?, + location: Self::decode_wrap(&v.location, "location")?, + }), + Value::JumpIf(v) => Ok(brillig::Opcode::JumpIf { + condition: Self::decode_some_wrap(&v.condition, "condition")?, + location: Self::decode_wrap(&v.location, "location")?, + }), + Value::Jump(v) => { + Ok(brillig::Opcode::Jump { location: Self::decode_wrap(&v.location, "location")? }) + } + Value::CalldataCopy(v) => Ok(brillig::Opcode::CalldataCopy { + destination_address: Self::decode_some_wrap( + &v.destination_address, + "destination_address", + )?, + size_address: Self::decode_some_wrap(&v.size_address, "size_address")?, + offset_address: Self::decode_some_wrap(&v.offset_address, "offset_address")?, + }), + Value::Call(v) => { + Ok(brillig::Opcode::Call { location: Self::decode_wrap(&v.location, "location")? }) + } + Value::Const(v) => Ok(brillig::Opcode::Const { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + value: Self::decode_some_wrap(&v.value, "value")?, + }), + Value::IndirectConst(v) => Ok(brillig::Opcode::IndirectConst { + destination_pointer: Self::decode_some_wrap( + &v.destination_pointer, + "destination_pointer", + )?, + bit_size: Self::decode_some_wrap(&v.bit_size, "bit_size")?, + value: Self::decode_some_wrap(&v.value, "value")?, + }), + Value::Return(_) => Ok(brillig::Opcode::Return), + Value::ForeignCall(v) => Ok(brillig::Opcode::ForeignCall { + function: v.function.clone(), + destinations: Self::decode_vec_wrap(&v.destinations, "destinations")?, + destination_value_types: Self::decode_vec_wrap( + &v.destination_value_types, + "destination_value_types", + )?, + inputs: Self::decode_vec_wrap(&v.inputs, "inputs")?, + input_value_types: Self::decode_vec_wrap( + &v.input_value_types, + "input_value_types", + )?, + }), + Value::Mov(v) => Ok(brillig::Opcode::Mov { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source: Self::decode_some_wrap(&v.source, "source")?, + }), + Value::ConditionalMov(v) => Ok(brillig::Opcode::ConditionalMov { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source_a: Self::decode_some_wrap(&v.source_a, "source_a")?, + source_b: Self::decode_some_wrap(&v.source_b, "source_b")?, + condition: Self::decode_some_wrap(&v.condition, "condition")?, + }), + Value::Load(v) => Ok(brillig::Opcode::Load { + destination: Self::decode_some_wrap(&v.destination, "destination")?, + source_pointer: Self::decode_some_wrap(&v.source_pointer, "source_pointer")?, + }), + Value::Store(v) => Ok(brillig::Opcode::Store { + destination_pointer: Self::decode_some_wrap( + &v.destination_pointer, + "destination_pointer", + )?, + source: Self::decode_some_wrap(&v.source, "source")?, + }), + Value::BlackBox(v) => { + Ok(brillig::Opcode::BlackBox(Self::decode_some_wrap(&v.op, "black_box")?)) + } + Value::Trap(v) => Ok(brillig::Opcode::Trap { + revert_data: Self::decode_some_wrap(&v.revert_data, "revert_data")?, + }), + Value::Stop(v) => Ok(brillig::Opcode::Stop { + return_data: Self::decode_some_wrap(&v.return_data, "return_data")?, + }), + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::MemoryAddress) -> MemoryAddress { + use crate::proto::brillig::memory_address::*; + let value = match value { + brillig::MemoryAddress::Direct(addr) => Value::Direct(Self::encode(addr)), + brillig::MemoryAddress::Relative(addr) => Value::Relative(Self::encode(addr)), + }; + MemoryAddress { value: Some(value) } + } + + fn decode(value: &MemoryAddress) -> eyre::Result { + use crate::proto::brillig::memory_address::*; + decode_oneof_map(&value.value, |value| match value { + Value::Direct(v) => Self::decode_wrap(v, "direct").map(brillig::MemoryAddress::Direct), + Value::Relative(v) => { + Self::decode_wrap(v, "relative").map(brillig::MemoryAddress::Relative) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BinaryFieldOp) -> BinaryFieldOpKind { + match value { + brillig::BinaryFieldOp::Add => BinaryFieldOpKind::BfoAdd, + brillig::BinaryFieldOp::Sub => BinaryFieldOpKind::BfoSub, + brillig::BinaryFieldOp::Mul => BinaryFieldOpKind::BfoMul, + brillig::BinaryFieldOp::Div => BinaryFieldOpKind::BfoDiv, + brillig::BinaryFieldOp::IntegerDiv => BinaryFieldOpKind::BfoIntegerDiv, + brillig::BinaryFieldOp::Equals => BinaryFieldOpKind::BfoEquals, + brillig::BinaryFieldOp::LessThan => BinaryFieldOpKind::BfoLessThan, + brillig::BinaryFieldOp::LessThanEquals => BinaryFieldOpKind::BfoLessThanEquals, + } + } + + fn decode(value: &BinaryFieldOpKind) -> eyre::Result { + match value { + BinaryFieldOpKind::BfoUnspecified => bail!("unspecified BinaryFieldOp"), + BinaryFieldOpKind::BfoAdd => Ok(brillig::BinaryFieldOp::Add), + BinaryFieldOpKind::BfoSub => Ok(brillig::BinaryFieldOp::Sub), + BinaryFieldOpKind::BfoMul => Ok(brillig::BinaryFieldOp::Mul), + BinaryFieldOpKind::BfoDiv => Ok(brillig::BinaryFieldOp::Div), + BinaryFieldOpKind::BfoIntegerDiv => Ok(brillig::BinaryFieldOp::IntegerDiv), + BinaryFieldOpKind::BfoEquals => Ok(brillig::BinaryFieldOp::Equals), + BinaryFieldOpKind::BfoLessThan => Ok(brillig::BinaryFieldOp::LessThan), + BinaryFieldOpKind::BfoLessThanEquals => Ok(brillig::BinaryFieldOp::LessThanEquals), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BinaryIntOp) -> BinaryIntOpKind { + match value { + brillig::BinaryIntOp::Add => BinaryIntOpKind::BioAdd, + brillig::BinaryIntOp::Sub => BinaryIntOpKind::BioSub, + brillig::BinaryIntOp::Mul => BinaryIntOpKind::BioMul, + brillig::BinaryIntOp::Div => BinaryIntOpKind::BioDiv, + brillig::BinaryIntOp::Equals => BinaryIntOpKind::BioEquals, + brillig::BinaryIntOp::LessThan => BinaryIntOpKind::BioLessThan, + brillig::BinaryIntOp::LessThanEquals => BinaryIntOpKind::BioLessThanEquals, + brillig::BinaryIntOp::And => BinaryIntOpKind::BioAnd, + brillig::BinaryIntOp::Or => BinaryIntOpKind::BioOr, + brillig::BinaryIntOp::Xor => BinaryIntOpKind::BioXor, + brillig::BinaryIntOp::Shl => BinaryIntOpKind::BioShl, + brillig::BinaryIntOp::Shr => BinaryIntOpKind::BioShr, + } + } + + fn decode(value: &BinaryIntOpKind) -> eyre::Result { + match value { + BinaryIntOpKind::BioUnspecified => bail!("unspecified BinaryIntOp"), + BinaryIntOpKind::BioAdd => Ok(brillig::BinaryIntOp::Add), + BinaryIntOpKind::BioSub => Ok(brillig::BinaryIntOp::Sub), + BinaryIntOpKind::BioMul => Ok(brillig::BinaryIntOp::Mul), + BinaryIntOpKind::BioDiv => Ok(brillig::BinaryIntOp::Div), + BinaryIntOpKind::BioEquals => Ok(brillig::BinaryIntOp::Equals), + BinaryIntOpKind::BioLessThan => Ok(brillig::BinaryIntOp::LessThan), + BinaryIntOpKind::BioLessThanEquals => Ok(brillig::BinaryIntOp::LessThanEquals), + BinaryIntOpKind::BioAnd => Ok(brillig::BinaryIntOp::And), + BinaryIntOpKind::BioOr => Ok(brillig::BinaryIntOp::Or), + BinaryIntOpKind::BioXor => Ok(brillig::BinaryIntOp::Xor), + BinaryIntOpKind::BioShl => Ok(brillig::BinaryIntOp::Shl), + BinaryIntOpKind::BioShr => Ok(brillig::BinaryIntOp::Shr), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::IntegerBitSize) -> IntegerBitSize { + match value { + brillig::IntegerBitSize::U1 => IntegerBitSize::IbsU1, + brillig::IntegerBitSize::U8 => IntegerBitSize::IbsU8, + brillig::IntegerBitSize::U16 => IntegerBitSize::IbsU16, + brillig::IntegerBitSize::U32 => IntegerBitSize::IbsU32, + brillig::IntegerBitSize::U64 => IntegerBitSize::IbsU64, + brillig::IntegerBitSize::U128 => IntegerBitSize::IbsU128, + } + } + + fn decode(value: &IntegerBitSize) -> eyre::Result { + match value { + IntegerBitSize::IbsUnspecified => bail!("unspecified IntegerBitSize"), + IntegerBitSize::IbsU1 => Ok(brillig::IntegerBitSize::U1), + IntegerBitSize::IbsU8 => Ok(brillig::IntegerBitSize::U8), + IntegerBitSize::IbsU16 => Ok(brillig::IntegerBitSize::U16), + IntegerBitSize::IbsU32 => Ok(brillig::IntegerBitSize::U32), + IntegerBitSize::IbsU64 => Ok(brillig::IntegerBitSize::U64), + IntegerBitSize::IbsU128 => Ok(brillig::IntegerBitSize::U128), + } + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BitSize) -> BitSize { + use crate::proto::brillig::bit_size::*; + let value = match value { + brillig::BitSize::Field => Value::Field(Field {}), + brillig::BitSize::Integer(integer_bit_size) => { + Value::Integer(Self::encode_enum(integer_bit_size)) + } + }; + BitSize { value: Some(value) } + } + + fn decode(value: &BitSize) -> eyre::Result { + use crate::proto::brillig::bit_size::*; + decode_oneof_map(&value.value, |value| match value { + Value::Field(_) => Ok(brillig::BitSize::Field), + Value::Integer(size) => { + Ok(brillig::BitSize::Integer(Self::decode_enum_wrap(*size, "size")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::ValueOrArray) -> ValueOrArray { + use crate::proto::brillig::value_or_array::*; + let value = match value { + brillig::ValueOrArray::MemoryAddress(memory_address) => { + Value::MemoryAddress(Self::encode(memory_address)) + } + brillig::ValueOrArray::HeapArray(heap_array) => { + Value::HeapArray(Self::encode(heap_array)) + } + brillig::ValueOrArray::HeapVector(heap_vector) => { + Value::HeapVector(Self::encode(heap_vector)) + } + }; + ValueOrArray { value: Some(value) } + } + + fn decode(value: &ValueOrArray) -> eyre::Result { + use crate::proto::brillig::value_or_array::*; + decode_oneof_map(&value.value, |value| match value { + Value::MemoryAddress(v) => { + Ok(brillig::ValueOrArray::MemoryAddress(Self::decode_wrap(v, "memory_address")?)) + } + Value::HeapArray(v) => { + Ok(brillig::ValueOrArray::HeapArray(Self::decode_wrap(v, "heap_array")?)) + } + Value::HeapVector(v) => { + Ok(brillig::ValueOrArray::HeapVector(Self::decode_wrap(v, "heap_vector")?)) + } + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapValueType) -> HeapValueType { + use crate::proto::brillig::heap_value_type::*; + let value = match value { + brillig::HeapValueType::Simple(bit_size) => Value::Simple(Self::encode(bit_size)), + brillig::HeapValueType::Array { value_types, size } => Value::Array(Array { + value_types: Self::encode_vec(value_types), + size: *size as u64, + }), + brillig::HeapValueType::Vector { value_types } => { + Value::Vector(Vector { value_types: Self::encode_vec(value_types) }) + } + }; + HeapValueType { value: Some(value) } + } + + fn decode(value: &HeapValueType) -> eyre::Result { + use crate::proto::brillig::heap_value_type::*; + decode_oneof_map(&value.value, |value| match value { + Value::Simple(bit_size) => { + Ok(brillig::HeapValueType::Simple(Self::decode_wrap(bit_size, "simple")?)) + } + Value::Array(v) => Ok(brillig::HeapValueType::Array { + value_types: Self::decode_vec_wrap(&v.value_types, "value_types")?, + size: Self::decode_wrap(&v.size, "size")?, + }), + Value::Vector(v) => Ok(brillig::HeapValueType::Vector { + value_types: Self::decode_vec_wrap(&v.value_types, "value_types")?, + }), + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapArray) -> HeapArray { + HeapArray { pointer: Self::encode_some(&value.pointer), size: Self::encode(&value.size) } + } + + fn decode(value: &HeapArray) -> eyre::Result { + Ok(brillig::HeapArray { + pointer: Self::decode_some_wrap(&value.pointer, "pointer")?, + size: Self::decode_wrap(&value.size, "size")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::HeapVector) -> HeapVector { + HeapVector { + pointer: Self::encode_some(&value.pointer), + size: Self::encode_some(&value.size), + } + } + + fn decode(value: &HeapVector) -> eyre::Result { + Ok(brillig::HeapVector { + pointer: Self::decode_some_wrap(&value.pointer, "pointer")?, + size: Self::decode_some_wrap(&value.size, "size")?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &brillig::BlackBoxOp) -> BlackBoxOp { + use crate::proto::brillig::black_box_op::*; + let value = match value { + brillig::BlackBoxOp::AES128Encrypt { inputs, iv, key, outputs } => { + Value::Aes128Encrypt(Aes128Encrypt { + inputs: Self::encode_some(inputs), + iv: Self::encode_some(iv), + key: Self::encode_some(key), + outputs: Self::encode_some(outputs), + }) + } + brillig::BlackBoxOp::Blake2s { message, output } => Value::Blake2s(Blake2s { + message: Self::encode_some(message), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::Blake3 { message, output } => Value::Blake3(Blake3 { + message: Self::encode_some(message), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::Keccakf1600 { input, output } => Value::KeccakF1600(Keccakf1600 { + input: Self::encode_some(input), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::EcdsaSecp256k1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result, + } => Value::EcdsaSecp256k1(EcdsaSecp256k1 { + hashed_msg: Self::encode_some(hashed_msg), + public_key_x: Self::encode_some(public_key_x), + public_key_y: Self::encode_some(public_key_y), + signature: Self::encode_some(signature), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::EcdsaSecp256r1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result, + } => Value::EcdsaSecp256r1(EcdsaSecp256r1 { + hashed_msg: Self::encode_some(hashed_msg), + public_key_x: Self::encode_some(public_key_x), + public_key_y: Self::encode_some(public_key_y), + signature: Self::encode_some(signature), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::MultiScalarMul { points, scalars, outputs } => { + Value::MultiScalarMul(MultiScalarMul { + points: Self::encode_some(points), + scalars: Self::encode_some(scalars), + outputs: Self::encode_some(outputs), + }) + } + brillig::BlackBoxOp::EmbeddedCurveAdd { + input1_x, + input1_y, + input1_infinite, + input2_x, + input2_y, + input2_infinite, + result, + } => Value::EmbeddedCurveAdd(EmbeddedCurveAdd { + input1_x: Self::encode_some(input1_x), + input1_y: Self::encode_some(input1_y), + input1_infinite: Self::encode_some(input1_infinite), + input2_x: Self::encode_some(input2_x), + input2_y: Self::encode_some(input2_y), + input2_infinite: Self::encode_some(input2_infinite), + result: Self::encode_some(result), + }), + brillig::BlackBoxOp::BigIntAdd { lhs, rhs, output } => Value::BigIntAdd(BigIntAdd { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntSub { lhs, rhs, output } => Value::BigIntSub(BigIntSub { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntMul { lhs, rhs, output } => Value::BigIntMul(BigIntMul { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntDiv { lhs, rhs, output } => Value::BigIntDiv(BigIntDiv { + lhs: Self::encode_some(lhs), + rhs: Self::encode_some(rhs), + output: Self::encode_some(output), + }), + brillig::BlackBoxOp::BigIntFromLeBytes { inputs, modulus, output } => { + Value::BigIntFromLeBytes(BigIntFromLeBytes { + inputs: Self::encode_some(inputs), + modulus: Self::encode_some(modulus), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::BigIntToLeBytes { input, output } => { + Value::BigIntToLeBytes(BigIntToLeBytes { + input: Self::encode_some(input), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::Poseidon2Permutation { message, output, len } => { + Value::Poseidon2Permutation(Poseidon2Permutation { + message: Self::encode_some(message), + output: Self::encode_some(output), + len: Self::encode_some(len), + }) + } + brillig::BlackBoxOp::Sha256Compression { input, hash_values, output } => { + Value::Sha256Compression(Sha256Compression { + input: Self::encode_some(input), + hash_values: Self::encode_some(hash_values), + output: Self::encode_some(output), + }) + } + brillig::BlackBoxOp::ToRadix { + input, + radix, + output_pointer, + num_limbs, + output_bits, + } => Value::ToRadix(ToRadix { + input: Self::encode_some(input), + radix: Self::encode_some(radix), + output_pointer: Self::encode_some(output_pointer), + num_limbs: Self::encode_some(num_limbs), + output_bits: Self::encode_some(output_bits), + }), + }; + BlackBoxOp { value: Some(value) } + } + + fn decode(value: &BlackBoxOp) -> eyre::Result { + use crate::proto::brillig::black_box_op::*; + decode_oneof_map(&value.value, |value| match value { + Value::Aes128Encrypt(v) => Ok(brillig::BlackBoxOp::AES128Encrypt { + inputs: Self::decode_some_wrap(&v.inputs, "inputs")?, + iv: Self::decode_some_wrap(&v.iv, "iv")?, + key: Self::decode_some_wrap(&v.key, "key")?, + outputs: Self::decode_some_wrap(&v.outputs, "outputs")?, + }), + Value::Blake2s(v) => Ok(brillig::BlackBoxOp::Blake2s { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Blake3(v) => Ok(brillig::BlackBoxOp::Blake3 { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::KeccakF1600(v) => Ok(brillig::BlackBoxOp::Keccakf1600 { + input: Self::decode_some_wrap(&v.input, "input")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::EcdsaSecp256k1(v) => Ok(brillig::BlackBoxOp::EcdsaSecp256k1 { + hashed_msg: Self::decode_some_wrap(&v.hashed_msg, "hashed_msg")?, + public_key_x: Self::decode_some_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_some_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_some_wrap(&v.signature, "signature")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::EcdsaSecp256r1(v) => Ok(brillig::BlackBoxOp::EcdsaSecp256r1 { + hashed_msg: Self::decode_some_wrap(&v.hashed_msg, "hashed_msg")?, + public_key_x: Self::decode_some_wrap(&v.public_key_x, "public_key_x")?, + public_key_y: Self::decode_some_wrap(&v.public_key_y, "public_key_y")?, + signature: Self::decode_some_wrap(&v.signature, "signature")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::MultiScalarMul(v) => Ok(brillig::BlackBoxOp::MultiScalarMul { + points: Self::decode_some_wrap(&v.points, "points")?, + scalars: Self::decode_some_wrap(&v.scalars, "scalars")?, + outputs: Self::decode_some_wrap(&v.outputs, "outputs")?, + }), + Value::EmbeddedCurveAdd(v) => Ok(brillig::BlackBoxOp::EmbeddedCurveAdd { + input1_x: Self::decode_some_wrap(&v.input1_x, "input1_x")?, + input1_y: Self::decode_some_wrap(&v.input1_y, "input1_y")?, + input1_infinite: Self::decode_some_wrap(&v.input1_infinite, "input1_infinite")?, + input2_x: Self::decode_some_wrap(&v.input2_x, "input2_x")?, + input2_y: Self::decode_some_wrap(&v.input2_y, "input2_y")?, + input2_infinite: Self::decode_some_wrap(&v.input2_infinite, "input2_infinite")?, + result: Self::decode_some_wrap(&v.result, "result")?, + }), + Value::BigIntAdd(v) => Ok(brillig::BlackBoxOp::BigIntAdd { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntSub(v) => Ok(brillig::BlackBoxOp::BigIntSub { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntMul(v) => Ok(brillig::BlackBoxOp::BigIntMul { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntDiv(v) => Ok(brillig::BlackBoxOp::BigIntDiv { + lhs: Self::decode_some_wrap(&v.lhs, "lhs")?, + rhs: Self::decode_some_wrap(&v.rhs, "rhs")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntFromLeBytes(v) => Ok(brillig::BlackBoxOp::BigIntFromLeBytes { + inputs: Self::decode_some_wrap(&v.inputs, "inputs")?, + modulus: Self::decode_some_wrap(&v.modulus, "modulus")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::BigIntToLeBytes(v) => Ok(brillig::BlackBoxOp::BigIntToLeBytes { + input: Self::decode_some_wrap(&v.input, "input")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::Poseidon2Permutation(v) => Ok(brillig::BlackBoxOp::Poseidon2Permutation { + message: Self::decode_some_wrap(&v.message, "message")?, + output: Self::decode_some_wrap(&v.output, "output")?, + len: Self::decode_some_wrap(&v.len, "len")?, + }), + Value::Sha256Compression(v) => Ok(brillig::BlackBoxOp::Sha256Compression { + input: Self::decode_some_wrap(&v.input, "input")?, + hash_values: Self::decode_some_wrap(&v.hash_values, "hash_values")?, + output: Self::decode_some_wrap(&v.output, "output")?, + }), + Value::ToRadix(v) => Ok(brillig::BlackBoxOp::ToRadix { + input: Self::decode_some_wrap(&v.input, "input")?, + radix: Self::decode_some_wrap(&v.radix, "radix")?, + output_pointer: Self::decode_some_wrap(&v.output_pointer, "output_pointer")?, + num_limbs: Self::decode_some_wrap(&v.num_limbs, "num_limbs")?, + output_bits: Self::decode_some_wrap(&v.output_bits, "output_bits")?, + }), + }) + } +} diff --git a/acvm-repo/acir/src/proto/convert/mod.rs b/acvm-repo/acir/src/proto/convert/mod.rs new file mode 100644 index 00000000000..9d68ba665e1 --- /dev/null +++ b/acvm-repo/acir/src/proto/convert/mod.rs @@ -0,0 +1,49 @@ +use std::marker::PhantomData; + +use acir_field::AcirField; +use color_eyre::eyre::{self, Context}; +use noir_protobuf::ProtoCodec; + +use crate::circuit; +use crate::proto::program::Program; + +mod acir; +mod brillig; +mod native; +mod witness; + +pub(crate) struct ProtoSchema { + field: PhantomData, +} + +impl ProtoCodec, Program> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &circuit::Program) -> Program { + Program { + functions: Self::encode_vec(&value.functions), + unconstrained_functions: Self::encode_vec(&value.unconstrained_functions), + } + } + + fn decode(value: &Program) -> eyre::Result> { + Ok(circuit::Program { + functions: Self::decode_vec_wrap(&value.functions, "functions")?, + unconstrained_functions: Self::decode_vec_wrap( + &value.unconstrained_functions, + "unconstrained_functions", + )?, + }) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &usize) -> u64 { + *value as u64 + } + + fn decode(value: &u64) -> eyre::Result { + (*value).try_into().wrap_err("failed to convert u64 to usize") + } +} diff --git a/acvm-repo/acir/src/proto/convert/native.rs b/acvm-repo/acir/src/proto/convert/native.rs new file mode 100644 index 00000000000..5d34ecf14f9 --- /dev/null +++ b/acvm-repo/acir/src/proto/convert/native.rs @@ -0,0 +1,80 @@ +use acir_field::AcirField; +use color_eyre::eyre; +use noir_protobuf::{ProtoCodec, decode_vec_map_wrap}; + +use crate::{ + native_types, + proto::acir::native::{Expression, Field, Witness}, +}; + +use super::ProtoSchema; + +impl ProtoCodec for ProtoSchema { + fn encode(value: &F) -> Field { + Field { value: value.to_le_bytes() } + } + + fn decode(value: &Field) -> eyre::Result { + Ok(F::from_le_bytes_reduce(&value.value)) + } +} + +impl ProtoCodec for ProtoSchema { + fn encode(value: &native_types::Witness) -> Witness { + Witness { index: value.0 } + } + + fn decode(value: &Witness) -> eyre::Result { + Ok(native_types::Witness(value.index)) + } +} + +impl ProtoCodec, Expression> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::Expression) -> Expression { + use crate::proto::acir::native::expression::*; + Expression { + mul_terms: value + .mul_terms + .iter() + .map(|(q_m, wl, wr)| MulTerm { + q_m: Self::encode_some(q_m), + witness_left: Self::encode_some(wl), + witness_right: Self::encode_some(wr), + }) + .collect(), + linear_combinations: value + .linear_combinations + .iter() + .map(|(q_l, w)| LinearCombination { + q_l: Self::encode_some(q_l), + witness: Self::encode_some(w), + }) + .collect(), + q_c: Self::encode_some(&value.q_c), + } + } + + fn decode(value: &Expression) -> eyre::Result> { + Ok(native_types::Expression { + mul_terms: decode_vec_map_wrap(&value.mul_terms, "mul_terms", |mt| { + let q_m = Self::decode_some_wrap(&mt.q_m, "q_m")?; + let wl = Self::decode_some_wrap(&mt.witness_left, "witness_left")?; + let wr = Self::decode_some_wrap(&mt.witness_right, "witness_right")?; + Ok((q_m, wl, wr)) + })?, + linear_combinations: decode_vec_map_wrap( + &value.linear_combinations, + "linear_combinations", + |lc| { + let q_l = Self::decode_some_wrap(&lc.q_l, "q_l")?; + let w = Self::decode_some_wrap(&lc.witness, "witness")?; + Ok((q_l, w)) + }, + )?, + q_c: Self::decode_some_wrap(&value.q_c, "q_c")?, + }) + } +} diff --git a/acvm-repo/acir/src/proto/convert/witness.rs b/acvm-repo/acir/src/proto/convert/witness.rs new file mode 100644 index 00000000000..e926b9ea00b --- /dev/null +++ b/acvm-repo/acir/src/proto/convert/witness.rs @@ -0,0 +1,64 @@ +use acir_field::AcirField; +use noir_protobuf::ProtoCodec; + +use crate::native_types; +use crate::proto::acir::witness::{WitnessMap, WitnessStack}; + +use super::ProtoSchema; + +impl ProtoCodec, WitnessMap> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::WitnessMap) -> WitnessMap { + use crate::proto::acir::witness::witness_map::*; + + let values = value + .clone() + .into_iter() + .map(|(w, f)| WitnessValue { + witness: Self::encode_some(&w), + field: Self::encode_some(&f), + }) + .collect(); + + WitnessMap { values } + } + + fn decode(value: &WitnessMap) -> color_eyre::eyre::Result> { + let mut wm = native_types::WitnessMap::default(); + for wv in &value.values { + wm.insert( + Self::decode_some_wrap(&wv.witness, "witness")?, + Self::decode_some_wrap(&wv.field, "field")?, + ); + } + Ok(wm) + } +} + +impl ProtoCodec, WitnessStack> for ProtoSchema +where + F: AcirField, +{ + fn encode(value: &native_types::WitnessStack) -> WitnessStack { + use crate::proto::acir::witness::witness_stack::*; + + let mut value = value.clone(); + let mut stack = Vec::new(); + while let Some(item) = value.pop() { + stack.push(StackItem { index: item.index, witness: Self::encode_some(&item.witness) }); + } + stack.reverse(); + + WitnessStack { stack } + } + + fn decode(value: &WitnessStack) -> color_eyre::eyre::Result> { + let mut ws = native_types::WitnessStack::default(); + for item in &value.stack { + ws.push(item.index, Self::decode_some_wrap(&item.witness, "witness")?); + } + Ok(ws) + } +} diff --git a/acvm-repo/acir/src/proto/mod.rs b/acvm-repo/acir/src/proto/mod.rs new file mode 100644 index 00000000000..ba5bbc5108c --- /dev/null +++ b/acvm-repo/acir/src/proto/mod.rs @@ -0,0 +1,29 @@ +pub(crate) mod convert; + +pub(crate) mod acir { + + #[allow(unreachable_pub)] + pub(crate) mod native { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.native.rs")); + } + + #[allow(unreachable_pub)] + pub(crate) mod witness { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.witness.rs")); + } + + #[allow(unreachable_pub)] + pub(crate) mod circuit { + include!(concat!(env!("OUT_DIR"), "/acvm.acir.circuit.rs")); + } +} + +#[allow(unreachable_pub, clippy::enum_variant_names)] +pub(crate) mod brillig { + include!(concat!(env!("OUT_DIR"), "/acvm.brillig.rs")); +} + +#[allow(unreachable_pub)] +pub(crate) mod program { + include!(concat!(env!("OUT_DIR"), "/acvm.program.rs")); +} diff --git a/acvm-repo/acir/src/proto/program.proto b/acvm-repo/acir/src/proto/program.proto new file mode 100644 index 00000000000..d54d0aff5a9 --- /dev/null +++ b/acvm-repo/acir/src/proto/program.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package acvm.program; + +import public "acir/circuit.proto"; +import public "brillig.proto"; + +// A program represents an entire circuit with ACIR and Brillig functions and +// potentially multiple endpoints. +message Program { + // ACIR circuits + repeated acvm.acir.circuit.Circuit functions = 1; + // Brillig functions + repeated acvm.brillig.BrilligBytecode unconstrained_functions = 2; +} diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index 4ff571106a1..4f9a5fb76c5 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -56,7 +56,10 @@ fn addition_circuit() { 135, 223, 13, 27, 135, 121, 106, 119, 3, 58, 173, 124, 163, 140, 1, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -99,7 +102,10 @@ fn multi_scalar_mul_circuit() { 179, 90, 23, 212, 196, 199, 187, 192, 0, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -173,7 +179,10 @@ fn simple_brillig_foreign_call() { 191, 40, 237, 37, 127, 1, 190, 36, 121, 0, 128, 254, 118, 42, 127, 2, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -323,7 +332,10 @@ fn complex_brillig_foreign_call() { 250, 76, 4, 233, 188, 7, 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -365,7 +377,10 @@ fn memory_op_circuit() { 0, 0, ]; - assert_eq!(bytes, expected_serialization) + assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } #[test] @@ -472,4 +487,7 @@ fn nested_acir_call_circuit() { 253, 11, 4, 0, 0, ]; assert_eq!(bytes, expected_serialization); + + let program_de = Program::deserialize_program(&bytes).unwrap(); + assert_eq!(program_de, program); } diff --git a/acvm-repo/acir_field/src/generic_ark.rs b/acvm-repo/acir_field/src/generic_ark.rs index 04761dd1ed0..3f2b27b864c 100644 --- a/acvm-repo/acir_field/src/generic_ark.rs +++ b/acvm-repo/acir_field/src/generic_ark.rs @@ -25,6 +25,7 @@ pub trait AcirField: + From + std::hash::Hash + Eq + + 'static { fn one() -> Self; fn zero() -> Self; @@ -85,3 +86,198 @@ pub trait AcirField: /// This method truncates fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec; } + +/// Define a _newtype_ wrapper around an `AcirField` by implementing all the +/// boilerplate for forwarding the field operations. +/// +/// This allows the wrapper to implement traits such as `Arbitrary`, and then +/// be used by code that is generic in `F: AcirField`. +/// +/// # Example +/// ```ignore +/// field_wrapper!(TestField, FieldElement); +/// ``` +#[macro_export] +macro_rules! field_wrapper { + ($wrapper:ident, $field:ident) => { + #[derive( + Clone, + Debug, + PartialEq, + Eq, + Hash, + PartialOrd, + Copy, + Default, + serde::Serialize, + serde::Deserialize, + )] + struct $wrapper(pub $field); + + impl $crate::AcirField for $wrapper { + fn one() -> Self { + Self($field::one()) + } + + fn zero() -> Self { + Self($field::zero()) + } + + fn is_zero(&self) -> bool { + self.0.is_zero() + } + + fn is_one(&self) -> bool { + self.0.is_one() + } + + fn pow(&self, exponent: &Self) -> Self { + Self(self.0.pow(&exponent.0)) + } + + fn max_num_bits() -> u32 { + $field::max_num_bits() + } + + fn max_num_bytes() -> u32 { + $field::max_num_bytes() + } + + fn modulus() -> num_bigint::BigUint { + $field::modulus() + } + + fn num_bits(&self) -> u32 { + self.0.num_bits() + } + + fn to_u128(self) -> u128 { + self.0.to_u128() + } + + fn try_into_u128(self) -> Option { + self.0.try_into_u128() + } + + fn to_i128(self) -> i128 { + self.0.to_i128() + } + + fn try_to_u64(&self) -> Option { + self.0.try_to_u64() + } + + fn try_to_u32(&self) -> Option { + self.0.try_to_u32() + } + + fn inverse(&self) -> Self { + Self(self.0.inverse()) + } + + fn to_hex(self) -> String { + self.0.to_hex() + } + + fn from_hex(hex_str: &str) -> Option { + $field::from_hex(hex_str).map(Self) + } + + fn to_be_bytes(self) -> Vec { + self.0.to_be_bytes() + } + + fn from_be_bytes_reduce(bytes: &[u8]) -> Self { + Self($field::from_be_bytes_reduce(bytes)) + } + + fn from_le_bytes_reduce(bytes: &[u8]) -> Self { + Self($field::from_le_bytes_reduce(bytes)) + } + + fn to_le_bytes(self) -> Vec { + self.0.to_le_bytes() + } + + fn fetch_nearest_bytes(&self, num_bits: usize) -> Vec { + self.0.fetch_nearest_bytes(num_bits) + } + } + + impl From for $wrapper { + fn from(value: bool) -> Self { + Self($field::from(value)) + } + } + + impl From for $wrapper { + fn from(value: u128) -> Self { + Self($field::from(value)) + } + } + + impl From for $wrapper { + fn from(value: usize) -> Self { + Self($field::from(value)) + } + } + + impl std::ops::SubAssign<$wrapper> for $wrapper { + fn sub_assign(&mut self, rhs: $wrapper) { + self.0.sub_assign(rhs.0); + } + } + + impl std::ops::AddAssign<$wrapper> for $wrapper { + fn add_assign(&mut self, rhs: $wrapper) { + self.0.add_assign(rhs.0); + } + } + + impl std::ops::Add<$wrapper> for $wrapper { + type Output = Self; + + fn add(self, rhs: $wrapper) -> Self::Output { + Self(self.0.add(rhs.0)) + } + } + + impl std::ops::Sub<$wrapper> for $wrapper { + type Output = Self; + + fn sub(self, rhs: $wrapper) -> Self::Output { + Self(self.0.sub(rhs.0)) + } + } + + impl std::ops::Mul<$wrapper> for $wrapper { + type Output = Self; + + fn mul(self, rhs: $wrapper) -> Self::Output { + Self(self.0.mul(rhs.0)) + } + } + + impl std::ops::Div<$wrapper> for $wrapper { + type Output = Self; + + fn div(self, rhs: $wrapper) -> Self::Output { + Self(self.0.div(rhs.0)) + } + } + + impl std::ops::Neg for $wrapper { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.neg()) + } + } + + impl std::fmt::Display for $wrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + }; +} diff --git a/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs b/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs index 2590c5f208a..0e0d22edcd8 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs @@ -12,7 +12,7 @@ use acir::{ use crate::compiler::CircuitSimulator; -pub(crate) struct MergeExpressionsOptimizer { +pub(crate) struct MergeExpressionsOptimizer { resolved_blocks: HashMap>, modified_gates: HashMap>, deleted_gates: BTreeSet, diff --git a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs index 67dce75411e..120a963192e 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/redundant_range.rs @@ -27,7 +27,7 @@ use std::collections::{BTreeMap, HashSet}; /// /// This optimization pass will keep the 16-bit range constraint /// and remove the 32-bit range constraint opcode. -pub(crate) struct RangeOptimizer { +pub(crate) struct RangeOptimizer { /// Maps witnesses to their lowest known bit sizes. lists: BTreeMap, circuit: Circuit, diff --git a/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs b/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs index 8b7e52d66f2..3a256aafe63 100644 --- a/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs +++ b/acvm-repo/acvm/src/compiler/optimizers/unused_memory.rs @@ -1,13 +1,16 @@ -use acir::circuit::{Circuit, Opcode, brillig::BrilligInputs, opcodes::BlockId}; +use acir::{ + AcirField, + circuit::{Circuit, Opcode, brillig::BrilligInputs, opcodes::BlockId}, +}; use std::collections::HashSet; /// `UnusedMemoryOptimizer` will remove initializations of memory blocks which are unused. -pub(crate) struct UnusedMemoryOptimizer { +pub(crate) struct UnusedMemoryOptimizer { unused_memory_initializations: HashSet, circuit: Circuit, } -impl UnusedMemoryOptimizer { +impl UnusedMemoryOptimizer { /// Creates a new `UnusedMemoryOptimizer ` by collecting unused memory init /// opcodes from `Circuit`. pub(crate) fn new(circuit: Circuit) -> Self { diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index f5d56df17c1..3c66b08eb4c 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -178,7 +178,7 @@ pub struct ProfilingSample { pub brillig_function_id: Option, } -pub struct ACVM<'a, F, B: BlackBoxFunctionSolver> { +pub struct ACVM<'a, F: AcirField, B: BlackBoxFunctionSolver> { status: ACVMStatus, backend: &'a B, diff --git a/acvm-repo/brillig/Cargo.toml b/acvm-repo/brillig/Cargo.toml index 5a9720238ac..0c052d8f700 100644 --- a/acvm-repo/brillig/Cargo.toml +++ b/acvm-repo/brillig/Cargo.toml @@ -18,7 +18,11 @@ workspace = true [dependencies] acir_field.workspace = true serde.workspace = true +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } [features] +default = [] bn254 = ["acir_field/bn254"] bls12_381 = ["acir_field/bls12_381"] +arb = ["proptest", "proptest-derive"] diff --git a/acvm-repo/brillig/src/black_box.rs b/acvm-repo/brillig/src/black_box.rs index eb496d0f826..67ddf21589f 100644 --- a/acvm-repo/brillig/src/black_box.rs +++ b/acvm-repo/brillig/src/black_box.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; /// These opcodes provide an equivalent of ACIR blackbox functions. /// They are implemented as native functions in the VM. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BlackBoxOp { /// Encrypts a message using AES128. AES128Encrypt { diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index 1cb31ca3d0a..bd083b914d4 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize}; pub type Label = usize; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum MemoryAddress { Direct(usize), Relative(usize), @@ -82,6 +83,7 @@ impl HeapValueType { /// A fixed-sized array starting from a Brillig memory location. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct HeapArray { pub pointer: MemoryAddress, pub size: usize, @@ -95,12 +97,14 @@ impl Default for HeapArray { /// A memory-sized vector passed starting from a Brillig memory location and with a memory-held size #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub struct HeapVector { pub pointer: MemoryAddress, pub size: MemoryAddress, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum IntegerBitSize { U1, U8, @@ -153,6 +157,7 @@ impl std::fmt::Display for IntegerBitSize { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BitSize { Field, Integer(IntegerBitSize), @@ -182,6 +187,7 @@ impl BitSize { /// this needs to be encoded somehow when dealing with an external system. /// For simplicity, the extra type information is given right in the ForeignCall instructions. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum ValueOrArray { /// A single value passed to or from an external call /// It is an 'immediate' value - used without dereferencing. @@ -199,6 +205,7 @@ pub enum ValueOrArray { } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BrilligOpcode { /// Takes the fields in addresses `lhs` and `rhs` /// Performs the specified binary operation @@ -315,6 +322,7 @@ pub enum BrilligOpcode { /// Binary fixed-length field expressions #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BinaryFieldOp { Add, Sub, @@ -333,6 +341,7 @@ pub enum BinaryFieldOp { /// Binary fixed-length integer expressions #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)] +#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))] pub enum BinaryIntOp { Add, Sub, @@ -355,3 +364,31 @@ pub enum BinaryIntOp { /// (>>) Shift right Shr, } + +#[cfg(feature = "arb")] +mod tests { + use proptest::arbitrary::Arbitrary; + use proptest::prelude::*; + + use super::{BitSize, HeapValueType}; + + // Need to define recursive strategy for `HeapValueType` + impl Arbitrary for HeapValueType { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let leaf = any::().prop_map(HeapValueType::Simple); + leaf.prop_recursive(2, 3, 2, |inner| { + prop_oneof![ + (prop::collection::vec(inner.clone(), 1..3), any::()).prop_map( + |(value_types, size)| { HeapValueType::Array { value_types, size } } + ), + (prop::collection::vec(inner.clone(), 1..3)) + .prop_map(|value_types| { HeapValueType::Vector { value_types } }), + ] + }) + .boxed() + } + } +} diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 935c296d5ae..a2c52eaa429 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -1737,7 +1737,7 @@ mod tests { ) -> VM<'a, F, StubbedBlackBoxSolver> { let mut vm = VM::new(calldata, opcodes, solver, false); brillig_execute(&mut vm); - assert_eq!(vm.call_stack, vec![]); + assert!(vm.call_stack.is_empty()); vm } diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml index e5231565041..ca2612f3d92 100644 --- a/compiler/noirc_frontend/Cargo.toml +++ b/compiler/noirc_frontend/Cargo.toml @@ -36,7 +36,7 @@ fxhash.workspace = true [dev-dependencies] base64.workspace = true proptest.workspace = true -proptest-derive = "0.5.0" +proptest-derive.workspace = true [features] experimental_parser = [] diff --git a/cspell.json b/cspell.json index 3bbbede78cc..31c81657144 100644 --- a/cspell.json +++ b/cspell.json @@ -173,6 +173,7 @@ "noncanonical", "nouner", "oneshot", + "oneof", "overflowing", "pedersen", "peekable", @@ -188,6 +189,11 @@ "printstd", "proptest", "proptests", + "prost", + "proto", + "protobuf", + "protoc", + "protos", "pseudocode", "pubkey", "quantile", diff --git a/scripts/bytecode-sizes/README.md b/scripts/bytecode-sizes/README.md new file mode 100644 index 00000000000..d860e843428 --- /dev/null +++ b/scripts/bytecode-sizes/README.md @@ -0,0 +1,41 @@ +# Bytecode Size Comparison + +These scripts can be used to compare the bytecode size of circuits in `aztec-packages` between two different versions of `nargo`. +For example we can see what happens if we change the serialization format from `bincode` to `protobuf` in https://github.com/noir-lang/noir/pull/7513 + +## Compiling contracts + +Run these commands to compile Noir protocol circuits and contracts in `aztec-packages` after rebuilding `nargo`: + +```shell +cargo build -p nargo_cli --release +./target/release/nargo --program-dir ../aztec-packages/noir-projects/noir-protocol-circuits compile --force --silence-warnings --skip-underconstrained-check +./target/release/nargo --program-dir ../aztec-packages/noir-projects/noir-contracts compile --force --silence-warnings --skip-underconstrained-check +``` + +## Baseline + +Record the baseline bytecode size with before switching to other implementations: +```shell +./scripts/bytecode-sizes/print-bytecode-size.sh ../aztec-packages > ./scripts/bytecode-sizes/baseline.jsonl +``` + +## Alternative + +After making some changes to `nargo`, compile the contracts again with the commands above, then run the following +commands to record a new measurement, and compare it against the baseline recorded earlier. + +```shell +BASELINE=baseline +ALTERNATIVE=alternative +./scripts/bytecode-sizes/print-bytecode-size.sh ../aztec-packages \ + > ./scripts/bytecode-sizes/$ALTERNATIVE.jsonl +./scripts/bytecode-sizes/compare-bytecode-size.sh \ + ./scripts/bytecode-sizes/$BASELINE.jsonl \ + ./scripts/bytecode-sizes/$ALTERNATIVE.jsonl \ + > ./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.jsonl +./scripts/bytecode-sizes/plot-bytecode-size.sh \ + ./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.jsonl +``` + +You can look at the impact in `./scripts/bytecode-sizes/$BASELINE-vs-$ALTERNATIVE.png`. \ No newline at end of file diff --git a/scripts/bytecode-sizes/bytecode-size-scatter.plt b/scripts/bytecode-sizes/bytecode-size-scatter.plt new file mode 100644 index 00000000000..713d66db8b8 --- /dev/null +++ b/scripts/bytecode-sizes/bytecode-size-scatter.plt @@ -0,0 +1,9 @@ +set term png size 1200,800; +set output FILEOUT; +unset key; +set title NAME; +set logscale x; +set xlabel "Base Bytecode Size (Log)"; +set ylabel "Alt Bytecode Ratio"; + +plot FILEIN using 2:4 with points; diff --git a/scripts/bytecode-sizes/compare-bytecode-size.sh b/scripts/bytecode-sizes/compare-bytecode-size.sh new file mode 100755 index 00000000000..12c0bdace38 --- /dev/null +++ b/scripts/bytecode-sizes/compare-bytecode-size.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +set -eu + +IN1=$1 +IN2=$2 + +jq --slurp -c ' +. as $top | +($top[] | select(.encoding == "base") | .data[]) as $base | +($top[] | select(.encoding == "alt") | .data[] | select(.name == $base.name)) as $alt | +{ + name: $base.name, + base_size: $base.bytecode_size, + alt_size: $alt.bytecode_size, + ratio: ($alt.bytecode_size / $base.bytecode_size) +} +' \ + <(cat $IN1 | jq --slurp '{encoding: "base", data: .}') \ + <(cat $IN2 | jq --slurp '{encoding: "alt", data: .}') \ diff --git a/scripts/bytecode-sizes/plot-bytecode-size.sh b/scripts/bytecode-sizes/plot-bytecode-size.sh new file mode 100755 index 00000000000..39d62876171 --- /dev/null +++ b/scripts/bytecode-sizes/plot-bytecode-size.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu + +IN=$1 +NAME=$(basename $IN .jsonl) +DAT=$(dirname $IN)/$NAME.dat +PNG=$(dirname $IN)/$NAME.png +PLT=$(dirname $0)/bytecode-size-scatter.plt + +cat $IN | jq -r '[.name, .base_size, .alt_size, .ratio] | @tsv' > $DAT + +gnuplot \ + -e "NAME='$(echo $NAME | tr _ - )'" \ + -e "FILEIN='$DAT'" \ + -e "FILEOUT='$PNG'" \ + $PLT + +rm $DAT diff --git a/scripts/bytecode-sizes/print-bytecode-size.sh b/scripts/bytecode-sizes/print-bytecode-size.sh new file mode 100755 index 00000000000..8b68c5907ef --- /dev/null +++ b/scripts/bytecode-sizes/print-bytecode-size.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu + +AZTEC_PACKAGES_DIR=$1 + +for file in $AZTEC_PACKAGES_DIR/noir-projects/noir-protocol-circuits/target/*.json; do + PROGRAM=$(basename $file .json) + cat $file \ + | jq --arg PROGRAM $PROGRAM \ + -c '{name: $PROGRAM, bytecode_size: .bytecode | @base64d | length}' +done + +for file in $AZTEC_PACKAGES_DIR/noir-projects/noir-contracts/target/*.json; do + CONTRACT=$(basename $file .json) + cat $file \ + | jq --arg CONTRACT $CONTRACT \ + -c '.functions | sort_by(.name) | .[] | {name: ($CONTRACT + "::" + .name), "bytecode_size": .bytecode | @base64d | length}' +done diff --git a/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 699c54e3f52..b12871c75e4 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -13,7 +13,7 @@ use crate::NargoError; use crate::errors::ExecutionError; use crate::foreign_calls::ForeignCallExecutor; -struct ProgramExecutor<'a, F, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> { +struct ProgramExecutor<'a, F: AcirField, B: BlackBoxFunctionSolver, E: ForeignCallExecutor> { functions: &'a [Circuit], unconstrained_functions: &'a [BrilligBytecode], diff --git a/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs index 8ce9ba1de39..d6d00b07503 100644 --- a/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs +++ b/tooling/profiler/src/cli/opcodes_flamegraph_cmd.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use acir::AcirField; use acir::circuit::brillig::BrilligFunctionId; use acir::circuit::{Circuit, Opcode, OpcodeLocation}; use clap::Args; @@ -134,7 +135,7 @@ fn run_with_generator( Ok(()) } -fn locate_brillig_call( +fn locate_brillig_call( brillig_fn_index: usize, acir_functions: &[Circuit], ) -> Option<(usize, usize)> { diff --git a/tooling/profiler/src/opcode_formatter.rs b/tooling/profiler/src/opcode_formatter.rs index 2276bcb4403..591c825e452 100644 --- a/tooling/profiler/src/opcode_formatter.rs +++ b/tooling/profiler/src/opcode_formatter.rs @@ -2,7 +2,7 @@ use acir::AcirField; use acir::brillig::{BinaryFieldOp, BinaryIntOp, BlackBoxOp, Opcode as BrilligOpcode}; use acir::circuit::{Opcode as AcirOpcode, opcodes::BlackBoxFuncCall}; -fn format_blackbox_function(call: &BlackBoxFuncCall) -> String { +fn format_blackbox_function(call: &BlackBoxFuncCall) -> String { match call { BlackBoxFuncCall::AES128Encrypt { .. } => "aes128_encrypt".to_string(), BlackBoxFuncCall::AND { .. } => "and".to_string(), @@ -49,7 +49,7 @@ fn format_blackbox_op(call: &BlackBoxOp) -> String { } } -fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { +fn format_acir_opcode_kind(opcode: &AcirOpcode) -> String { match opcode { AcirOpcode::AssertZero(_) => "arithmetic".to_string(), AcirOpcode::BlackBoxFuncCall(call) => { diff --git a/utils/protobuf/Cargo.toml b/utils/protobuf/Cargo.toml new file mode 100644 index 00000000000..fdd8fb0ba2e --- /dev/null +++ b/utils/protobuf/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "noir_protobuf" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +color-eyre.workspace = true +prost.workspace = true diff --git a/utils/protobuf/src/lib.rs b/utils/protobuf/src/lib.rs new file mode 100644 index 00000000000..c3d72086c83 --- /dev/null +++ b/utils/protobuf/src/lib.rs @@ -0,0 +1,162 @@ +use color_eyre::eyre::{self, Context, bail, eyre}; + +/// A protobuf codec to convert between a domain type `T` +/// and its protobuf representation `R`. +/// +/// It is to be implemented on a `Self` independent of `T` and `R`, +/// so that `T` can be in a third party crate, and `Self` can be +/// generic in the `F` _field_ type as well, which would be cumbersome +/// if we had to implement traits on `R` because `T` is in another +/// crate from the schema, or to scatter the `.proto` schema around +/// so that the traits can be co-defined with `T` which is what can +/// actually be generic in `F`. +pub trait ProtoCodec { + /// Convert domain type `T` to protobuf representation `R`. + fn encode(value: &T) -> R; + /// Encode a field as `Some`. + fn encode_some(value: &T) -> Option { + Some(Self::encode(value)) + } + /// Encode an `enum` to the `i32` value that `prost` represents it with. + fn encode_enum(value: &T) -> i32 + where + R: Into, + { + Self::encode(value).into() + } + /// Encode multiple values as a vector. + fn encode_vec<'a, I>(values: I) -> Vec + where + I: IntoIterator, + T: 'a, + { + values.into_iter().map(Self::encode).collect() + } + + /// Try to convert protobuf representation `R` to domain type `T`. + fn decode(value: &R) -> eyre::Result; + /// Decode a field and attach the name of the field if it fails. + fn decode_wrap(value: &R, msg: &'static str) -> eyre::Result { + Self::decode(value).wrap_err(msg) + } + /// Decode multiple values into a vector. + fn decode_vec(values: &[R]) -> eyre::Result> { + values.iter().map(Self::decode).collect() + } + /// Decode multiple values into a vector, attaching a field name to any errors. + fn decode_vec_wrap(values: &[R], msg: &'static str) -> eyre::Result> { + Self::decode_vec(values).wrap_err(msg) + } + /// Decode a fixed size array. + fn decode_arr(values: &[R]) -> eyre::Result<[T; N]> { + match Self::decode_vec(values)?.try_into() { + Ok(arr) => Ok(arr), + Err(vec) => { + bail!("expected {N} items, got {}", vec.len()); + } + } + } + /// Decode a fixed size array, attaching a field name to any errors + fn decode_arr_wrap(values: &[R], msg: &'static str) -> eyre::Result<[T; N]> { + Self::decode_arr(values).wrap_err(msg) + } + /// Decode a boxed fixed size array. + fn decode_box_arr(values: &[R]) -> eyre::Result> { + Self::decode_arr(values).map(Box::new) + } + /// Decode a boxed fixed size array, attaching a field name to any errors + fn decode_box_arr_wrap( + values: &[R], + msg: &'static str, + ) -> eyre::Result> { + Self::decode_box_arr(values).wrap_err(msg) + } + /// Decode an optional field as a required one; fails if it's `None`. + fn decode_some(value: &Option) -> eyre::Result { + match value { + Some(value) => Self::decode(value), + None => Err(eyre!("missing field")), + } + } + /// Decode an optional field as a required one, attaching a field name to any errors. + /// Returns error if the field is missing. + fn decode_some_wrap(value: &Option, msg: &'static str) -> eyre::Result { + Self::decode_some(value).wrap_err(msg) + } + /// Decode an optional field, attaching a field name to any errors. + /// Return `None` if the field is missing. + fn decode_opt_wrap(value: &Option, msg: &'static str) -> eyre::Result> { + value.as_ref().map(|value| Self::decode_wrap(value, msg)).transpose() + } + /// Decode the numeric representation of an enum into the domain type. + /// Return an error if the value cannot be recognized. + fn decode_enum(value: i32) -> eyre::Result + where + R: TryFrom, + { + let r = R::try_from(value)?; + Self::decode(&r) + } + /// Decode the numeric representation of an enum, attaching the field name to any errors. + fn decode_enum_wrap(value: i32, msg: &'static str) -> eyre::Result + where + R: TryFrom, + { + Self::decode_enum(value).wrap_err(msg) + } + + /// Encode a domain type to protobuf and serialize it to bytes. + fn serialize_to_vec(value: &T) -> Vec + where + R: prost::Message, + { + Self::encode(value).encode_to_vec() + } + /// Deserialize a buffer into protobuf and then decode into the domain type. + fn deserialize_from_vec(buf: &[u8]) -> eyre::Result + where + R: prost::Message + Default, + { + let repr = R::decode(buf).wrap_err("failed to decode into protobuf")?; + Self::decode(&repr).wrap_err("failed to decode protobuf into domain") + } +} + +/// Decode repeated items by mapping a function over them, attaching an error message if it fails. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_vec_map_wrap(rs: &[R], msg: &'static str, f: F) -> eyre::Result> +where + F: Fn(&R) -> eyre::Result, +{ + rs.iter().map(f).collect::>>().wrap_err(msg) +} + +/// Decode an optional item, returning an error if it's `None`. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_some_map(r: &Option, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + match r { + Some(r) => f(r), + None => Err(eyre!("missing field")), + } +} + +/// Decode an optional item, attaching a field name to any errors. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_some_map_wrap(r: &Option, msg: &'static str, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + decode_some_map(r, f).wrap_err(msg) +} + +/// Decode a `oneof` field, returning an error if it's missing. +/// Useful when a lambda needs to be applied before we can use one of the type class methods. +pub fn decode_oneof_map(r: &Option, f: F) -> eyre::Result +where + F: Fn(&R) -> eyre::Result, +{ + decode_some_map_wrap(r, "oneof value", f) +}