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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions acvm-repo/brillig_vm/src/arithmetic.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Implementations for [binary field operations][acir::brillig::Opcode::BinaryFieldOp] and
//! [binary integer operations][acir::brillig::Opcode::BinaryIntOp].
use std::ops::{BitAnd, BitOr, BitXor, Shl, Shr};

use acir::AcirField;
Expand Down Expand Up @@ -201,6 +203,15 @@ pub(crate) fn evaluate_binary_int_op<F: AcirField>(
}
}

/// Evaluates binary operations on 1-bit unsigned integers (booleans).
///
/// # Returns
/// - Ok(result) if successful.
/// - Err([BrilligArithmeticError::DivisionByZero]) if division by zero occurs.
///
/// # Panics
/// If an operation other than Add, Sub, Mul, Div, And, Or, Xor, Equals, LessThan,
/// or LessThanEquals is supplied as an argument.
fn evaluate_binary_int_op_u1(
op: &BinaryIntOp,
lhs: bool,
Expand All @@ -225,6 +236,11 @@ fn evaluate_binary_int_op_u1(
Ok(result)
}

/// Evaluates comparison operations (Equals, LessThan, LessThanEquals)
/// between two values of an ordered type (e.g., fields are unordered).
///
/// # Panics
/// If an unsupported operator is provided (i.e., not Equals, LessThan, or LessThanEquals).
fn evaluate_binary_int_op_cmp<T: Ord + PartialEq>(op: &BinaryIntOp, lhs: T, rhs: T) -> bool {
match op {
BinaryIntOp::Equals => lhs == rhs,
Expand All @@ -234,6 +250,11 @@ fn evaluate_binary_int_op_cmp<T: Ord + PartialEq>(op: &BinaryIntOp, lhs: T, rhs:
}
}

/// Evaluates shift operations (Shl, Shr) for unsigned integers.
/// Ensures that shifting beyond the type width returns zero.
///
/// # Panics
/// If an unsupported operator is provided (i.e., not Shl or Shr).
fn evaluate_binary_int_op_shifts<T: From<u8> + Zero + Shl<Output = T> + Shr<Output = T>>(
op: &BinaryIntOp,
lhs: T,
Expand All @@ -252,6 +273,15 @@ fn evaluate_binary_int_op_shifts<T: From<u8> + Zero + Shl<Output = T> + Shr<Outp
}
}

/// Evaluates arithmetic or bitwise operations on unsigned integer types,
/// using wrapping arithmetic for [add][BinaryIntOp::Add], [sub][BinaryIntOp::Sub], and [mul][BinaryIntOp::Mul].
///
/// # Returns
/// - Ok(result) if successful.
/// - Err([BrilligArithmeticError::DivisionByZero]) if division by zero occurs.
///
/// # Panics
/// If there an operation other than Add, Sub, Mul, Div, And, Or, Xor is supplied as an argument.
fn evaluate_binary_int_op_arith<
T: WrappingAdd
+ WrappingSub
Expand Down
29 changes: 29 additions & 0 deletions acvm-repo/brillig_vm/src/black_box.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Implementations for VM native [black box functions][acir::brillig::Opcode::BlackBox].
use acir::brillig::{BlackBoxOp, HeapArray, HeapVector};
use acir::{AcirField, BlackBoxFunc};
use acvm_blackbox_solver::{
Expand All @@ -10,6 +11,7 @@
use crate::Memory;
use crate::memory::MemoryValue;

/// Reads a dynamically-sized [vector][HeapVector] from memory.
fn read_heap_vector<'a, F: AcirField>(
memory: &'a Memory<F>,
vector: &HeapVector,
Expand All @@ -18,6 +20,7 @@
memory.read_slice(memory.read_ref(vector.pointer), size.to_usize())
}

/// Reads a fixed-size [array][HeapArray] from memory.
fn read_heap_array<'a, F: AcirField>(
memory: &'a Memory<F>,
array: &HeapArray,
Expand All @@ -34,12 +37,33 @@
result
}

/// Converts a slice of u8 values into a Vec<[`MemoryValue<F>`]>,
/// wrapping each byte as a [MemoryValue::U8].
fn to_value_vec<F: AcirField>(input: &[u8]) -> Vec<MemoryValue<F>> {
input.iter().map(|&x| x.into()).collect()
}

pub(crate) type BrilligBigIntSolver = BigIntSolverWithId;

/// Evaluates a black box function inside the VM, performing the actual native computation.
///
/// Delegates the execution to the corresponding cryptographic or arithmetic
/// function, depending on the [BlackBoxOp] variant.
/// Handles input conversion, writing the result to memory, and error propagation.
///
/// # Arguments
/// - op: The black box operation to evaluate.
/// - solver: An implementation of [BlackBoxFunctionSolver] providing external function behavior.
/// - memory: The VM memory from which inputs are read and to which results are written.
/// - bigint_solver: A solver used for big integer operations.
///
/// # Returns
/// - Ok(()) if evaluation succeeds.
/// - Err([BlackBoxResolutionError]) if an error occurs during execution or input is invalid.
///
/// # Panics
/// If any required memory value cannot be converted to the expected type (e.g., [expect_u8][MemoryValue::expect_u8])
/// or if the [radix decomposition][BlackBoxOp::ToRadix] constraints are violated internally, such as an invalid radix range (e.g., radix of 1).
pub(crate) fn evaluate_black_box<F: AcirField, Solver: BlackBoxFunctionSolver<F>>(
op: &BlackBoxOp,
solver: &Solver,
Expand All @@ -59,10 +83,10 @@
to_u8_vec(read_heap_array(memory, key)).try_into().map_err(|_| {
BlackBoxResolutionError::Failed(bb_func, "Invalid key length".to_string())
})?;
let ciphertext = aes128_encrypt(&inputs, iv, key)?;

Check warning on line 86 in acvm-repo/brillig_vm/src/black_box.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (ciphertext)

memory.write(outputs.size, ciphertext.len().into());

Check warning on line 88 in acvm-repo/brillig_vm/src/black_box.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (ciphertext)
memory.write_slice(memory.read_ref(outputs.pointer), &to_value_vec(&ciphertext));

Check warning on line 89 in acvm-repo/brillig_vm/src/black_box.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (ciphertext)

Ok(())
}
Expand Down Expand Up @@ -361,6 +385,11 @@
}
}

/// Maps a [BlackBoxOp] variant to its corresponding [BlackBoxFunc].
/// Used primarily for error reporting and resolution purposes.
///
/// # Panics
/// If called with a [BlackBoxOp::ToRadix] operation, which is not part of the [BlackBoxFunc] enum.
fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc {
match op {
BlackBoxOp::AES128Encrypt { .. } => BlackBoxFunc::AES128Encrypt,
Expand Down
1 change: 1 addition & 0 deletions acvm-repo/brillig_vm/src/cast.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Implementation for the [cast operation][acir::brillig::Opcode::Cast].
use acir::{
AcirField,
brillig::{BitSize, IntegerBitSize},
Expand Down
30 changes: 27 additions & 3 deletions acvm-repo/brillig_vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,44 @@
/// The error call stack contains the opcode indexes of the call stack at the time of failure, plus the index of the opcode that failed.
pub type ErrorCallStack = Vec<usize>;

/// Represents the reason why the Brillig VM failed during execution.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum FailureReason {
Trap { revert_data_offset: usize, revert_data_size: usize },
/// A trap was encountered, which indicates an explicit failure from within the VM program.
///
/// A trap is triggered explicitly by the [trap opcode][Opcode::Trap].
/// The revert data is referenced by the offset and size in the VM memory.
Trap {
/// Offset in memory where the revert data begins.
revert_data_offset: usize,
/// Size of the revert data.
revert_data_size: usize,
},
/// A runtime failure during execution.
/// This error is triggered by all opcodes aside the [trap opcode][Opcode::Trap].
/// For example, a [binary operation][Opcode::BinaryIntOp] can trigger a division by zero error.
RuntimeError { message: String },
}

/// Represents the current execution status of the Brillig VM.
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum VMStatus<F> {
/// The VM has completed execution successfully.
/// The output of the program is stored in the VM memory and can be accessed via the provided offset and size.
Finished {
/// Offset in memory where the return data begins.
return_data_offset: usize,
/// Size of the return data.
return_data_size: usize,
},
/// The VM is still in progress and has not yet completed execution.
/// This is used when simulating execution.
InProgress,
/// The VM encountered a failure and halted execution.
Failure {
/// The reason for the failure.
reason: FailureReason,
/// The call stack at the time the failure occurred, useful for debugging nested calls.
call_stack: ErrorCallStack,
},
/// The VM process is not solvable as a [foreign call][Opcode::ForeignCall] has been
Expand All @@ -65,7 +88,7 @@
},
}

// A sample for each opcode that was executed.
/// All samples for each opcode that was executed
pub type BrilligProfilingSamples = Vec<BrilligProfilingSample>;

/// The position of an opcode that is currently being executed in the bytecode
Expand All @@ -91,9 +114,10 @@
/// A map for translating encountered branching logic to features for fuzzing
pub type BranchToFeatureMap = HashMap<Branch, UniqueFeatureIndex>;

/// A sample for an executed opcode
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BrilligProfilingSample {
// The call stack when processing a given opcode.
/// The call stack when processing a given opcode.
pub call_stack: Vec<usize>,
}

Expand Down Expand Up @@ -583,7 +607,7 @@
self.set_program_counter(*location)
}
Opcode::Const { destination, value, bit_size } => {
// Consts are not checked in runtime to fit in the bit size, since they can safely be checked statically.

Check warning on line 610 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (Consts)
self.memory.write(*destination, MemoryValue::new_from_field(*value, *bit_size));
self.increment_program_counter()
}
Expand Down Expand Up @@ -1119,7 +1143,7 @@
}

#[test]
fn jmpifnot_opcode() {

Check warning on line 1146 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (jmpifnot)
let calldata: Vec<FieldElement> = vec![1u128.into(), 2u128.into()];

let opcodes = vec![
Expand Down Expand Up @@ -1368,7 +1392,7 @@
}

#[test]
fn cmov_opcode() {

Check warning on line 1395 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (cmov)
let calldata: Vec<FieldElement> =
vec![(0u128).into(), (1u128).into(), (2u128).into(), (3u128).into()];

Expand Down Expand Up @@ -1625,7 +1649,7 @@
}

#[test]
fn iconst_opcode() {

Check warning on line 1652 in acvm-repo/brillig_vm/src/lib.rs

View workflow job for this annotation

GitHub Actions / Code

Unknown word (iconst)
let opcodes = &[
Opcode::Const {
destination: MemoryAddress::direct(0),
Expand Down
20 changes: 17 additions & 3 deletions acvm-repo/brillig_vm/src/memory.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
//! Implementation of the VM's memory
use acir::{
AcirField,
brillig::{BitSize, IntegerBitSize, MemoryAddress},
};

/// The bit size used for addressing memory within the Brillig VM.
///
/// All memory pointers are interpreted as `u32` values, meaning the VM can directly address up to 2^32 memory slots.
pub const MEMORY_ADDRESSING_BIT_SIZE: IntegerBitSize = IntegerBitSize::U32;

/// A single typed value in the Brillig VM's memory.
///
/// Memory in the VM is strongly typed and can represent either a native field element
/// or an integer of a specific bit width. This enum encapsulates all supported
/// in-memory types and allows conversion between representations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum MemoryValue<F> {
Field(F),
Expand All @@ -16,12 +25,16 @@ pub enum MemoryValue<F> {
U128(u128),
}

/// Represents errors that can occur when interpreting or converting typed memory values.
#[derive(Debug, thiserror::Error)]
pub enum MemoryTypeError {
/// The value's bit size does not match the expected bit size for the operation.
#[error(
"Bit size for value {value_bit_size} does not match the expected bit size {expected_bit_size}"
)]
MismatchedBitSize { value_bit_size: u32, expected_bit_size: u32 },
/// The memory value is not an integer and cannot be interpreted as one.
/// For example, this can be triggered when attempting to convert a field element to an integer such as in [MemoryValue::to_u128].
#[error("Value is not an integer")]
NotAnInteger,
}
Expand Down Expand Up @@ -284,11 +297,12 @@ impl<F: AcirField> TryFrom<MemoryValue<F>> for u128 {
memory_value.expect_u128()
}
}

/// The VM's memory.
/// Memory is internally represented as a vector of values.
/// We grow the memory when values past the end are set, extending with 0s.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Memory<F> {
// Memory is a vector of values.
// We grow the memory when values past the end are set, extending with 0s.
// Internal memory representation
inner: Vec<MemoryValue<F>>,
}

Expand Down
Loading