From 4c2fdad61b807e7bea8b5306184eb1967ec039d7 Mon Sep 17 00:00:00 2001 From: ohad nir Date: Tue, 11 Feb 2025 11:56:02 +0200 Subject: [PATCH] implement Blake2s opcode in runner --- CHANGELOG.md | 2 + .../blake2s_opcode_test.cairo | 163 ++++++++++++ vm/src/tests/cairo_run_test.rs | 8 + vm/src/types/instruction.rs | 1 + vm/src/vm/decoding/decoder.rs | 65 ++++- vm/src/vm/errors/vm_errors.rs | 4 + vm/src/vm/vm_core.rs | 237 +++++++++++++++++- 7 files changed, 475 insertions(+), 5 deletions(-) create mode 100644 cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index a4189ed073..f55eb9b542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: implement `Blake2s` opcode in VM [#1927](https://github.com/lambdaclass/cairo-vm/pull/1927) + * feat: remove `NonZeroReservedBits` from `VirtualMachineError` [#1948](https://github.com/lambdaclass/cairo-vm/pull/1948) * feat: set `encoded_instruction` to be u128 for opcode_extensions to come [#1940](https://github.com/lambdaclass/cairo-vm/pull/1940) diff --git a/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo b/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo new file mode 100644 index 0000000000..28332ade33 --- /dev/null +++ b/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo @@ -0,0 +1,163 @@ +%builtins range_check bitwise + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import STATE_SIZE_FELTS, INPUT_BLOCK_FELTS, _get_sigma +from starkware.cairo.common.cairo_blake2s.packed_blake2s import N_PACKED_INSTANCES, blake2s_compress +from starkware.cairo.common.cairo_builtins import BitwiseBuiltin + +const COUNTER = 64; +const U32_MASK = 0xffffffff; + +// Tests the Blake2s opcode runner using a preexisting implementation within the repo as reference. +// The initial state, a random message of 64 bytes and counter are used as input. +// Both the opcode and the reference implementation are run on the same inputs and then their outputs are compared. +// Before comparing the outputs, it is verified that the opcode runner has written the output to the correct location. +func main{range_check_ptr, bitwise_ptr: BitwiseBuiltin*}() { + alloc_locals; + + let (local random_message) = alloc(); + assert random_message[0] = 930933030; + assert random_message[1] = 1766240503; + assert random_message[2] = 3660871006; + assert random_message[3] = 388409270; + assert random_message[4] = 1948594622; + assert random_message[5] = 3119396969; + assert random_message[6] = 3924579183; + assert random_message[7] = 2089920034; + assert random_message[8] = 3857888532; + assert random_message[9] = 929304360; + assert random_message[10] = 1810891574; + assert random_message[11] = 860971754; + assert random_message[12] = 1822893775; + assert random_message[13] = 2008495810; + assert random_message[14] = 2958962335; + assert random_message[15] = 2340515744; + + let (local input_state) = alloc(); + // Set the initial state to IV (IV[0] is modified). + assert input_state[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + assert input_state[1] = 0xBB67AE85; + assert input_state[2] = 0x3C6EF372; + assert input_state[3] = 0xA54FF53A; + assert input_state[4] = 0x510E527F; + assert input_state[5] = 0x9B05688C; + assert input_state[6] = 0x1F83D9AB; + assert input_state[7] = 0x5BE0CD19; + static_assert STATE_SIZE_FELTS == 8; + + // Use the packed blake2s_compress to compute the output of the first instance. + let (sigma) = _get_sigma(); + let (local cairo_output) = alloc(); + blake2s_compress( + h=input_state, + message=random_message, + t0=COUNTER, + f0=0, + sigma=sigma, + output=cairo_output, + ); + + // Unpack the first instance of the blake2s_compress output (extract the first 32 bits). + assert bitwise_ptr[0].x = cairo_output[0]; + assert bitwise_ptr[0].y = U32_MASK; + assert bitwise_ptr[1].x = cairo_output[1]; + assert bitwise_ptr[1].y = U32_MASK; + assert bitwise_ptr[2].x = cairo_output[2]; + assert bitwise_ptr[2].y = U32_MASK; + assert bitwise_ptr[3].x = cairo_output[3]; + assert bitwise_ptr[3].y = U32_MASK; + assert bitwise_ptr[4].x = cairo_output[4]; + assert bitwise_ptr[4].y = U32_MASK; + assert bitwise_ptr[5].x = cairo_output[5]; + assert bitwise_ptr[5].y = U32_MASK; + assert bitwise_ptr[6].x = cairo_output[6]; + assert bitwise_ptr[6].y = U32_MASK; + assert bitwise_ptr[7].x = cairo_output[7]; + assert bitwise_ptr[7].y = U32_MASK; + + // Run the blake2s opcode runner on the same inputs and store its output. + let vm_output = run_blake2s( + dst=COUNTER, + op0=input_state, + op1=random_message, + ); + + // Verify that the opcode runner has written the 8 felts to the correct location. + tempvar check_nonempty = vm_output[0]; + tempvar check_nonempty = vm_output[1]; + tempvar check_nonempty = vm_output[2]; + tempvar check_nonempty = vm_output[3]; + tempvar check_nonempty = vm_output[4]; + tempvar check_nonempty = vm_output[5]; + tempvar check_nonempty = vm_output[6]; + tempvar check_nonempty = vm_output[7]; + + // Compare the vm_output to the blake2s_compress first instance output. + assert vm_output[0] = bitwise_ptr[0].x_and_y; + assert vm_output[1] = bitwise_ptr[1].x_and_y; + assert vm_output[2] = bitwise_ptr[2].x_and_y; + assert vm_output[3] = bitwise_ptr[3].x_and_y; + assert vm_output[4] = bitwise_ptr[4].x_and_y; + assert vm_output[5] = bitwise_ptr[5].x_and_y; + assert vm_output[6] = bitwise_ptr[6].x_and_y; + assert vm_output[7] = bitwise_ptr[7].x_and_y; + + let bitwise_ptr = bitwise_ptr + BitwiseBuiltin.SIZE * STATE_SIZE_FELTS; + + return (); +} + +// Forces the runner to execute the Blake2s with the given operands. +// op0 is a pointer to an array of 8 felts as u32 integers of the state. +// op1 is a pointer to an array of 16 felts as u32 integers of the messsage. +// dst is a felt representing a u32 of the counter. +// ap contains a pointer to an array of 8 felts as u32 integers of the output state. +// Those values are stored within addresses fp-5, fp-4 and fp-3 respectively. +// An instruction encoding is built from offsets -5, -4, -3 and flags which are all 0 except for +// those denoting uses of fp as the base for operand addresses and flag_opcode_blake (16th flag). +// The instruction is then written to [pc] and the runner is forced to execute Blake2s. +func run_blake2s( + dst: felt, + op0: felt*, + op1: felt*, +) -> felt* { + alloc_locals; + + // Set the offsets for the operands. + let offset0 = (2**15)-5; + let offset1 = (2**15)-4; + let offset2 = (2**15)-3; + static_assert dst == [fp -5]; + static_assert op0 == [fp -4]; + static_assert op1 == [fp -3]; + + // Set the flags for the instruction. + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 1; + let flag_op1_imm = 0; + let flag_op1_base_fp = 1; + let flag_op1_base_ap = 0; + let flag_res_add = 0; + let flag_res_mul = 0; + let flag_PC_update_jump = 0; + let flag_PC_update_jump_rel = 0; + let flag_PC_update_jnz = 0; + let flag_ap_update_add = 0; + let flag_ap_update_add_1 = 0; + let flag_opcode_call = 0; + let flag_opcode_ret = 0; + let flag_opcode_assert_eq = 0; + let flag_opcode_blake2s = 1; + + // Build the instruction encoding. + let flag_num = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_opcode_blake2s*(2**15); + let instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num*(2**48); + static_assert instruction_num==9226608988349300731; + + // Write the instruction to [pc] and point [ap] to the designated output. + let (local vm_output) = alloc(); + assert [ap] = cast(vm_output, felt); + dw 9226608988349300731; + + return cast([ap], felt*); +} diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c2dea2f4b2..cbce327c08 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -568,6 +568,14 @@ fn blake2s_integration_tests() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn blake2s_opcode_test() { + let program_data = + include_bytes!("../../../cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.json"); + run_program_simple(program_data.as_slice()); +} + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn relocate_segments() { diff --git a/vm/src/types/instruction.rs b/vm/src/types/instruction.rs index 8f598ccd6e..45cfab1f6f 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -80,6 +80,7 @@ pub enum Opcode { #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum OpcodeExtension { Stone, + Blake, } impl Instruction { diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index 5f78a81e02..e395c08795 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -102,6 +102,17 @@ pub fn decode_instruction(encoded_instr: u128) -> Result OpcodeExtension::Stone, + 1 => { + if opcode != Opcode::NOp + || (op1_addr != Op1Addr::FP && op1_addr != Op1Addr::AP) + || res != Res::Op1 + || pc_update != PcUpdate::Regular + || (ap_update_num != 0 && ap_update_num != 2) + { + return Err(VirtualMachineError::InvalidBlake2sFlags(flags & 0x7FFF)); + }; + OpcodeExtension::Blake + } _ => { return Err(VirtualMachineError::InvalidOpcodeExtension( opcode_extension_num, @@ -412,15 +423,61 @@ mod decoder_test { assert_matches!(error, Err(VirtualMachineError::InvalidOpcode(1))); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn decode_opcode_extension_clash() { + // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // Blake| CALL| REGULAR| REGULAR| Op1| FP| AP| AP + // 1 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 + // 1001 0000 0000 1000 = 0x9008; off0 = 1, off1 = 1 + let error = decode_instruction(0x9008800180018001); + assert_matches!(error, Err(VirtualMachineError::InvalidBlake2sFlags(4104))); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn decode_blake_imm() { + // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // Blake| NOP| REGULAR| REGULAR| Op1| IMM| AP| AP + // 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 + // 1000 0000 0000 0100 = 0x8004; off0 = 1, off1 = 1 + let error = decode_instruction(0x8004800180018001); + assert_matches!(error, Err(VirtualMachineError::InvalidBlake2sFlags(4))); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn decode_blake() { + // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // Blake| NOP| ADD1| REGULAR| Op1| AP| FP| FP + // 1 0 0 0 1 0 0 0 0 0 0 1 0 0 1 1 + // 1000 1000 0001 0011 = 0x8813; off0 = 1, off1 = 1 + let inst = decode_instruction(0x8813800180018001).unwrap(); + assert_matches!(inst.opcode, Opcode::NOp); + assert_matches!(inst.off0, 1); + assert_matches!(inst.off1, 1); + assert_matches!(inst.dst_register, Register::FP); + assert_matches!(inst.op0_register, Register::FP); + assert_matches!(inst.op1_addr, Op1Addr::AP); + assert_matches!(inst.res, Res::Op1); + assert_matches!(inst.pc_update, PcUpdate::Regular); + assert_matches!(inst.ap_update, ApUpdate::Add1); + assert_matches!(inst.fp_update, FpUpdate::Regular); + assert_matches!(inst.opcode_extension, OpcodeExtension::Blake); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_invalid_opcode_extension_error() { // opcode_extension| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg // 79 ... 17 16 15| 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 // ???| CALL| Add2| JumpRel| Op1| IMM| FP| FP - // 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 - // 1001 0001 0000 0100 = 0x9104; off0 = 0, off1 = 1 - let error = decode_instruction(0x9104800180018000); - assert_matches!(error, Err(VirtualMachineError::InvalidOpcodeExtension(1))); + // 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 + // 0001 1001 0001 0000 0100 = 0x39104; off0 = 0, off1 = 1 + let error = decode_instruction(0x19104800180018000); + assert_matches!(error, Err(VirtualMachineError::InvalidOpcodeExtension(3))); } } diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index e225f96d5f..9162d591bd 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -136,6 +136,10 @@ pub enum VirtualMachineError { RelocationNotFound(usize), #[error("{} batch size is not {}", (*.0).0, (*.0).1)] ModBuiltinBatchSize(Box<(BuiltinName, usize)>), + #[error("Blake2s opcode invalid operand: op{0} does not point to {1} u32 numbers.")] + Blake2sInvalidOperand(u8, u8), + #[error("Blake2s opcode invalid flags {0}")] + InvalidBlake2sFlags(u128), } #[cfg(test)] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 878953b9ea..d573f65088 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1,10 +1,14 @@ use crate::math_utils::signed_felt; use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; +use crate::types::instruction::OpcodeExtension; #[cfg(feature = "extensive_hints")] use crate::types::program::HintRange; use crate::{ - hint_processor::hint_processor_definition::HintProcessor, + hint_processor::{ + builtin_hint_processor::blake2s_hash::blake2s_compress, + hint_processor_definition::HintProcessor, + }, types::{ errors::math_errors::MathError, exec_scope::ExecutionScopes, @@ -441,12 +445,66 @@ impl VirtualMachine { .memory .mark_as_accessed(operands_addresses.op1_addr); + if instruction.opcode_extension == OpcodeExtension::Blake { + self.handle_blake2s_instruction(&operands_addresses)?; + } + self.update_registers(instruction, operands)?; self.current_step += 1; Ok(()) } + /// Executes a Blake2s instruction. + /// Expects operands to be RelocatableValue and to point to segments of memory. + /// op0 is expected to point to a sequence of 8 u32 values (state). + /// op1 is expected to point to a sequence of 16 u32 values (message). + /// dst is expected hold the u32 value of the counter (t). + /// [ap] is expected to point to a sequence of 8 cells each being either unitialised or + /// containing the Blake2s compression output at that index. + /// Deviation from the aforementioned expectations will result in an error. + /// The instruction will update the memory segment pointed by [ap] with the new state. + /// Note: the byte counter should count the number of message bytes processed so far including + /// the current portion of the message (i.e. it starts at 64, not 0). + fn handle_blake2s_instruction( + &mut self, + operands_addresses: &OperandsAddresses, + ) -> Result<(), VirtualMachineError> { + let counter = self.segments.memory.get_u32(operands_addresses.dst_addr)?; + + let state: [u32; 8] = (self.get_u32_range( + self.segments + .memory + .get_relocatable(operands_addresses.op0_addr)?, + 8, + )?) + .try_into() + .map_err(|_| VirtualMachineError::Blake2sInvalidOperand(0, 8))?; + + let message: [u32; 16] = (self.get_u32_range( + self.segments + .memory + .get_relocatable(operands_addresses.op1_addr)?, + 16, + )?) + .try_into() + .map_err(|_| VirtualMachineError::Blake2sInvalidOperand(1, 16))?; + + let ap = self.run_context.get_ap(); + let output_address = self.segments.memory.get_relocatable(ap)?; + + let new_state = blake2s_compress(&state, &message, counter, 0, 0, 0); + + for (i, &val) in new_state.iter().enumerate() { + self.segments.memory.insert_as_accessed( + (output_address + i)?, + MaybeRelocatable::Int(Felt252::from(val)), + )?; + } + + Ok(()) + } + fn decode_current_instruction(&self) -> Result { let instruction = self .segments @@ -4412,6 +4470,183 @@ mod tests { assert_matches!(vm.get_u32_range((0, 1).into(), 3), Err(MemoryError::UnknownMemoryCell(bx)) if *bx == (0, 2).into()); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn handle_blake2s_instruction_state_too_short() { + let mut vm = vm!(); + vm.segments.memory = memory![ + ((0, 0), 0), + ((0, 1), 0), + ((0, 2), 0), + ((0, 3), 0), + ((0, 4), 0), + ((0, 5), 0), + ((0, 6), 0), + ((2, 0), (0, 0)) + ]; + let operands_addresses = OperandsAddresses { + dst_addr: (0, 0).into(), + op0_addr: (2, 0).into(), + op1_addr: (2, 0).into(), + }; + vm.run_context = RunContext { + pc: (0, 0).into(), + ap: 0, + fp: 0, + }; + + assert_matches!( + vm.handle_blake2s_instruction(&operands_addresses), + Err(VirtualMachineError::Memory(MemoryError::UnknownMemoryCell(bx))) if *bx == (0, 7).into() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn handle_blake2s_instruction_message_too_short() { + let mut vm = vm!(); + vm.segments.memory = memory![ + ((0, 0), 0), + ((0, 1), 0), + ((0, 2), 0), + ((0, 3), 0), + ((0, 4), 0), + ((0, 5), 0), + ((0, 6), 0), + ((0, 7), 0), + ((2, 0), (0, 0)) + ]; + let operands_addresses = OperandsAddresses { + dst_addr: (0, 0).into(), + op0_addr: (2, 0).into(), + op1_addr: (2, 0).into(), + }; + vm.run_context = RunContext { + pc: (0, 0).into(), + ap: 0, + fp: 0, + }; + + assert_matches!( + vm.handle_blake2s_instruction(&operands_addresses), + Err(VirtualMachineError::Memory(MemoryError::UnknownMemoryCell(bx))) if *bx == (0, 8).into() + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn handle_blake2s_instruction_ap_points_to_inconsistent_memory() { + let mut vm = vm!(); + vm.segments.memory = memory![ + ((0, 0), 0), + ((0, 1), 0), + ((0, 2), 0), + ((0, 3), 0), + ((0, 4), 0), + ((0, 5), 0), + ((0, 6), 0), + ((0, 7), 0), + ((0, 8), 0), + ((0, 9), 0), + ((0, 10), 0), + ((0, 11), 0), + ((0, 12), 0), + ((0, 13), 0), + ((0, 14), 0), + ((0, 15), 0), + ((1, 0), (0, 0)) + ]; + let operands_addresses = OperandsAddresses { + dst_addr: (0, 0).into(), + op0_addr: (1, 0).into(), + op1_addr: (1, 0).into(), + }; + vm.run_context = RunContext { + pc: (0, 0).into(), + ap: 0, + fp: 0, + }; + + assert_matches!( + vm.handle_blake2s_instruction(&operands_addresses), + Err(VirtualMachineError::Memory(MemoryError::InconsistentMemory(bx))) if *bx == ((0, 0).into(),0.into(),1848029226.into()) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn handle_blake2s_instruction_ok() { + let mut vm = vm!(); + vm.segments.memory = memory![ + // State + ((0, 0), 0x6B08E647), + ((0, 1), 0xBB67AE85), + ((0, 2), 0x3C6EF372), + ((0, 3), 0xA54FF53A), + ((0, 4), 0x510E527F), + ((0, 5), 0x9B05688C), + ((0, 6), 0x1F83D9AB), + ((0, 7), 0x5BE0CD19), + // Message + ((0, 8), 930933030), + ((0, 9), 1766240503), + ((0, 10), 3660871006), + ((0, 11), 388409270), + ((0, 12), 1948594622), + ((0, 13), 3119396969), + ((0, 14), 3924579183), + ((0, 15), 2089920034), + ((0, 16), 3857888532), + ((0, 17), 929304360), + ((0, 18), 1810891574), + ((0, 19), 860971754), + ((0, 20), 1822893775), + ((0, 21), 2008495810), + ((0, 22), 2958962335), + ((0, 23), 2340515744), + // Counter + ((0, 24), 64), + // AP + ((1, 0), (0, 25)), + ((2, 0), (0, 0)), + ((2, 1), (0, 8)) + ]; + let operands_addresses = OperandsAddresses { + dst_addr: (0, 24).into(), + op0_addr: (2, 0).into(), + op1_addr: (2, 1).into(), + }; + vm.run_context = RunContext { + pc: (0, 0).into(), + ap: 0, + fp: 0, + }; + assert_matches!(vm.handle_blake2s_instruction(&operands_addresses), Ok(())); + + let state: [u32; 8] = vm + .get_u32_range((0, 0).into(), 8) + .unwrap() + .try_into() + .unwrap(); + let message: [u32; 16] = vm + .get_u32_range((0, 8).into(), 16) + .unwrap() + .try_into() + .unwrap(); + let counter = vm.segments.memory.get_u32((0, 24).into()).unwrap(); + + let expected_new_state: [u32; 8] = blake2s_compress(&state, &message, counter, 0, 0, 0) + .try_into() + .unwrap(); + + let new_state: [u32; 8] = vm + .get_u32_range((0, 25).into(), 8) + .unwrap() + .try_into() + .unwrap(); + assert_eq!(new_state, expected_new_state); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn get_traceback_entries_bad_usort() {