From 4919048b2a831a0471490ed57adb9da35d9f3675 Mon Sep 17 00:00:00 2001 From: ohad nir Date: Wed, 29 Jan 2025 16:33:13 +0200 Subject: [PATCH] implement Blake2sLastBlock opcode in runner --- CHANGELOG.md | 2 + cairo-vm-tracer/src/tracer_data.rs | 2 +- .../blake2s_last_block_opcode_test.cairo | 136 ++++++++++++++++++ .../blake2s_opcode_test.cairo | 3 +- vm/src/tests/cairo_run_test.rs | 9 ++ vm/src/types/instruction.rs | 7 +- vm/src/vm/decoding/decoder.rs | 112 ++++++++------- vm/src/vm/errors/vm_errors.rs | 12 +- vm/src/vm/vm_core.rs | 52 ++++--- 9 files changed, 258 insertions(+), 77 deletions(-) create mode 100644 cairo_programs/stwo_exclusive_programs/blake2s_last_block_opcode_test.cairo diff --git a/CHANGELOG.md b/CHANGELOG.md index 5291a4a615..55fdeb2149 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: implement `Blake2sLastBlock` opcode in VM [#1932](https://github.com/lambdaclass/cairo-vm/pull/1932) + * feat: implement `Blake2s` opcode in VM [#1927](https://github.com/lambdaclass/cairo-vm/pull/1927) * refactor: Limit ret opcode decodeing to Cairo0's standards. [#1925](https://github.com/lambdaclass/cairo-vm/pull/1925) diff --git a/cairo-vm-tracer/src/tracer_data.rs b/cairo-vm-tracer/src/tracer_data.rs index e2b02cfb23..6b739c3ba3 100644 --- a/cairo-vm-tracer/src/tracer_data.rs +++ b/cairo-vm-tracer/src/tracer_data.rs @@ -143,7 +143,7 @@ impl TracerData { let (instruction_encoding, _) = get_instruction_encoding(entry.pc, &memory, program.prime())?; - let instruction_encoding = instruction_encoding.to_u64(); + let instruction_encoding = instruction_encoding.to_u128(); if instruction_encoding.is_none() { return Err(TraceDataError::FailedToConvertInstructionEncoding); } diff --git a/cairo_programs/stwo_exclusive_programs/blake2s_last_block_opcode_test.cairo b/cairo_programs/stwo_exclusive_programs/blake2s_last_block_opcode_test.cairo new file mode 100644 index 0000000000..f2f081bbd6 --- /dev/null +++ b/cairo_programs/stwo_exclusive_programs/blake2s_last_block_opcode_test.cairo @@ -0,0 +1,136 @@ +%builtins range_check + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import blake2s_last_block, INPUT_BLOCK_BYTES, STATE_SIZE_FELTS + +const COUNTER = 128; +const N_BYTES = 56; + +// Tests the Blake2sLastBlock opcode runner using a preexisting implementation within the repo as reference. +// The initial state, a random message of 56 bytes (the zeros are treated as padding) and counter are used as input. +// Both the opcode and the reference implementation are run on said inputs and 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}() { + alloc_locals; + + let (local random_message) = alloc(); + assert random_message[0] = 2064419414; + assert random_message[1] = 827054614; + assert random_message[2] = 909720013; + assert random_message[3] = 2388254362; + assert random_message[4] = 2102761495; + assert random_message[5] = 3057501890; + assert random_message[6] = 3153385271; + assert random_message[7] = 2052550804; + assert random_message[8] = 1117201254; + assert random_message[9] = 1365353504; + assert random_message[10] = 3040233373; + assert random_message[11] = 1533241351; + assert random_message[12] = 2146580218; + assert random_message[13] = 1141362435; + assert random_message[14] = 0; + assert random_message[15] = 0; + + let (local blake2s_ptr_start) = alloc(); + let blake2s_ptr = blake2s_ptr_start; + // Set the initial state to IV (IV[0] is modified). + assert blake2s_ptr[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + assert blake2s_ptr[1] = 0xBB67AE85; + assert blake2s_ptr[2] = 0x3C6EF372; + assert blake2s_ptr[3] = 0xA54FF53A; + assert blake2s_ptr[4] = 0x510E527F; + assert blake2s_ptr[5] = 0x9B05688C; + assert blake2s_ptr[6] = 0x1F83D9AB; + assert blake2s_ptr[7] = 0x5BE0CD19; + static_assert STATE_SIZE_FELTS == 8; + let blake2s_ptr = blake2s_ptr + STATE_SIZE_FELTS; + + let (cairo_output) = blake2s_last_block{range_check_ptr=range_check_ptr, blake2s_ptr=blake2s_ptr}(data=random_message, n_bytes=N_BYTES, counter=COUNTER); + + let (local initial_state) = alloc(); + assert initial_state[0] = 0x6B08E647; + assert initial_state[1] = 0xBB67AE85; + assert initial_state[2] = 0x3C6EF372; + assert initial_state[3] = 0xA54FF53A; + assert initial_state[4] = 0x510E527F; + assert initial_state[5] = 0x9B05688C; + assert initial_state[6] = 0x1F83D9AB; + assert initial_state[7] = 0x5BE0CD19; + assert initial_state[8] = COUNTER; + assert initial_state[9] = COUNTER+N_BYTES; + + let (local vm_output_start) = alloc(); + + force_blake2s_last_block_opcode( + dst=vm_output_start, + op0=initial_state, + op1=random_message, + ); + + tempvar check_nonempty = vm_output_start[0]; + tempvar check_nonempty = vm_output_start[1]; + tempvar check_nonempty = vm_output_start[2]; + tempvar check_nonempty = vm_output_start[3]; + tempvar check_nonempty = vm_output_start[4]; + tempvar check_nonempty = vm_output_start[5]; + tempvar check_nonempty = vm_output_start[6]; + tempvar check_nonempty = vm_output_start[7]; + + assert vm_output_start[0] = cairo_output[0]; + assert vm_output_start[1] = cairo_output[1]; + assert vm_output_start[2] = cairo_output[2]; + assert vm_output_start[3] = cairo_output[3]; + assert vm_output_start[4] = cairo_output[4]; + assert vm_output_start[5] = cairo_output[5]; + assert vm_output_start[6] = cairo_output[6]; + assert vm_output_start[7] = cairo_output[7]; + + return (); +} + +// Forces the runner to execute the Blake2sLastBlock with the given operands. +// op0 is a pointer to an array of 10 felts, 8 as u32 integers of the state 1 as a u32 of the +// counter and 1 of the counter+n+bytes. +// op1 is a pointer to an array of 16 felts as u32 integers of the messsage. +// dst is a pointer to an array of 8 felts representing the u32 integers of the output state. +// The values of said pointers 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 Blake2sLastBlock. +func force_blake2s_last_block_opcode( + dst: felt*, + op0: felt*, + op1: felt*, +) { + 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]; + + 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 = 0; + let flag_opcode_blake2s_last_block = 1; + + 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)+flag_opcode_blake2s_last_block*(2**16); + let instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num*(2**48); + static_assert instruction_num==18449981025204076539; + dw 18449981025204076539; + return (); +} diff --git a/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo b/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo index 97914fcc1b..e8dce1255e 100644 --- a/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo +++ b/cairo_programs/stwo_exclusive_programs/blake2s_opcode_test.cairo @@ -128,8 +128,9 @@ func force_blake2s_non_last_block_opcode( let flag_opcode_ret = 0; let flag_opcode_assert_eq = 0; let flag_opcode_blake2s = 1; + let flag_opcode_blake2s_last_block = 0; - 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 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)+flag_opcode_blake2s_last_block*(2**16); let instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num*(2**48); static_assert instruction_num==9226608988349300731; dw 9226608988349300731; diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c757217416..528394c34f 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -576,6 +576,15 @@ fn blake2s_opcode_test() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn blake2s_last_block_opcode_test() { + let program_data = include_bytes!( + "../../../cairo_programs/stwo_exclusive_programs/blake2s_last_block_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 99e0f8a020..73c2cfd8d7 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -75,6 +75,7 @@ pub enum Opcode { Call, Ret, Blake2s, + Blake2sLastBlock, } impl Instruction { @@ -88,11 +89,11 @@ impl Instruction { // Returns True if the given instruction looks like a call instruction pub(crate) fn is_call_instruction(encoded_instruction: &Felt252) -> bool { - let encoded_i64_instruction = match encoded_instruction.to_u64() { + let encoded_u128_instruction = match encoded_instruction.to_u128() { Some(num) => num, None => return false, }; - let instruction = match decode_instruction(encoded_i64_instruction) { + let instruction = match decode_instruction(encoded_u128_instruction) { Ok(inst) => inst, Err(_) => return false, }; @@ -135,7 +136,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn instruction_size() { let encoded_instruction = Felt252::from(1226245742482522112_i64); - let instruction = decode_instruction(encoded_instruction.to_u64().unwrap()).unwrap(); + let instruction = decode_instruction(encoded_instruction.to_u128().unwrap()).unwrap(); assert_eq!(instruction.size(), 2); } } diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index e80ffdc548..2291581ef8 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -5,32 +5,37 @@ use crate::{ vm::errors::vm_errors::VirtualMachineError, }; -// opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg -// 16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 +// 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg +// 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 /// Decodes an instruction. The encoding is little endian, so flags go from bit 64 to 48. -pub fn decode_instruction(encoded_instr: u64) -> Result { - const DST_REG_MASK: u64 = 0x0001; - const DST_REG_OFF: u64 = 0; - const OP0_REG_MASK: u64 = 0x0002; - const OP0_REG_OFF: u64 = 1; - const OP1_SRC_MASK: u64 = 0x001C; - const OP1_SRC_OFF: u64 = 2; - const RES_LOGIC_MASK: u64 = 0x0060; - const RES_LOGIC_OFF: u64 = 5; - const PC_UPDATE_MASK: u64 = 0x0380; - const PC_UPDATE_OFF: u64 = 7; - const AP_UPDATE_MASK: u64 = 0x0C00; - const AP_UPDATE_OFF: u64 = 10; - const OPCODE_MASK: u64 = 0xF000; - const OPCODE_OFF: u64 = 12; +pub fn decode_instruction(encoded_instr: u128) -> Result { + const HIGH_BITS: u128 = ((1 << 127) - (1 << 64)) << 1; + const DST_REG_MASK: u128 = 0x0001; + const DST_REG_OFF: u128 = 0; + const OP0_REG_MASK: u128 = 0x0002; + const OP0_REG_OFF: u128 = 1; + const OP1_SRC_MASK: u128 = 0x001C; + const OP1_SRC_OFF: u128 = 2; + const RES_LOGIC_MASK: u128 = 0x0060; + const RES_LOGIC_OFF: u128 = 5; + const PC_UPDATE_MASK: u128 = 0x0380; + const PC_UPDATE_OFF: u128 = 7; + const AP_UPDATE_MASK: u128 = 0x0C00; + const AP_UPDATE_OFF: u128 = 10; + const OPCODE_MASK: u128 = 0x1F000; + const OPCODE_OFF: u128 = 12; // Flags start on the 48th bit. - const FLAGS_OFFSET: u64 = 48; - const OFF0_OFF: u64 = 0; - const OFF1_OFF: u64 = 16; - const OFF2_OFF: u64 = 32; - const OFFX_MASK: u64 = 0xFFFF; + const FLAGS_OFFSET: u128 = 48; + const OFF0_OFF: u128 = 0; + const OFF1_OFF: u128 = 16; + const OFF2_OFF: u128 = 32; + const OFFX_MASK: u128 = 0xFFFF; + + if (encoded_instr & HIGH_BITS) != 0 { + return Err(VirtualMachineError::InstructionNonZeroHighBits); + } // Grab offsets and convert them from little endian format. let off0 = decode_offset(encoded_instr >> OFF0_OFF & OFFX_MASK); @@ -91,6 +96,7 @@ pub fn decode_instruction(encoded_instr: u64) -> Result Opcode::Ret, 4 => Opcode::AssertEq, 8 => Opcode::Blake2s, + 16 => Opcode::Blake2sLastBlock, _ => return Err(VirtualMachineError::InvalidOpcode(opcode_num)), }; @@ -144,8 +150,8 @@ pub fn decode_instruction(encoded_instr: u64) -> Result isize { - let vectorized_offset: [u8; 8] = offset.to_le_bytes(); +fn decode_offset(offset: u128) -> isize { + let vectorized_offset: [u8; 8] = (offset as u64).to_le_bytes(); let offset_16b_encoded = u16::from_le_bytes([vectorized_offset[0], vectorized_offset[1]]); let complement_const = 0x8000u16; let (offset_16b, _) = offset_16b_encoded.overflowing_sub(complement_const); @@ -161,6 +167,16 @@ mod decoder_test { #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn non_zero_high_bits() { + let error = decode_instruction(0x214a7800080008000); + assert_eq!( + error.unwrap_err().to_string(), + "Instruction bits 65 to 127 should be 0", + ) + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn invalid_op1_reg() { @@ -207,10 +223,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_nop_add_jmp_add_imm_fp_fp() { - // 0 opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // NOp| ADD| JUMP| ADD| IMM| FP| FP - // 0 0 0 0 0 1 0 0 1 0 1 0 0 1 1 1 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | NOp| ADD| JUMP| ADD| IMM| FP| FP + // 0 0 0 0 0 0 1 0 0 1 0 1 0 0 1 1 1 // 0000 0100 1010 0111 = 0x04A7; offx = 0 let inst = decode_instruction(0x04A7800080008000).unwrap(); assert_matches!(inst.dst_register, Register::FP); @@ -226,10 +242,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_nop_add1_jmp_rel_mul_fp_ap_ap() { - // opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // NOp| ADD1| JUMP_REL| MUL| FP| AP| AP - // 0 0 0 0 1 0 0 1 0 1 0 0 1 0 0 0 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | NOp| ADD1| JUMP_REL| MUL| FP| AP| AP + // 0 0 0 0 0 1 0 0 1 0 1 0 0 1 0 0 0 // 0000 1001 0100 1000 = 0x0948; offx = 0 let inst = decode_instruction(0x0948800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -245,10 +261,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_assrt_add_regular_mul_ap_ap_ap() { - // opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // ASSRT_EQ| ADD| REGULAR| MUL| AP| AP| AP - // 0 1 0 0 1 0 0 0 0 1 0 1 0 0 0 0 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | ASSRT_EQ| ADD| REGULAR| MUL| AP| AP| AP + // 0 0 1 0 0 1 0 0 0 0 1 0 1 0 0 0 0 // 0100 1000 0101 0000 = 0x4850; offx = 0 let inst = decode_instruction(0x4850800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -264,10 +280,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_assrt_add2_jnz_uncon_op0_ap_ap() { - // opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // ASSRT_EQ| ADD2| JNZ|UNCONSTRD| OP0| AP| AP - // 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | ASSRT_EQ| ADD2| JNZ|UNCONSTRD| OP0| AP| AP + // 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 // 0100 0010 0000 0000 = 0x4200; offx = 0 let inst = decode_instruction(0x4200800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -283,10 +299,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_nop_regu_regu_op1_op0_ap_ap() { - // opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // NOP| REGULAR| REGULAR| OP1| OP0| AP| AP - // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | NOP| REGULAR| REGULAR| OP1| OP0| AP| AP + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // 0000 0000 0000 0000 = 0x0000; offx = 0 let inst = decode_instruction(0x0000800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -302,10 +318,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_offset_negative() { - // opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg - // 16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 - // NOP| REGULAR| REGULAR| OP1| OP0| AP| AP - // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // 0| opcode|ap_update|pc_update|res_logic|op1_src|op0_reg|dst_reg + // 31 ... 17|16 15 14 13 12| 11 10| 9 8 7| 6 5|4 3 2| 1| 0 + // | NOP| REGULAR| REGULAR| OP1| OP0| AP| AP + // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 // 0000 0000 0000 0000 = 0x0000; offx = 0 let inst = decode_instruction(0x0000800180007FFF).unwrap(); assert_eq!(inst.off0, -1); diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index b50654298b..ee24893fa4 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -34,18 +34,20 @@ pub enum VirtualMachineError { MainScopeError(#[from] ExecScopeError), #[error(transparent)] Other(anyhow::Error), + #[error("Instruction bits 65 to 127 should be 0")] + InstructionNonZeroHighBits, #[error("Instruction should be an int")] InvalidInstructionEncoding, #[error("Invalid op1_register value: {0}")] - InvalidOp1Reg(u64), + InvalidOp1Reg(u128), #[error("In immediate mode, off2 should be 1")] ImmShouldBe1, #[error("op0 must be known in double dereference")] UnknownOp0, #[error("Invalid ap_update value: {0}")] - InvalidApUpdate(u64), + InvalidApUpdate(u128), #[error("Invalid pc_update value: {0}")] - InvalidPcUpdate(u64), + InvalidPcUpdate(u128), #[error("Res.UNCONSTRAINED cannot be used with ApUpdate.ADD")] UnconstrainedResAdd, #[error("Res.UNCONSTRAINED cannot be used with PcUpdate.JUMP")] @@ -71,9 +73,9 @@ pub enum VirtualMachineError { #[error("Couldn't get or load dst")] NoDst, #[error("Invalid res value: {0}")] - InvalidRes(u64), + InvalidRes(u128), #[error("Invalid opcode value: {0}")] - InvalidOpcode(u64), + InvalidOpcode(u128), #[error("This is not implemented")] NotImplemented, #[error("Inconsistent auto-deduction for {}, expected {}, got {:?}", (*.0).0, (*.0).1, (*.0).2)] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 73a5a4ed5f..982637180c 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -442,8 +442,11 @@ impl VirtualMachine { .memory .mark_as_accessed(operands_addresses.op1_addr); - if instruction.opcode == Opcode::Blake2s { - self.handle_blake2s_instruction(&operands)?; + if instruction.opcode == Opcode::Blake2s || instruction.opcode == Opcode::Blake2sLastBlock { + self.handle_blake2s_instruction( + &operands, + instruction.opcode == Opcode::Blake2sLastBlock, + )?; } self.update_registers(instruction, operands)?; @@ -452,45 +455,56 @@ impl VirtualMachine { Ok(()) } - /// Executes a Blake2s instruction. + /// Executes a Blake2s or Blake2sLastBlock instruction. /// Expects operands to be RelocatableValue and to point to segments of memory. - /// op0 is expected to point to a sequence of 9 u32 values (state and counter t0). + /// op0 is expected to point to a sequence of 9 u32 values (state and counter t0) in the case + /// of Blake2s and 10 u32 values (state, t0, t0+n_bytes) in the case of Blake2sLastBlock. /// op1 is expected to point to a sequence of 16 u32 values (message). /// dst is expected to point to a sequence of 9 cells with the first 8 cells each /// being either unitialised or containing the Blake2s compression output at that index - /// and the last cell being unitialised or containing . + /// and the last cell being unitialised or containing the updated counter in the case of + /// Blake2s and just 8 cells as described previously in the case of Blake2sLastBlock. /// Deviation from the aforementioned expectations will result in an error. /// The instruction will update the dst memory segment 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). + /// + /// Note: the byte counter should count the number of message bytes processed so far not + /// including the current portion of the message. i.e. it starts at 0 and when running + /// Blake2sLastBlock the [op0+9] should be the total length of the entire message in bytes. + /// It is the cairo program's responsibility to ensure this and to pad the message with zeros. fn handle_blake2s_instruction( &mut self, operands: &Operands, + is_last_block: bool, ) -> Result<(), VirtualMachineError> { let dst: Relocatable = match operands.dst { RelocatableValue(relocatable) => relocatable, _ => return Err(VirtualMachineError::Memory(AddressNotRelocatable)), }; - let state_and_t0: [u32; 9] = (self.get_u32_range(&operands.op0, 9)?) - .try_into() - .map_err(|_| VirtualMachineError::Blake2sInvalidOperand(0, 9))?; + let op0_len = if is_last_block { 10 } else { 9 }; + + let state_and_t0 = self.get_u32_range(&operands.op0, op0_len)?; let message = (self.get_u32_range(&operands.op1, 16)?) .try_into() .map_err(|_| VirtualMachineError::Blake2sInvalidOperand(1, 16))?; - let next_counter = state_and_t0[8] + BLAKE2S_BYTES_PER_BLOCK; - let mut new_state_and_t0 = blake2s_compress( + let mut output = blake2s_compress( state_and_t0[0..8].try_into().unwrap(), &message, - next_counter, - 0, + if !is_last_block { + state_and_t0[8] + BLAKE2S_BYTES_PER_BLOCK + } else { + state_and_t0[9] + }, 0, + if !is_last_block { 0 } else { 0xffffffff }, 0, ); - new_state_and_t0.push(next_counter); + if !is_last_block { + output.push(state_and_t0[8] + BLAKE2S_BYTES_PER_BLOCK); + } - for (i, &val) in new_state_and_t0.iter().enumerate() { + for (i, &val) in output.iter().enumerate() { self.segments .memory .insert_as_accessed((dst + i)?, MaybeRelocatable::Int(Felt252::from(val)))?; @@ -504,7 +518,7 @@ impl VirtualMachine { .segments .memory .get_integer(self.run_context.pc)? - .to_u64() + .to_u128() .ok_or(VirtualMachineError::InvalidInstructionEncoding)?; decode_instruction(instruction) } @@ -4204,7 +4218,7 @@ mod tests { #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_current_instruction_invalid_encoding() { let mut vm = vm!(); - vm.segments = segments![((0, 0), ("112233445566778899", 16))]; + vm.segments = segments![((0, 0), ("112233445566778899112233445566778899", 16))]; assert_matches!( vm.decode_current_instruction(), Err(VirtualMachineError::InvalidInstructionEncoding) @@ -4428,7 +4442,7 @@ mod tests { op1: (0, 1).into(), }; assert_matches!( - vm.handle_blake2s_instruction(&operands), + vm.handle_blake2s_instruction(&operands, false), Err(VirtualMachineError::Memory(AddressNotRelocatable)) ); }