From 7091cd84b7b998811485a1d378cc68e79cbe1a5b Mon Sep 17 00:00:00 2001 From: sirasistant Date: Wed, 21 Jun 2023 13:39:26 +0000 Subject: [PATCH 1/5] refactor: extracted memory to its own struct --- brillig_vm/src/lib.rs | 55 +++++++++++++--------------------------- brillig_vm/src/memory.rs | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 brillig_vm/src/memory.rs diff --git a/brillig_vm/src/lib.rs b/brillig_vm/src/lib.rs index e66d968c2..27b861a6b 100644 --- a/brillig_vm/src/lib.rs +++ b/brillig_vm/src/lib.rs @@ -9,10 +9,12 @@ //! [acir]: https://crates.io/crates/acir //! [acvm]: https://crates.io/crates/acvm +mod memory; mod opcodes; mod registers; mod value; +pub use memory::Memory; pub use opcodes::{BinaryFieldOp, BinaryIntOp, RegisterOrMemory}; pub use opcodes::{Label, Opcode}; pub use registers::{RegisterIndex, Registers}; @@ -88,7 +90,7 @@ pub struct VM { /// Status of the VM status: VMStatus, /// Memory of the VM - memory: Vec, + memory: Memory, /// Call stack call_stack: Vec, } @@ -108,7 +110,7 @@ impl VM { foreign_call_results, bytecode, status: VMStatus::InProgress, - memory, + memory: memory.into(), call_stack: Vec::new(), } } @@ -154,7 +156,7 @@ impl VM { } pub fn get_memory(&self) -> &Vec { - &self.memory + self.memory.values() } /// Process a single opcode and modify the program counter. @@ -232,15 +234,8 @@ impl VM { } // Convert the destination pointer to a usize let destination = self.registers.get(*pointer_index).to_usize(); - // Calculate new memory size - let new_size = - std::cmp::max(self.memory.len(), destination + size); - // Expand memory to new size with default values if needed - self.memory.resize(new_size, Value::from(0_usize)); // Write to our destination memory - for (i, value) in values.iter().enumerate() { - self.memory[destination + i] = *value; - } + self.memory.write_slice(destination, values); } _ => { unreachable!("Function result size does not match brillig bytecode size") @@ -254,15 +249,8 @@ impl VM { self.registers.set(*size_index, Value::from(values.len())); // Convert the destination pointer to a usize let destination = self.registers.get(*pointer_index).to_usize(); - // Calculate new memory size - let new_size = - std::cmp::max(self.memory.len(), destination + values.len()); - // Expand memory to new size with default values if needed - self.memory.resize(new_size, Value::from(0_usize)); // Write to our destination memory - for (i, value) in values.iter().enumerate() { - self.memory[destination + i] = *value; - } + self.memory.write_slice(destination, values); } _ => { unreachable!("Function result size does not match brillig bytecode size") @@ -294,21 +282,15 @@ impl VM { // Convert our source_pointer to a usize let source = self.registers.get(*source_pointer); // Use our usize source index to lookup the value in memory - let value = &self.memory[source.to_usize()]; + let value = &self.memory.read(source.to_usize()); self.registers.set(*destination_register, *value); self.increment_program_counter() } Opcode::Store { destination_pointer, source: source_register } => { // Convert our destination_pointer to a usize let destination = self.registers.get(*destination_pointer).to_usize(); - if destination >= self.memory.len() { - self.memory.append(&mut vec![ - Value::from(0_usize); - destination - self.memory.len() + 1 - ]); - } // Use our usize destination index to set the value in memory - self.memory[destination] = self.registers.get(*source_register); + self.memory.write(destination, self.registers.get(*source_register)); self.increment_program_counter() } Opcode::Call { location } => { @@ -352,12 +334,12 @@ impl VM { } RegisterOrMemory::HeapArray(pointer_index, size) => { let start = self.registers.get(pointer_index); - self.memory[start.to_usize()..(start.to_usize() + size)].to_vec() + self.memory.read_slice(start.to_usize(), size).to_vec() } RegisterOrMemory::HeapVector(pointer_index, size_index) => { let start = self.registers.get(pointer_index); let size = self.registers.get(size_index); - self.memory[start.to_usize()..(start.to_usize() + size.to_usize())].to_vec() + self.memory.read_slice(start.to_usize(), size.to_usize()).to_vec() } } } @@ -675,7 +657,7 @@ mod tests { Opcode::JumpIf { condition: r_tmp, location: start.len() }, ]; let vm = brillig_execute_and_get_vm(memory, [&start[..], &loop_body[..]].concat()); - vm.memory + vm.get_memory().clone() } let memory = brillig_write_memory(vec![Value::from(0u128); 5]); @@ -828,7 +810,7 @@ mod tests { ]; let vm = brillig_execute_and_get_vm(memory, [&start[..], &recursive_fn[..]].concat()); - vm.memory + vm.get_memory().clone() } let memory = brillig_recursive_write_memory(vec![Value::from(0u128); 5]); @@ -959,7 +941,7 @@ mod tests { assert_eq!(vm.status, VMStatus::Finished); // Check result in memory - let result_values = vm.memory[0..4].to_vec(); + let result_values = vm.memory.read_slice(0, 4).to_vec(); assert_eq!(result_values, expected_result); // Ensure the foreign call counter has been incremented @@ -1028,8 +1010,7 @@ mod tests { assert_eq!(vm.status, VMStatus::Finished); // Check result in memory - let result_values = - vm.memory[input_string.len()..(input_string.len() + output_string.len())].to_vec(); + let result_values = vm.memory.read_slice(input_string.len(), output_string.len()).to_vec(); assert_eq!(result_values, output_string); // Ensure the foreign call counter has been incremented @@ -1083,11 +1064,11 @@ mod tests { assert_eq!(vm.status, VMStatus::Finished); // Check initial memory still in place - let initial_values = vm.memory[0..4].to_vec(); + let initial_values = vm.memory.read_slice(0, 4).to_vec(); assert_eq!(initial_values, initial_matrix); // Check result in memory - let result_values = vm.memory[4..8].to_vec(); + let result_values = vm.memory.read_slice(4, 4).to_vec(); assert_eq!(result_values, expected_result); // Ensure the foreign call counter has been incremented @@ -1159,7 +1140,7 @@ mod tests { assert_eq!(vm.status, VMStatus::Finished); // Check result in memory - let result_values = vm.memory[0..4].to_vec(); + let result_values = vm.memory.read_slice(0, 4).to_vec(); assert_eq!(result_values, expected_result); // Ensure the foreign call counter has been incremented diff --git a/brillig_vm/src/memory.rs b/brillig_vm/src/memory.rs new file mode 100644 index 000000000..e23095372 --- /dev/null +++ b/brillig_vm/src/memory.rs @@ -0,0 +1,45 @@ +use crate::Value; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Memory { + // Memory is a vector of values. + // We grow the memory when values past the end are set, extending with 0s. + inner: Vec, +} + +impl From> for Memory { + fn from(values: Vec) -> Self { + Memory { inner: values } + } +} + +impl Memory { + /// Gets the value at pointer + pub fn read(&self, ptr: usize) -> Value { + self.inner[ptr] + } + + pub fn read_slice(&self, ptr: usize, len: usize) -> &[Value] { + &self.inner[ptr..ptr + len] + } + + /// Sets the value at pointer `ptr` to `value` + pub fn write(&mut self, ptr: usize, value: Value) { + self.write_slice(ptr, &[value]); + } + + /// Sets the values after pointer `ptr` to `values` + pub fn write_slice(&mut self, ptr: usize, values: &[Value]) { + // Calculate new memory size + let new_size = std::cmp::max(self.inner.len(), ptr + values.len()); + // Expand memory to new size with default values if needed + self.inner.resize(new_size, Value::from(0_usize)); + + self.inner[ptr..ptr + values.len()].copy_from_slice(values); + } + + /// Returns the values of the memory + pub fn values(&self) -> &Vec { + &self.inner + } +} From f3c9af494a51f8de2d7f4315f5c55d5f69deb366 Mon Sep 17 00:00:00 2001 From: sirasistant Date: Thu, 22 Jun 2023 07:03:01 +0000 Subject: [PATCH 2/5] feat: implemented the first blackbox functions --- brillig_vm/Cargo.toml | 10 ++ brillig_vm/src/black_box.rs | 206 ++++++++++++++++++++++++++++++++++++ brillig_vm/src/lib.rs | 64 ++++++++--- brillig_vm/src/opcodes.rs | 26 ++++- 4 files changed, 287 insertions(+), 19 deletions(-) create mode 100644 brillig_vm/src/black_box.rs diff --git a/brillig_vm/Cargo.toml b/brillig_vm/Cargo.toml index c5dae5edb..e16d9a983 100644 --- a/brillig_vm/Cargo.toml +++ b/brillig_vm/Cargo.toml @@ -13,6 +13,16 @@ repository.workspace = true [dependencies] acir_field.workspace = true serde.workspace = true +blake2 = "0.10.6" +sha2 = "0.10.6" +sha3 = "0.10.6" +k256 = { version = "0.11.0", features = [ + "ecdsa", + "ecdsa-core", + "sha256", + "digest", + "arithmetic", +] } [features] default = ["bn254"] diff --git a/brillig_vm/src/black_box.rs b/brillig_vm/src/black_box.rs new file mode 100644 index 000000000..101aa4166 --- /dev/null +++ b/brillig_vm/src/black_box.rs @@ -0,0 +1,206 @@ +use crate::{memory::Memory, opcodes::HeapVector, HeapArray, RegisterIndex, Registers, Value}; +use acir_field::FieldElement; +use blake2::digest::generic_array::GenericArray; +use blake2::{Blake2s256, Digest}; +use k256::elliptic_curve::sec1::FromEncodedPoint; +use k256::elliptic_curve::PrimeField; +use serde::{Deserialize, Serialize}; +use sha2::Sha256; +use sha3::Keccak256; + +use k256::{ecdsa::Signature, Scalar}; +use k256::{ + elliptic_curve::{ + sec1::{Coordinates, ToEncodedPoint}, + IsHigh, + }, + AffinePoint, EncodedPoint, ProjectivePoint, PublicKey, +}; + +/// Converts an array of Values to an array of u8s. +fn to_u8_vec(inputs: &[Value]) -> Vec { + let mut result = Vec::with_capacity(inputs.len()); + for input in inputs { + let field_bytes = input.to_field().to_be_bytes(); + let byte = field_bytes.last().unwrap(); + result.push(*byte); + } + result +} + +/// Does a generic hash of the inputs storing the resulting 32 bytes as items in the output array. +fn generic_hash_256( + message: &HeapVector, + output: &HeapArray, + registers: &mut Registers, + memory: &mut Memory, +) { + let message_values = memory.read_slice( + registers.get(message.pointer).to_usize(), + registers.get(message.size).to_usize(), + ); + let message_bytes = to_u8_vec(message_values); + + assert!(output.size == 32, "Expected a 32-element result array"); + + let output_bytes: [u8; 32] = + D::digest(message_bytes).as_slice().try_into().expect("digest should be 256 bits"); + let output_values: Vec = output_bytes.iter().map(|b| (*b as u128).into()).collect(); + + memory.write_slice(registers.get(output.pointer).to_usize(), &output_values); +} + +/// Does a generic hash of the inputs storing the resulting hash into the output register. +fn generic_hash_to_field( + message: &HeapVector, + output: &RegisterIndex, + registers: &mut Registers, + memory: &mut Memory, +) { + let message_values = memory.read_slice( + registers.get(message.pointer).to_usize(), + registers.get(message.size).to_usize(), + ); + let message_bytes = to_u8_vec(message_values); + + let output_bytes: [u8; 32] = + D::digest(message_bytes).as_slice().try_into().expect("digest should be 256 bits"); + + let reduced_res = FieldElement::from_be_bytes_reduce(&output_bytes); + + registers.set(*output, reduced_res.into()); +} + +// TODO: remove from here and use the one from acvm +fn verify_secp256k1_ecdsa_signature( + hashed_msg: &[u8], + public_key_x_bytes: &[u8; 32], + public_key_y_bytes: &[u8; 32], + signature: &[u8; 64], +) -> bool { + // Convert the inputs into k256 data structures + + let signature = Signature::try_from(signature.as_slice()).unwrap(); + + let point = EncodedPoint::from_affine_coordinates( + public_key_x_bytes.into(), + public_key_y_bytes.into(), + true, + ); + let pubkey = PublicKey::from_encoded_point(&point).unwrap(); + + let z = Scalar::from_repr(*GenericArray::from_slice(hashed_msg)).unwrap(); + + // Finished converting bytes into data structures + + let r = signature.r(); + let s = signature.s(); + + // Ensure signature is "low S" normalized ala BIP 0062 + if s.is_high().into() { + return false; + } + + let s_inv = s.invert().unwrap(); + let u1 = z * s_inv; + let u2 = *r * s_inv; + + #[allow(non_snake_case)] + let R: AffinePoint = ((ProjectivePoint::GENERATOR * u1) + + (ProjectivePoint::from(*pubkey.as_affine()) * u2)) + .to_affine(); + + match R.to_encoded_point(false).coordinates() { + Coordinates::Uncompressed { x, y: _ } => Scalar::from_repr(*x).unwrap().eq(&r), + _ => unreachable!("Point is uncompressed"), + } +} + +/// 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)] +pub enum BlackBoxOp { + /// Calculates the SHA256 hash of the inputs. + Sha256 { message: HeapVector, output: HeapArray }, + /// Calculates the Blake2s hash of the inputs. + Blake2s { message: HeapVector, output: HeapArray }, + /// Calculates the Keccak256 hash of the inputs. + Keccak256 { message: HeapVector, output: HeapArray }, + /// Hashes a set of inputs and applies the field modulus to the result + /// to return a value which can be represented as a [`FieldElement`][acir_field::FieldElement] + /// + /// This is implemented using the `Blake2s` hash function. + /// The "128" in the name specifies that this function should have 128 bits of security. + HashToField128Security { message: HeapVector, output: RegisterIndex }, + /// Verifies a ECDSA signature over the secp256k1 curve. + EcdsaSecp256k1 { + hashed_msg: HeapVector, + public_key_x: HeapArray, + public_key_y: HeapArray, + signature: HeapArray, + result: RegisterIndex, + }, +} + +impl BlackBoxOp { + pub(crate) fn evaluate(&self, registers: &mut Registers, memory: &mut Memory) { + match self { + BlackBoxOp::Sha256 { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::Blake2s { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::Keccak256 { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::HashToField128Security { message, output } => { + generic_hash_to_field::(message, output, registers, memory); + } + BlackBoxOp::EcdsaSecp256k1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result: result_register, + } => { + let message_values = memory.read_slice( + registers.get(hashed_msg.pointer).to_usize(), + registers.get(hashed_msg.size).to_usize(), + ); + let message_bytes = to_u8_vec(message_values); + + let public_key_x_bytes: [u8; 32] = + to_u8_vec(memory.read_slice( + registers.get(public_key_x.pointer).to_usize(), + public_key_x.size, + )) + .try_into() + .expect("Expected a 32-element public key x array"); + + let public_key_y_bytes: [u8; 32] = + to_u8_vec(memory.read_slice( + registers.get(public_key_y.pointer).to_usize(), + public_key_y.size, + )) + .try_into() + .expect("Expected a 32-element public key y array"); + + let signature_bytes: [u8; 64] = to_u8_vec( + memory.read_slice(registers.get(signature.pointer).to_usize(), signature.size), + ) + .try_into() + .expect("Expected a 64-element signature array"); + + let result = verify_secp256k1_ecdsa_signature( + &message_bytes, + &public_key_x_bytes, + &public_key_y_bytes, + &signature_bytes, + ); + + registers.set(*result_register, (result as u128).into()) + } + } + } +} diff --git a/brillig_vm/src/lib.rs b/brillig_vm/src/lib.rs index 27b861a6b..f9495692c 100644 --- a/brillig_vm/src/lib.rs +++ b/brillig_vm/src/lib.rs @@ -9,18 +9,20 @@ //! [acir]: https://crates.io/crates/acir //! [acvm]: https://crates.io/crates/acvm +mod black_box; mod memory; mod opcodes; mod registers; mod value; pub use memory::Memory; -pub use opcodes::{BinaryFieldOp, BinaryIntOp, RegisterOrMemory}; +pub use opcodes::{BinaryFieldOp, BinaryIntOp, RegisterOrMemory, HeapArray, HeapVector}; pub use opcodes::{Label, Opcode}; pub use registers::{RegisterIndex, Registers}; use serde::{Deserialize, Serialize}; pub use value::Typ; pub use value::Value; +pub use black_box::BlackBoxOp; #[derive(Debug, PartialEq, Eq, Clone)] pub enum VMStatus { @@ -225,7 +227,7 @@ impl VM { "Function result size does not match brillig bytecode (expected 1 result)" ), }, - RegisterOrMemory::HeapArray(pointer_index, size) => { + RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { match output { ForeignCallOutput::Array(values) => { if values.len() != *size { @@ -242,7 +244,7 @@ impl VM { } } } - RegisterOrMemory::HeapVector(pointer_index, size_index) => { + RegisterOrMemory::HeapVector(HeapVector { pointer: pointer_index, size: size_index }) => { match output { ForeignCallOutput::Array(values) => { // Set our size in the size register @@ -302,6 +304,10 @@ impl VM { self.registers.set(*destination, *value); self.increment_program_counter() } + Opcode::BlackBox(black_box_op) => { + black_box_op.evaluate(&mut self.registers, &mut self.memory); + self.increment_program_counter() + } } } @@ -332,11 +338,14 @@ impl VM { RegisterOrMemory::RegisterIndex(value_index) => { vec![self.registers.get(value_index)] } - RegisterOrMemory::HeapArray(pointer_index, size) => { + RegisterOrMemory::HeapArray(HeapArray { pointer: pointer_index, size }) => { let start = self.registers.get(pointer_index); self.memory.read_slice(start.to_usize(), size).to_vec() } - RegisterOrMemory::HeapVector(pointer_index, size_index) => { + RegisterOrMemory::HeapVector(HeapVector { + pointer: pointer_index, + size: size_index, + }) => { let start = self.registers.get(pointer_index); let size = self.registers.get(size_index); self.memory.read_slice(start.to_usize(), size.to_usize()).to_vec() @@ -915,8 +924,14 @@ mod tests { // *output = matrix_2x2_transpose(*input) Opcode::ForeignCall { function: "matrix_2x2_transpose".into(), - destinations: vec![RegisterOrMemory::HeapArray(r_output, initial_matrix.len())], - inputs: vec![RegisterOrMemory::HeapArray(r_input, initial_matrix.len())], + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: initial_matrix.len(), + })], + inputs: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input, + size: initial_matrix.len(), + })], }, ]; @@ -982,8 +997,14 @@ mod tests { // output_pointer[0..output_size] = string_double(input_pointer[0...input_size]) Opcode::ForeignCall { function: "string_double".into(), - destinations: vec![RegisterOrMemory::HeapVector(r_output_pointer, r_output_size)], - inputs: vec![RegisterOrMemory::HeapVector(r_input_pointer, r_input_size)], + destinations: vec![RegisterOrMemory::HeapVector(HeapVector { + pointer: r_output_pointer, + size: r_output_size, + })], + inputs: vec![RegisterOrMemory::HeapVector(HeapVector { + pointer: r_input_pointer, + size: r_input_size, + })], }, ]; @@ -1038,8 +1059,14 @@ mod tests { // *output = matrix_2x2_transpose(*input) Opcode::ForeignCall { function: "matrix_2x2_transpose".into(), - destinations: vec![RegisterOrMemory::HeapArray(r_output, initial_matrix.len())], - inputs: vec![RegisterOrMemory::HeapArray(r_input, initial_matrix.len())], + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: initial_matrix.len(), + })], + inputs: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input, + size: initial_matrix.len(), + })], }, ]; @@ -1110,10 +1137,19 @@ mod tests { // *output = matrix_2x2_transpose(*input) Opcode::ForeignCall { function: "matrix_2x2_transpose".into(), - destinations: vec![RegisterOrMemory::HeapArray(r_output, matrix_a.len())], + destinations: vec![RegisterOrMemory::HeapArray(HeapArray { + pointer: r_output, + size: matrix_a.len(), + })], inputs: vec![ - RegisterOrMemory::HeapArray(r_input_a, matrix_a.len()), - RegisterOrMemory::HeapArray(r_input_b, matrix_b.len()), + RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input_a, + size: matrix_a.len(), + }), + RegisterOrMemory::HeapArray(HeapArray { + pointer: r_input_b, + size: matrix_b.len(), + }), ], }, ]; diff --git a/brillig_vm/src/opcodes.rs b/brillig_vm/src/opcodes.rs index cd531934a..be3d2ad40 100644 --- a/brillig_vm/src/opcodes.rs +++ b/brillig_vm/src/opcodes.rs @@ -1,9 +1,23 @@ -use crate::{RegisterIndex, Value}; +use crate::{black_box::BlackBoxOp, RegisterIndex, Value}; use acir_field::FieldElement; use serde::{Deserialize, Serialize}; pub type Label = usize; +/// A fix-sized array passed starting from a Brillig register memory location. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub struct HeapArray { + pub pointer: RegisterIndex, + pub size: usize, +} + +/// A register-sized vector passed starting from a Brillig register memory location and with a register-held size +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub struct HeapVector { + pub pointer: RegisterIndex, + pub size: RegisterIndex, +} + /// Lays out various ways an external foreign call's input and output data may be interpreted inside Brillig. /// This data can either be an individual register value or memory. /// @@ -17,14 +31,14 @@ pub enum RegisterOrMemory { /// For a foreign call input, the value is read directly from the register. /// For a foreign call output, the value is written directly to the register. RegisterIndex(RegisterIndex), - /// A fix-sized array passed starting from a Brillig register memory location. + /// An array passed to or from an external call /// In the case of a foreign call input, the array is read from this Brillig memory location + usize more cells. /// In the case of a foreign call output, the array is written to this Brillig memory location with the usize being here just as a sanity check for the size write. - HeapArray(RegisterIndex, usize), - /// A register-sized vector passed starting from a Brillig register memory location and with a register-held size + HeapArray(HeapArray), + /// A vector passed to or from an external call /// In the case of a foreign call input, the vector is read from this Brillig memory location + as many cells as the 2nd register indicates. /// In the case of a foreign call output, the vector is written to this Brillig memory location and as 'size' cells, with size being stored in the second register. - HeapVector(RegisterIndex, RegisterIndex), + HeapVector(HeapVector), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -97,6 +111,7 @@ pub enum Opcode { destination_pointer: RegisterIndex, source: RegisterIndex, }, + BlackBox(BlackBoxOp), /// Used to denote execution failure Trap, /// Stop execution @@ -118,6 +133,7 @@ impl Opcode { Opcode::Mov { .. } => "mov", Opcode::Load { .. } => "load", Opcode::Store { .. } => "store", + Opcode::BlackBox(_) => "black_box", Opcode::Trap => "trap", Opcode::Stop => "stop", } From 53aa18a7aee8b0c8b86bc083018da1ab2b95e58e Mon Sep 17 00:00:00 2001 From: sirasistant Date: Thu, 22 Jun 2023 08:56:18 +0000 Subject: [PATCH 3/5] refactor: fmt and restructuring --- brillig_vm/src/black_box.rs | 191 ++++++++++++++++++------------------ brillig_vm/src/lib.rs | 4 +- 2 files changed, 100 insertions(+), 95 deletions(-) diff --git a/brillig_vm/src/black_box.rs b/brillig_vm/src/black_box.rs index 101aa4166..94adb2cdc 100644 --- a/brillig_vm/src/black_box.rs +++ b/brillig_vm/src/black_box.rs @@ -17,7 +17,96 @@ use k256::{ AffinePoint, EncodedPoint, ProjectivePoint, PublicKey, }; -/// Converts an array of Values to an array of u8s. +/// 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)] +pub enum BlackBoxOp { + /// Calculates the SHA256 hash of the inputs. + Sha256 { message: HeapVector, output: HeapArray }, + /// Calculates the Blake2s hash of the inputs. + Blake2s { message: HeapVector, output: HeapArray }, + /// Calculates the Keccak256 hash of the inputs. + Keccak256 { message: HeapVector, output: HeapArray }, + /// Hashes a set of inputs and applies the field modulus to the result + /// to return a value which can be represented as a [`FieldElement`][acir_field::FieldElement] + /// + /// This is implemented using the `Blake2s` hash function. + /// The "128" in the name specifies that this function should have 128 bits of security. + HashToField128Security { message: HeapVector, output: RegisterIndex }, + /// Verifies a ECDSA signature over the secp256k1 curve. + EcdsaSecp256k1 { + hashed_msg: HeapVector, + public_key_x: HeapArray, + public_key_y: HeapArray, + signature: HeapArray, + result: RegisterIndex, + }, +} + +impl BlackBoxOp { + pub(crate) fn evaluate(&self, registers: &mut Registers, memory: &mut Memory) { + match self { + BlackBoxOp::Sha256 { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::Blake2s { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::Keccak256 { message, output } => { + generic_hash_256::(message, output, registers, memory); + } + BlackBoxOp::HashToField128Security { message, output } => { + generic_hash_to_field::(message, output, registers, memory); + } + BlackBoxOp::EcdsaSecp256k1 { + hashed_msg, + public_key_x, + public_key_y, + signature, + result: result_register, + } => { + let message_values = memory.read_slice( + registers.get(hashed_msg.pointer).to_usize(), + registers.get(hashed_msg.size).to_usize(), + ); + let message_bytes = to_u8_vec(message_values); + + let public_key_x_bytes: [u8; 32] = + to_u8_vec(memory.read_slice( + registers.get(public_key_x.pointer).to_usize(), + public_key_x.size, + )) + .try_into() + .expect("Expected a 32-element public key x array"); + + let public_key_y_bytes: [u8; 32] = + to_u8_vec(memory.read_slice( + registers.get(public_key_y.pointer).to_usize(), + public_key_y.size, + )) + .try_into() + .expect("Expected a 32-element public key y array"); + + let signature_bytes: [u8; 64] = to_u8_vec( + memory.read_slice(registers.get(signature.pointer).to_usize(), signature.size), + ) + .try_into() + .expect("Expected a 64-element signature array"); + + let result = verify_secp256k1_ecdsa_signature( + &message_bytes, + &public_key_x_bytes, + &public_key_y_bytes, + &signature_bytes, + ); + + registers.set(*result_register, (result as u128).into()) + } + } + } +} + +/// Extracts the last byte of every value fn to_u8_vec(inputs: &[Value]) -> Vec { let mut result = Vec::with_capacity(inputs.len()); for input in inputs { @@ -50,7 +139,7 @@ fn generic_hash_256( memory.write_slice(registers.get(output.pointer).to_usize(), &output_values); } -/// Does a generic hash of the inputs storing the resulting hash into the output register. +/// Does a generic hash of the entire inputs storing the resulting hash into a single output register. fn generic_hash_to_field( message: &HeapVector, output: &RegisterIndex, @@ -61,7 +150,12 @@ fn generic_hash_to_field( registers.get(message.pointer).to_usize(), registers.get(message.size).to_usize(), ); - let message_bytes = to_u8_vec(message_values); + let mut message_bytes = Vec::new(); + + for value in message_values { + let field_bytes = value.to_field().to_be_bytes(); + message_bytes.extend_from_slice(&field_bytes); + } let output_bytes: [u8; 32] = D::digest(message_bytes).as_slice().try_into().expect("digest should be 256 bits"); @@ -71,7 +165,7 @@ fn generic_hash_to_field( registers.set(*output, reduced_res.into()); } -// TODO: remove from here and use the one from acvm +// TODO(https://github.com/noir-lang/acvm/issues/402): remove from here and use the one from acvm fn verify_secp256k1_ecdsa_signature( hashed_msg: &[u8], public_key_x_bytes: &[u8; 32], @@ -115,92 +209,3 @@ fn verify_secp256k1_ecdsa_signature( _ => unreachable!("Point is uncompressed"), } } - -/// 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)] -pub enum BlackBoxOp { - /// Calculates the SHA256 hash of the inputs. - Sha256 { message: HeapVector, output: HeapArray }, - /// Calculates the Blake2s hash of the inputs. - Blake2s { message: HeapVector, output: HeapArray }, - /// Calculates the Keccak256 hash of the inputs. - Keccak256 { message: HeapVector, output: HeapArray }, - /// Hashes a set of inputs and applies the field modulus to the result - /// to return a value which can be represented as a [`FieldElement`][acir_field::FieldElement] - /// - /// This is implemented using the `Blake2s` hash function. - /// The "128" in the name specifies that this function should have 128 bits of security. - HashToField128Security { message: HeapVector, output: RegisterIndex }, - /// Verifies a ECDSA signature over the secp256k1 curve. - EcdsaSecp256k1 { - hashed_msg: HeapVector, - public_key_x: HeapArray, - public_key_y: HeapArray, - signature: HeapArray, - result: RegisterIndex, - }, -} - -impl BlackBoxOp { - pub(crate) fn evaluate(&self, registers: &mut Registers, memory: &mut Memory) { - match self { - BlackBoxOp::Sha256 { message, output } => { - generic_hash_256::(message, output, registers, memory); - } - BlackBoxOp::Blake2s { message, output } => { - generic_hash_256::(message, output, registers, memory); - } - BlackBoxOp::Keccak256 { message, output } => { - generic_hash_256::(message, output, registers, memory); - } - BlackBoxOp::HashToField128Security { message, output } => { - generic_hash_to_field::(message, output, registers, memory); - } - BlackBoxOp::EcdsaSecp256k1 { - hashed_msg, - public_key_x, - public_key_y, - signature, - result: result_register, - } => { - let message_values = memory.read_slice( - registers.get(hashed_msg.pointer).to_usize(), - registers.get(hashed_msg.size).to_usize(), - ); - let message_bytes = to_u8_vec(message_values); - - let public_key_x_bytes: [u8; 32] = - to_u8_vec(memory.read_slice( - registers.get(public_key_x.pointer).to_usize(), - public_key_x.size, - )) - .try_into() - .expect("Expected a 32-element public key x array"); - - let public_key_y_bytes: [u8; 32] = - to_u8_vec(memory.read_slice( - registers.get(public_key_y.pointer).to_usize(), - public_key_y.size, - )) - .try_into() - .expect("Expected a 32-element public key y array"); - - let signature_bytes: [u8; 64] = to_u8_vec( - memory.read_slice(registers.get(signature.pointer).to_usize(), signature.size), - ) - .try_into() - .expect("Expected a 64-element signature array"); - - let result = verify_secp256k1_ecdsa_signature( - &message_bytes, - &public_key_x_bytes, - &public_key_y_bytes, - &signature_bytes, - ); - - registers.set(*result_register, (result as u128).into()) - } - } - } -} diff --git a/brillig_vm/src/lib.rs b/brillig_vm/src/lib.rs index f9495692c..d3ecd5c2a 100644 --- a/brillig_vm/src/lib.rs +++ b/brillig_vm/src/lib.rs @@ -15,14 +15,14 @@ mod opcodes; mod registers; mod value; +pub use black_box::BlackBoxOp; pub use memory::Memory; -pub use opcodes::{BinaryFieldOp, BinaryIntOp, RegisterOrMemory, HeapArray, HeapVector}; +pub use opcodes::{BinaryFieldOp, BinaryIntOp, HeapArray, HeapVector, RegisterOrMemory}; pub use opcodes::{Label, Opcode}; pub use registers::{RegisterIndex, Registers}; use serde::{Deserialize, Serialize}; pub use value::Typ; pub use value::Value; -pub use black_box::BlackBoxOp; #[derive(Debug, PartialEq, Eq, Clone)] pub enum VMStatus { From adaf745859ea77d61769ecb1db6e541b77e4c60b Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 26 Jun 2023 09:31:30 +0000 Subject: [PATCH 4/5] refactor: addressed peer review --- Cargo.toml | 10 ++++++++ acvm/Cargo.toml | 14 +++-------- brillig_vm/Cargo.toml | 14 +++-------- brillig_vm/src/black_box.rs | 50 ++++++++++++++++++++++++++++++++++++- brillig_vm/src/opcodes.rs | 2 +- 5 files changed, 68 insertions(+), 22 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 95246a7fe..7f7f01655 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,13 @@ num-traits = "0.2" thiserror = "1.0.21" serde = { version = "1.0.136", features = ["derive"] } +blake2 = "0.10.6" +sha2 = "0.10.6" +sha3 = "0.10.6" +k256 = { version = "0.11.0", features = [ + "ecdsa", + "ecdsa-core", + "sha256", + "digest", + "arithmetic", +] } \ No newline at end of file diff --git a/acvm/Cargo.toml b/acvm/Cargo.toml index 1ddf2a6a1..92a2eeffe 100644 --- a/acvm/Cargo.toml +++ b/acvm/Cargo.toml @@ -18,16 +18,10 @@ thiserror.workspace = true acir.workspace = true stdlib.workspace = true -blake2 = "0.10.6" -sha2 = "0.10.6" -sha3 = "0.10.6" -k256 = { version = "0.11.0", features = [ - "ecdsa", - "ecdsa-core", - "sha256", - "digest", - "arithmetic", -] } +blake2.workspace = true +sha2.workspace = true +sha3.workspace = true +k256.workspace = true indexmap = "1.7.0" async-trait = "0.1" diff --git a/brillig_vm/Cargo.toml b/brillig_vm/Cargo.toml index e16d9a983..59f294446 100644 --- a/brillig_vm/Cargo.toml +++ b/brillig_vm/Cargo.toml @@ -13,16 +13,10 @@ repository.workspace = true [dependencies] acir_field.workspace = true serde.workspace = true -blake2 = "0.10.6" -sha2 = "0.10.6" -sha3 = "0.10.6" -k256 = { version = "0.11.0", features = [ - "ecdsa", - "ecdsa-core", - "sha256", - "digest", - "arithmetic", -] } +blake2.workspace = true +sha2.workspace = true +sha3.workspace = true +k256.workspace = true [features] default = ["bn254"] diff --git a/brillig_vm/src/black_box.rs b/brillig_vm/src/black_box.rs index 94adb2cdc..3c83de54a 100644 --- a/brillig_vm/src/black_box.rs +++ b/brillig_vm/src/black_box.rs @@ -106,6 +106,54 @@ impl BlackBoxOp { } } +#[cfg(test)] +mod test { + use crate::{BlackBoxOp, HeapArray, HeapVector, Memory, Registers, Value, black_box::to_u8_vec}; + + fn to_value_vec(input: &[u8]) -> Vec { + input.iter().map(|x| Value::from(*x as usize)).collect() + } + + #[test] + fn sha256() { + let message: Vec = b"hello world".to_vec(); + let message_length = message.len(); + + let mut memory = Memory::from(vec![]); + let message_pointer = 0; + let result_pointer = message_pointer + message_length; + memory.write_slice( + message_pointer, + to_value_vec(&message).as_slice(), + ); + + let mut registers = Registers { + inner: vec![ + Value::from(message_pointer), + Value::from(message_length), + Value::from(result_pointer), + ], + }; + + let op = BlackBoxOp::Sha256 { + message: HeapVector { pointer: 0.into(), size: 1.into() }, + output: HeapArray { pointer: 2.into(), size: 32 }, + }; + + op.evaluate(&mut registers, &mut memory); + + let result = memory.read_slice(result_pointer, 32); + + assert_eq!( + to_u8_vec(result), + vec![ + 185, 77, 39, 185, 147, 77, 62, 8, 165, 46, 82, 215, 218, 125, 171, 250, 196, 132, + 239, 227, 122, 83, 128, 238, 144, 136, 247, 172, 226, 239, 205, 233 + ] + ); + } +} + /// Extracts the last byte of every value fn to_u8_vec(inputs: &[Value]) -> Vec { let mut result = Vec::with_capacity(inputs.len()); @@ -121,7 +169,7 @@ fn to_u8_vec(inputs: &[Value]) -> Vec { fn generic_hash_256( message: &HeapVector, output: &HeapArray, - registers: &mut Registers, + registers: &Registers, memory: &mut Memory, ) { let message_values = memory.read_slice( diff --git a/brillig_vm/src/opcodes.rs b/brillig_vm/src/opcodes.rs index be3d2ad40..01af6c1bd 100644 --- a/brillig_vm/src/opcodes.rs +++ b/brillig_vm/src/opcodes.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; pub type Label = usize; -/// A fix-sized array passed starting from a Brillig register memory location. +/// A fixed-sized array starting from a Brillig register memory location. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)] pub struct HeapArray { pub pointer: RegisterIndex, From 983af846a286dc6a0f72d042a64bda2e9360bedd Mon Sep 17 00:00:00 2001 From: sirasistant Date: Mon, 26 Jun 2023 09:43:36 +0000 Subject: [PATCH 5/5] refactor: restrict mut --- brillig_vm/src/black_box.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/brillig_vm/src/black_box.rs b/brillig_vm/src/black_box.rs index 3c83de54a..5214b4863 100644 --- a/brillig_vm/src/black_box.rs +++ b/brillig_vm/src/black_box.rs @@ -108,7 +108,9 @@ impl BlackBoxOp { #[cfg(test)] mod test { - use crate::{BlackBoxOp, HeapArray, HeapVector, Memory, Registers, Value, black_box::to_u8_vec}; + use crate::{ + black_box::to_u8_vec, BlackBoxOp, HeapArray, HeapVector, Memory, Registers, Value, + }; fn to_value_vec(input: &[u8]) -> Vec { input.iter().map(|x| Value::from(*x as usize)).collect() @@ -122,10 +124,7 @@ mod test { let mut memory = Memory::from(vec![]); let message_pointer = 0; let result_pointer = message_pointer + message_length; - memory.write_slice( - message_pointer, - to_value_vec(&message).as_slice(), - ); + memory.write_slice(message_pointer, to_value_vec(&message).as_slice()); let mut registers = Registers { inner: vec![ @@ -192,7 +191,7 @@ fn generic_hash_to_field( message: &HeapVector, output: &RegisterIndex, registers: &mut Registers, - memory: &mut Memory, + memory: &Memory, ) { let message_values = memory.read_slice( registers.get(message.pointer).to_usize(),