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
4 changes: 4 additions & 0 deletions acvm-repo/acir_field/src/generic_ark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ pub trait AcirField:
/// This is the number of bits required to represent this specific field element
fn num_bits(&self) -> u32;

/// Downcast the field into a `u128`.
///
/// If the value does not fit, it is truncated.
fn to_u128(self) -> u128;

/// Downcast the field into a `u128` if it fits into 128 bits, otherwise return `None`.
fn try_into_u128(self) -> Option<u128>;

fn to_i128(self) -> i128;
Expand Down
2 changes: 1 addition & 1 deletion acvm-repo/acvm/src/pwg/brillig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl<'b, B: BlackBoxFunctionSolver<F>, F: AcirField> BrilligSolver<'b, F, B> {
}

pub fn step(&mut self) -> Result<BrilligSolverStatus<F>, OpcodeResolutionError<F>> {
let status = self.vm.process_opcode();
let status = self.vm.process_opcode().clone();
self.handle_vm_status(status)
}

Expand Down
72 changes: 42 additions & 30 deletions acvm-repo/brillig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ Finite Field VM

The Brillig VM operates over finite fields and supports using up to a 128 bit integer representation. The finite field Brillig supports is generalizable and based upon the field ACIR supports (where [Arkworks](https://github.com/arkworks-rs/algebra/tree/master/ff) is used for the finite field interface). The ACIR fields currently supported are the prime fields of the bn254 curve and the bls12-381 curve.

All integers ultimately translate to the finite field which Brillig is based upon. For example when a BinaryIntOp is used, the field is first cast to a fixed bitsize integer that can be accommodated within the field, prior to performing operations. Certain operations, such as ordered comparison or signed division, only make sense in the context of BinaryIntOp. The exact maximum integer value the field can store should not be relied upon, however, it is assured to be capable of packing 3 32-bit integers.
All integers ultimately translate to the finite field which Brillig is based upon. For example when a `BinaryIntOp` is used, the field is first cast to a fixed bitsize integer that can be accommodated within the field, prior to performing operations. Certain operations, such as ordered comparison or signed division, only make sense in the context of `BinaryIntOp`. The exact maximum integer value the field can store should not be relied upon, however, it is assured to be capable of packing 3 32-bit integers.

The decision to have Brillig operate over finite fields simplifies SNARK proving and optimizes for efficiency, as ultimately all data types translate to a finite field which is the native data type for SNARKs.

Expand All @@ -101,13 +101,13 @@ The VM accepts four initialization parameters.
3. The black-box solver, which is used to execute black-box functions
4. A flag, that can be used for profiling purposes

The VM then processes each opcode according to their [specification](02_opcode_listing.md).
The VM then processes each opcode according to their [specification](./src/opcodes.rs).

If the VM reaches a foreign call instruction it will first fetch the call's input according to the information inside of the instruction. Through an internal counter and the foreign call results supplied to the VM, the VM will determine whether the outputs have been resolved. If they have not been resolved, the VM will then pause and return a status containing the foreign call inputs. The caller of the VM should interpret the call information returned and update the Brillig process. Execution can then continue as normal. While technically the foreign call result is considered part of the VM's input along with bytecode and calldata, it is practically an input to the program.

Error and Exception Handling
---
Failed asserts, represented by the TRAP opcode, during the execution of an unconstrained function, result in an error in Brillig bytecode, accompanied by data detailing the failure. In a hedged trust blockchain environment, a prover might still want to generate a 'valid' proof of an error result so that incorrectness can be correctly attributed. This emphasizes the importance of handling errors and exceptions within the context of a blockchain-based VM.
Failed asserts, represented by the `Trap` opcode, during the execution of an unconstrained function, result in an error in Brillig bytecode, accompanied by data detailing the failure. In a hedged trust blockchain environment, a prover might still want to generate a 'valid' proof of an error result so that incorrectness can be correctly attributed. This emphasizes the importance of handling errors and exceptions within the context of a blockchain-based VM.

Conclusion
---
Expand All @@ -119,9 +119,9 @@ For the following Noir:

```noir
fn main() {
let mut a = 10;
let mut b = 5;
let mut c = 0;
let mut a = 10_u32;
let mut b = 5_u32;
let mut c = 0_u32;

c = a + b;

Expand All @@ -131,7 +131,7 @@ fn main() {
b = a + b;
}

print(a, b, c);
println((a, b, c));
}
```

Expand All @@ -141,58 +141,62 @@ One possible Brillig output would be:
[
{ // location = 0
"Const": {
"destination": 0,
"destination": { "Direct": 0 },
"bit_size": { "Integer": "U32" },
"value": { "inner": "10" }
}
},
{ // location = 1
"Const": {
"destination": 1,
"destination": { "Direct": 1 },
"bit_size": { "Integer": "U32" },
"value": { "inner": "5" }
}
},
{ // location = 2
"Const": {
"destination": 2,
"destination": { "Direct": 2 },
"bit_size": { "Integer": "U32" },
"value": { "inner": "0" }
}
},
{ // location = 3
"Const": {
"destination": 3,
"destination": { "Direct": 3 },
"bit_size": { "Integer": "U32" },
"value": { "inner": "15" }
}
},
{ // location = 4
"BinaryIntOp": {
"destination": 2,
"destination": { "Direct": 2 },
"op": "Add",
"bit_size": 32,
"lhs": 0,
"rhs": 1
"bit_size": { "Integer": "U32" },
"lhs": { "Direct": 0 },
"rhs": { "Direct": 1 }
}
},
{ // location = 5
"BinaryIntOp": {
"destination": 4,
"destination": { "Direct": 4 },
"op": "LessThanEquals",
"bit_size": 32,
"lhs": 2,
"rhs": 3
"bit_size": { "Integer": "U32" },
"lhs": { "Direct": 2 },
"rhs": { "Direct": 3 }
}
},
{ // location = 6
"JumpIf": {
"condition": 4,
"condition": { "Direct": 4 },
"location": 9
}
},
{ // location = 7
"BinaryFieldOp": {
"destination": 0,
"destination": { "Direct": 0 },
"op": "Multiply",
"lhs": 0,
"rhs": 1
"lhs": { "Direct": 0 },
"rhs": { "Direct": 1 }
}
},
{ // location = 8
Expand All @@ -202,20 +206,26 @@ One possible Brillig output would be:
},
{ // location = 9
"BinaryFieldOp": {
"destination": 1,
"destination": { "Direct": 1 },
"op": "Add",
"lhs": 0,
"rhs": 1
"lhs": { "Direct": 0 },
"rhs": { "Direct": 1 }
}
},
{ // location = 10
"ForeignCall": {
"function": "print",
"destination": [], // No output
"input": [
{"RegisterIndex": 0},
{"RegisterIndex": 1},
{"RegisterIndex": 2}
"destination_value_types": [],
"inputs": [
{ "MemoryAddress": { "Direct": 0 } },
{ "MemoryAddress": { "Direct": 1 } },
{ "MemoryAddress": { "Direct": 2 } }
],
"input_value_types": [
{ "Simple": { "Integer": "U32" } },
{ "Simple": { "Integer": "U32" } },
{ "Simple": { "Integer": "U32" } }
]
}
},
Expand All @@ -233,6 +243,8 @@ The execution and interpretation of the program would be as follows:
5. At location 10, we queue up inputs to a foreign call from reg0, reg1, reg2 (variables a, b, and c). This interrupts execution, calls to the outer system, and then returns to Brillig execution. If this had outputs, they might be written to registers and memory.
6. We finally reach the final location where we `Stop`. If this were a function to be called by another Brillig function, we would `Return`.

The output above is only for illustrative purposes. To see the actual opcodes, use the `--show-brillig` CLI option.

## Usage in Noir

Runtime
Expand Down
8 changes: 4 additions & 4 deletions acvm-repo/brillig/src/black_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@ use serde::{Deserialize, Serialize};
#[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.
/// Encrypts a message using AES-128.
AES128Encrypt { inputs: HeapVector, iv: HeapArray, key: HeapArray, outputs: HeapVector },
/// Calculates the Blake2s hash of the inputs.
Blake2s { message: HeapVector, output: HeapArray },
/// Calculates the Blake3 hash of the inputs.
Blake3 { message: HeapVector, output: HeapArray },
/// Keccak Permutation function of 1600 width
/// Keccak permutation function of 1600 width.
Keccakf1600 { input: HeapArray, output: HeapArray },
/// Verifies a ECDSA signature over the secp256k1 curve.
/// Verifies an ECDSA signature over the secp256k1 curve.
EcdsaSecp256k1 {
hashed_msg: HeapVector,
public_key_x: HeapArray,
public_key_y: HeapArray,
signature: HeapArray,
result: MemoryAddress,
},
/// Verifies a ECDSA signature over the secp256r1 curve.
/// Verifies an ECDSA signature over the secp256r1 curve.
EcdsaSecp256r1 {
hashed_msg: HeapVector,
public_key_x: HeapArray,
Expand Down
4 changes: 4 additions & 0 deletions acvm-repo/brillig/src/foreign_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ impl<F: AcirField> ForeignCallParam<F> {
}
}

/// Unwrap the field in a `Single` input. Panics if it's an `Array`.
pub fn unwrap_field(&self) -> F {
match self {
ForeignCallParam::Single(value) => *value,
Expand All @@ -45,18 +46,21 @@ pub struct ForeignCallResult<F> {
pub values: Vec<ForeignCallParam<F>>,
}

/// Result of a call returning a one output value.
impl<F> From<F> for ForeignCallResult<F> {
fn from(value: F) -> Self {
ForeignCallResult { values: vec![value.into()] }
}
}

/// Result of a call returning a one output array.
impl<F> From<Vec<F>> for ForeignCallResult<F> {
fn from(values: Vec<F>) -> Self {
ForeignCallResult { values: vec![values.into()] }
}
}

/// Result of a call returning multiple outputs.
impl<F> From<Vec<ForeignCallParam<F>>> for ForeignCallResult<F> {
fn from(values: Vec<ForeignCallParam<F>>) -> Self {
ForeignCallResult { values }
Expand Down
3 changes: 2 additions & 1 deletion acvm-repo/brillig/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod opcodes;
pub use black_box::BlackBoxOp;
pub use foreign_call::{ForeignCallParam, ForeignCallResult};
pub use opcodes::{
BinaryFieldOp, BinaryIntOp, HeapArray, HeapValueType, HeapVector, MemoryAddress, ValueOrArray,
ArrayAddress, BinaryFieldOp, BinaryIntOp, HeapArray, HeapValueType, HeapVector, MemoryAddress,
ValueOrArray, VectorAddress,
};
pub use opcodes::{BitSize, BrilligOpcode as Opcode, IntegerBitSize, Label};
Loading
Loading