Skip to content

Commit

Permalink
feat(comptime): Implement blackbox functions in comptime interpreter (#…
Browse files Browse the repository at this point in the history
…6551)

Co-authored-by: jfecher <[email protected]>
Co-authored-by: Maxim Vezenov <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent 7d61242 commit 10a9f81
Show file tree
Hide file tree
Showing 18 changed files with 988 additions and 424 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ proptest-derive = "0.4.0"
rayon = "1.8.0"
sha2 = { version = "0.10.6", features = ["compress"] }
sha3 = "0.10.6"
strum = "0.24"
strum_macros = "0.24"

im = { version = "15.1", features = ["serde"] }
tracing = "0.1.40"
Expand Down
4 changes: 2 additions & 2 deletions acvm-repo/acir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ flate2.workspace = true
bincode.workspace = true
base64.workspace = true
serde-big-array = "0.5.1"
strum = { workspace = true }
strum_macros = { workspace = true }

[dev-dependencies]
serde_json = "1.0"
strum = "0.24"
strum_macros = "0.24"
serde-reflection = "0.3.6"
serde-generate = "0.25.1"
fxhash.workspace = true
Expand Down
4 changes: 1 addition & 3 deletions acvm-repo/acir/src/circuit/black_box_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
//! implemented in more basic constraints.
use serde::{Deserialize, Serialize};
#[cfg(test)]
use strum_macros::EnumIter;

#[allow(clippy::upper_case_acronyms)]
#[derive(Clone, Debug, Hash, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(test, derive(EnumIter))]
#[derive(Clone, Debug, Hash, Copy, PartialEq, Eq, Serialize, Deserialize, EnumIter)]
pub enum BlackBoxFunc {
/// Ciphers (encrypts) the provided plaintext using AES128 in CBC mode,
/// padding the input using PKCS#7.
Expand Down
11 changes: 4 additions & 7 deletions acvm-repo/acvm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,19 @@ acvm_blackbox_solver.workspace = true
indexmap = "1.7.0"

[features]
bn254 = [
"acir/bn254",
"brillig_vm/bn254",
"acvm_blackbox_solver/bn254",
]
bn254 = ["acir/bn254", "brillig_vm/bn254", "acvm_blackbox_solver/bn254"]
bls12_381 = [
"acir/bls12_381",
"brillig_vm/bls12_381",
"acvm_blackbox_solver/bls12_381",
]

[dev-dependencies]
ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] }
ark-bls12-381 = { version = "^0.4.0", default-features = false, features = [
"curve",
] }
ark-bn254.workspace = true
bn254_blackbox_solver.workspace = true
proptest.workspace = true
zkhash = { version = "^0.2.0", default-features = false }
num-bigint.workspace = true

48 changes: 48 additions & 0 deletions acvm-repo/blackbox_solver/src/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,51 @@ impl BigIntSolver {
Ok(())
}
}

/// Wrapper over the generic bigint solver to automatically assign bigint IDs.
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct BigIntSolverWithId {
solver: BigIntSolver,
last_id: u32,
}

impl BigIntSolverWithId {
pub fn create_bigint_id(&mut self) -> u32 {
let output = self.last_id;
self.last_id += 1;
output
}

pub fn bigint_from_bytes(
&mut self,
inputs: &[u8],
modulus: &[u8],
) -> Result<u32, BlackBoxResolutionError> {
let id = self.create_bigint_id();
self.solver.bigint_from_bytes(inputs, modulus, id)?;
Ok(id)
}

pub fn bigint_to_bytes(&self, input: u32) -> Result<Vec<u8>, BlackBoxResolutionError> {
self.solver.bigint_to_bytes(input)
}

pub fn bigint_op(
&mut self,
lhs: u32,
rhs: u32,
func: BlackBoxFunc,
) -> Result<u32, BlackBoxResolutionError> {
let modulus_lhs = self.solver.get_modulus(lhs, func)?;
let modulus_rhs = self.solver.get_modulus(rhs, func)?;
if modulus_lhs != modulus_rhs {
return Err(BlackBoxResolutionError::Failed(
func,
"moduli should be identical in BigInt operation".to_string(),
));
}
let id = self.create_bigint_id();
self.solver.bigint_op(lhs, rhs, id, func)?;
Ok(id)
}
}
2 changes: 1 addition & 1 deletion acvm-repo/blackbox_solver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod hash;
mod logic;

pub use aes128::aes128_encrypt;
pub use bigint::BigIntSolver;
pub use bigint::{BigIntSolver, BigIntSolverWithId};
pub use curve_specific_solver::{BlackBoxFunctionSolver, StubbedBlackBoxSolver};
pub use ecdsa::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify};
pub use hash::{blake2s, blake3, keccakf1600, sha256_compression};
Expand Down
57 changes: 5 additions & 52 deletions acvm-repo/brillig_vm/src/black_box.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use acir::brillig::{BlackBoxOp, HeapArray, HeapVector, IntegerBitSize};
use acir::{AcirField, BlackBoxFunc};
use acvm_blackbox_solver::BigIntSolver;
use acvm_blackbox_solver::{
aes128_encrypt, blake2s, blake3, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, keccakf1600,
sha256_compression, BlackBoxFunctionSolver, BlackBoxResolutionError,
sha256_compression, BigIntSolverWithId, BlackBoxFunctionSolver, BlackBoxResolutionError,
};
use num_bigint::BigUint;
use num_traits::Zero;
Expand Down Expand Up @@ -39,11 +38,13 @@ fn to_value_vec<F: AcirField>(input: &[u8]) -> Vec<MemoryValue<F>> {
input.iter().map(|&x| x.into()).collect()
}

pub(crate) type BrilligBigIntSolver = BigIntSolverWithId;

pub(crate) fn evaluate_black_box<F: AcirField, Solver: BlackBoxFunctionSolver<F>>(
op: &BlackBoxOp,
solver: &Solver,
memory: &mut Memory<F>,
bigint_solver: &mut BrilligBigintSolver,
bigint_solver: &mut BrilligBigIntSolver,
) -> Result<(), BlackBoxResolutionError> {
match op {
BlackBoxOp::AES128Encrypt { inputs, iv, key, outputs } => {
Expand All @@ -56,7 +57,7 @@ pub(crate) fn evaluate_black_box<F: AcirField, Solver: BlackBoxFunctionSolver<F>
})?;
let key: [u8; 16] =
to_u8_vec(read_heap_array(memory, key)).try_into().map_err(|_| {
BlackBoxResolutionError::Failed(bb_func, "Invalid ley length".to_string())
BlackBoxResolutionError::Failed(bb_func, "Invalid key length".to_string())
})?;
let ciphertext = aes128_encrypt(&inputs, iv, key)?;

Expand Down Expand Up @@ -353,54 +354,6 @@ pub(crate) fn evaluate_black_box<F: AcirField, Solver: BlackBoxFunctionSolver<F>
}
}

/// Wrapper over the generic bigint solver to automatically assign bigint ids in brillig
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub(crate) struct BrilligBigintSolver {
bigint_solver: BigIntSolver,
last_id: u32,
}

impl BrilligBigintSolver {
pub(crate) fn create_bigint_id(&mut self) -> u32 {
let output = self.last_id;
self.last_id += 1;
output
}

pub(crate) fn bigint_from_bytes(
&mut self,
inputs: &[u8],
modulus: &[u8],
) -> Result<u32, BlackBoxResolutionError> {
let id = self.create_bigint_id();
self.bigint_solver.bigint_from_bytes(inputs, modulus, id)?;
Ok(id)
}

pub(crate) fn bigint_to_bytes(&self, input: u32) -> Result<Vec<u8>, BlackBoxResolutionError> {
self.bigint_solver.bigint_to_bytes(input)
}

pub(crate) fn bigint_op(
&mut self,
lhs: u32,
rhs: u32,
func: BlackBoxFunc,
) -> Result<u32, BlackBoxResolutionError> {
let modulus_lhs = self.bigint_solver.get_modulus(lhs, func)?;
let modulus_rhs = self.bigint_solver.get_modulus(rhs, func)?;
if modulus_lhs != modulus_rhs {
return Err(BlackBoxResolutionError::Failed(
func,
"moduli should be identical in BigInt operation".to_string(),
));
}
let id = self.create_bigint_id();
self.bigint_solver.bigint_op(lhs, rhs, id, func)?;
Ok(id)
}
}

fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc {
match op {
BlackBoxOp::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt,
Expand Down
4 changes: 2 additions & 2 deletions acvm-repo/brillig_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use acir::brillig::{
use acir::AcirField;
use acvm_blackbox_solver::BlackBoxFunctionSolver;
use arithmetic::{evaluate_binary_field_op, evaluate_binary_int_op, BrilligArithmeticError};
use black_box::{evaluate_black_box, BrilligBigintSolver};
use black_box::{evaluate_black_box, BrilligBigIntSolver};

// Re-export `brillig`.
pub use acir::brillig;
Expand Down Expand Up @@ -95,7 +95,7 @@ pub struct VM<'a, F, B: BlackBoxFunctionSolver<F>> {
/// The solver for blackbox functions
black_box_solver: &'a B,
// The solver for big integers
bigint_solver: BrilligBigintSolver,
bigint_solver: BrilligBigIntSolver,
// Flag that determines whether we want to profile VM.
profiling_active: bool,
// Samples for profiling the VM execution.
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ cfg-if.workspace = true
tracing.workspace = true
petgraph = "0.6"
rangemap = "1.4.0"
strum = "0.24"
strum_macros = "0.24"
strum.workspace = true
strum_macros.workspace = true


[dev-dependencies]
Expand Down
10 changes: 10 additions & 0 deletions compiler/noirc_frontend/src/hir/comptime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ pub enum InterpreterError {
item: String,
location: Location,
},
InvalidInComptimeContext {
item: String,
location: Location,
explanation: String,
},
TypeAnnotationsNeededForMethodCall {
location: Location,
},
Expand Down Expand Up @@ -291,6 +296,7 @@ impl InterpreterError {
| InterpreterError::UnsupportedTopLevelItemUnquote { location, .. }
| InterpreterError::ComptimeDependencyCycle { location, .. }
| InterpreterError::Unimplemented { location, .. }
| InterpreterError::InvalidInComptimeContext { location, .. }
| InterpreterError::NoImpl { location, .. }
| InterpreterError::ImplMethodTypeMismatch { location, .. }
| InterpreterError::DebugEvaluateComptime { location, .. }
Expand Down Expand Up @@ -540,6 +546,10 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic {
let msg = format!("{item} is currently unimplemented");
CustomDiagnostic::simple_error(msg, String::new(), location.span)
}
InterpreterError::InvalidInComptimeContext { item, location, explanation } => {
let msg = format!("{item} is invalid in comptime context");
CustomDiagnostic::simple_error(msg, explanation.clone(), location.span)
}
InterpreterError::BreakNotInLoop { location } => {
let msg = "There is no loop to break out of!".into();
CustomDiagnostic::simple_error(msg, String::new(), location.span)
Expand Down
Loading

0 comments on commit 10a9f81

Please sign in to comment.