diff --git a/CHANGELOG.md b/CHANGELOG.md index 549522c8f3..da4915bec0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ #### Upcoming Changes +* feat: implement `Blake2s` and `Blake2sLastBlock` opcodes in VM [#1922](https://github.com/lambdaclass/cairo-vm/pull/1922) + #### [2.0.0-rc4] - 2025-01-23 * feat: implement `kzg` data availability hints [#1887](https://github.com/lambdaclass/cairo-vm/pull/1887) 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/blake2s_last_block_opcode_test.cairo b/cairo_programs/blake2s_last_block_opcode_test.cairo new file mode 100644 index 0000000000..edeca07d2f --- /dev/null +++ b/cairo_programs/blake2s_last_block_opcode_test.cairo @@ -0,0 +1,154 @@ +%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; + +// 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; // 3342861606 + assert random_message[15] = 0; // 1541788056 + + 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=4*14, counter=COUNTER); + + let (local initial_state) = alloc(); + assert initial_state[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + 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; + + 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]; + tempvar check_nonempty = vm_output_start[8];// + + 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]; + assert vm_output_start[8] = [cairo_output-2];// + + return (); +} + +// pub const FLAG_DST_BASE_FP_INDEX: usize = 0; +// pub const FLAG_OP0_BASE_FP_INDEX: usize = 1; +// pub const FLAG_OP1_IMM_INDEX: usize = 2; +// pub const FLAG_OP1_BASE_FP_INDEX: usize = 3; +// pub const FLAG_OP1_BASE_AP_INDEX: usize = 4; +// pub const FLAG_RES_ADD_INDEX: usize = 5; +// pub const FLAG_RES_MUL_INDEX: usize = 6; +// pub const FLAG_PC_UPDATE_JUMP_INDEX: usize = 7; +// pub const FLAG_PC_UPDATE_JUMP_REL_INDEX: usize = 8; +// pub const FLAG_PC_UPDATE_JNZ_INDEX: usize = 9; +// pub const FLAG_AP_UPDATE_ADD_INDEX: usize = 10; +// pub const FLAG_AP_UPDATE_ADD_1_INDEX: usize = 11; +// pub const FLAG_OPCODE_CALL_INDEX: usize = 12; +// pub const FLAG_OPCODE_RET_INDEX: usize = 13; +// pub const FLAG_OPCODE_ASSERT_EQ_INDEX: usize = 14; +// pub const FLAG_OPCODE_BLAKE2S_INDEX: usize = 15; // +// pub const FLAG_OPCODE_BLAKE2S_LAST_BLOCK_INDEX: usize = 16; // + +// Forces the runner to execute the Blake2sLastBlock with the given operands. +// op0 is a pointer to an array of 9 felts, 8 as u32 integers of the state and 1 as a u32 of the counter. +// op1 is a pointer to an array of 16 felts as u32 integers of the messsage. +// dst is a pointer to an array of 9 felts, 8 as u32 integers of the output state and 1 as a u32 of the updated counter. +// 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/blake2s_opcode_test.cairo b/cairo_programs/blake2s_opcode_test.cairo new file mode 100644 index 0000000000..ab08015dfb --- /dev/null +++ b/cairo_programs/blake2s_opcode_test.cairo @@ -0,0 +1,157 @@ +%builtins range_check + +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.cairo_blake2s.blake2s import blake2s_inner, INPUT_BLOCK_BYTES, STATE_SIZE_FELTS, INPUT_BLOCK_FELTS + +const COUNTER = 128; + +// Tests the Blake2s opcode runner using a preexisting implementation within the repo as reference. +// The initial state, a random message of 68 bytes 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] = 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; + assert random_message[16] = 1111307871; + + 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_inner{range_check_ptr=range_check_ptr, blake2s_ptr=blake2s_ptr}(data=random_message, n_bytes=INPUT_BLOCK_BYTES+4, counter=COUNTER); + + let (local initial_state) = alloc(); + assert initial_state[0] = 0x6B08E647; // IV[0] ^ 0x01010020 (config: no key, 32 bytes output). + 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; + + let (local vm_output_start) = alloc(); + + force_blake2s_non_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]; + tempvar check_nonempty = vm_output_start[8]; + + let relevant_output_start = blake2s_ptr_start+INPUT_BLOCK_FELTS+2+STATE_SIZE_FELTS; + + assert vm_output_start[0] = relevant_output_start[0]; + assert vm_output_start[1] = relevant_output_start[1]; + assert vm_output_start[2] = relevant_output_start[2]; + assert vm_output_start[3] = relevant_output_start[3]; + assert vm_output_start[4] = relevant_output_start[4]; + assert vm_output_start[5] = relevant_output_start[5]; + assert vm_output_start[6] = relevant_output_start[6]; + assert vm_output_start[7] = relevant_output_start[7]; + assert vm_output_start[8] = [relevant_output_start-2]; + + return (); +} + +// pub const FLAG_DST_BASE_FP_INDEX: usize = 0; +// pub const FLAG_OP0_BASE_FP_INDEX: usize = 1; +// pub const FLAG_OP1_IMM_INDEX: usize = 2; +// pub const FLAG_OP1_BASE_FP_INDEX: usize = 3; +// pub const FLAG_OP1_BASE_AP_INDEX: usize = 4; +// pub const FLAG_RES_ADD_INDEX: usize = 5; +// pub const FLAG_RES_MUL_INDEX: usize = 6; +// pub const FLAG_PC_UPDATE_JUMP_INDEX: usize = 7; +// pub const FLAG_PC_UPDATE_JUMP_REL_INDEX: usize = 8; +// pub const FLAG_PC_UPDATE_JNZ_INDEX: usize = 9; +// pub const FLAG_AP_UPDATE_ADD_INDEX: usize = 10; +// pub const FLAG_AP_UPDATE_ADD_1_INDEX: usize = 11; +// pub const FLAG_OPCODE_CALL_INDEX: usize = 12; +// pub const FLAG_OPCODE_RET_INDEX: usize = 13; +// pub const FLAG_OPCODE_ASSERT_EQ_INDEX: usize = 14; +// pub const FLAG_OPCODE_BLAKE2S_INDEX: usize = 15; // +// pub const FLAG_OPCODE_BLAKE2S_LAST_BLOCK_INDEX: usize = 16; // + +// Forces the runner to execute the Blake2s with the given operands. +// op0 is a pointer to an array of 9 felts, 8 as u32 integers of the state and 1 as a u32 of the counter. +// op1 is a pointer to an array of 16 felts as u32 integers of the messsage. +// dst is a pointer to an array of 9 felts, 8 as u32 integers of the output state and 1 as a u32 of the updated counter. +// 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 Blake2s. +func force_blake2s_non_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 = 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)+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; + return (); +} + diff --git a/vm/src/math_utils/blake2s_utils.rs b/vm/src/math_utils/blake2s_utils.rs new file mode 100644 index 0000000000..16edd75972 --- /dev/null +++ b/vm/src/math_utils/blake2s_utils.rs @@ -0,0 +1,196 @@ +use core::array::from_fn; + +const INPUT_BLOCK_BYTES: u32 = 64; +const IV: [u32; 8] = [ + 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19, +]; +const SIGMA: [[usize; 16]; 10] = [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3], + [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4], + [7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8], + [9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13], + [2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9], + [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11], + [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10], + [6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5], + [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0], +]; + +fn right_rot(value: u32, n: u32) -> u32 { + (value >> n) | ((value & ((1 << n) - 1)) << (32 - n)) +} + +/// Helper function for the Cairo blake2s() implementation. +/// Computes the blake2s compress function and fills the value in the right position. +/// output_ptr should point to the middle of an instance, right after initial_state, message, t, f, +/// which should all have a value at this point, and right before the output portion which will be +/// written by this function. + +// func compute_blake2s_func(segments: MemorySegmentManager, output_ptr: RelocatableValue): +// h = segments.memory.get_range(output_ptr - 26, 8) +// message = segments.memory.get_range(output_ptr - 18, 16) +// t = segments.memory[output_ptr - 2] +// f = segments.memory[output_ptr - 1] +// new_state = blake2s_compress( +// message=message, +// h=h, +// t0=t, +// t1=0, +// f0=f, +// f1=0, +// ) +// segments.write_arg(output_ptr, new_state) + +pub fn blake2s_non_last_block_raw(state_with_t_raw: [u32; 9], message: [u32; 16]) -> [u32; 9] { + let state: [u32; 8] = state_with_t_raw[0..8].try_into().unwrap(); + let t0 = state_with_t_raw[8] + INPUT_BLOCK_BYTES; + let new_state = blake2s_non_last_block(state, t0, message); + let mut res = [0; 9]; + res[..8].copy_from_slice(&new_state); + res[8] = t0; + res +} + +/// Assumtions: +/// 0 [u32; 9] { + let state: [u32; 8] = state_with_t_raw[0..8].try_into().unwrap(); // + let t0 = state_with_t_raw[8] + n_bytes; + let new_state = blake2s_last_block(state, t0, message); + let mut res = [0; 9]; + res[..8].copy_from_slice(&new_state); + res[8] = t0; + res +} + +fn blake2s_non_last_block(h: [u32; 8], t0: u32, message: [u32; 16]) -> [u32; 8] { + blake2s_compress(h, message, t0, 0, 0, 0) +} + +fn blake2s_last_block(h: [u32; 8], t0: u32, message: [u32; 16]) -> [u32; 8] { + blake2s_compress(h, message, t0, 0, 0xffffffff, 0) +} + +/// Computes the blake2s compression function. +/// h is a list of 8 32-bit words. +/// message is a list of 16 32-bit words. +fn blake2s_compress( + h: [u32; 8], + message: [u32; 16], + t0: u32, + t1: u32, + f0: u32, + f1: u32, +) -> [u32; 8] { + let mut state = [ + h[0], + h[1], + h[2], + h[3], + h[4], + h[5], + h[6], + h[7], + IV[0], + IV[1], + IV[2], + IV[3], + IV[4] ^ t0, + IV[5] ^ t1, + IV[6] ^ f0, + IV[7] ^ f1, + ]; + for &permutation in &SIGMA[..10] { + state = blake_round(state, message, permutation) + } + from_fn(|j| h[j] ^ state[j] ^ state[j + 8]) +} + +fn blake_round(in_state: [u32; 16], message: [u32; 16], sigma: [usize; 16]) -> [u32; 16] { + let mut state = in_state; + (state[0], state[4], state[8], state[12]) = mix( + state[0], + state[4], + state[8], + state[12], + message[sigma[0]], + message[sigma[1]], + ); + (state[1], state[5], state[9], state[13]) = mix( + state[1], + state[5], + state[9], + state[13], + message[sigma[2]], + message[sigma[3]], + ); + (state[2], state[6], state[10], state[14]) = mix( + state[2], + state[6], + state[10], + state[14], + message[sigma[4]], + message[sigma[5]], + ); + (state[3], state[7], state[11], state[15]) = mix( + state[3], + state[7], + state[11], + state[15], + message[sigma[6]], + message[sigma[7]], + ); + (state[0], state[5], state[10], state[15]) = mix( + state[0], + state[5], + state[10], + state[15], + message[sigma[8]], + message[sigma[9]], + ); + (state[1], state[6], state[11], state[12]) = mix( + state[1], + state[6], + state[11], + state[12], + message[sigma[10]], + message[sigma[11]], + ); + (state[2], state[7], state[8], state[13]) = mix( + state[2], + state[7], + state[8], + state[13], + message[sigma[12]], + message[sigma[13]], + ); + (state[3], state[4], state[9], state[14]) = mix( + state[3], + state[4], + state[9], + state[14], + message[sigma[14]], + message[sigma[15]], + ); + + state +} + +fn mix(mut a: u32, mut b: u32, mut c: u32, mut d: u32, m0: u32, m1: u32) -> (u32, u32, u32, u32) { + a = a.wrapping_add(b).wrapping_add(m0); + d = right_rot(d ^ a, 16); + c = c.wrapping_add(d); + b = right_rot(b ^ c, 12); + a = a.wrapping_add(b).wrapping_add(m1); + d = right_rot(d ^ a, 8); + c = c.wrapping_add(d); + b = right_rot(b ^ c, 7); + + (a, b, c, d) +} diff --git a/vm/src/math_utils/mod.rs b/vm/src/math_utils/mod.rs index bd5ff0935d..e7837c6852 100644 --- a/vm/src/math_utils/mod.rs +++ b/vm/src/math_utils/mod.rs @@ -1,3 +1,4 @@ +pub mod blake2s_utils; mod is_prime; pub use is_prime::is_prime; diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index d847852929..0c03ae2099 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -568,6 +568,21 @@ 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/blake2s_opcode_test.json"); + 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/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 133c691302..73c2cfd8d7 100644 --- a/vm/src/types/instruction.rs +++ b/vm/src/types/instruction.rs @@ -74,6 +74,8 @@ pub enum Opcode { AssertEq, Call, Ret, + Blake2s, + Blake2sLastBlock, } impl Instruction { @@ -87,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, }; @@ -134,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 35a98e7812..2b373f53c0 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -5,36 +5,36 @@ use crate::{ vm::errors::vm_errors::VirtualMachineError, }; -// 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 +// 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 63 to 48. -pub fn decode_instruction(encoded_instr: u64) -> Result { - const HIGH_BIT: u64 = 1u64 << 63; - 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 = 0x7000; - const OPCODE_OFF: u64 = 12; +/// Decodes an instruction. The encoding is little endian, so flags go from bit 64 to 48. +pub fn decode_instruction(encoded_instr: u128) -> Result { + const HIGH_BITS: u128 = ((1 << 127) - (1 << 64)) << 1; //63 + 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; // 5 bits + 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_BIT != 0 { - return Err(VirtualMachineError::InstructionNonZeroHighBit); + if (encoded_instr & HIGH_BITS) != 0 { + return Err(VirtualMachineError::InstructionNonZeroHighBits); } // Grab offsets and convert them from little endian format. @@ -95,6 +95,8 @@ pub fn decode_instruction(encoded_instr: u64) -> Result Opcode::Call, 2 => Opcode::Ret, 4 => Opcode::AssertEq, + 8 => Opcode::Blake2s, + 16 => Opcode::Blake2sLastBlock, // _ => return Err(VirtualMachineError::InvalidOpcode(opcode_num)), }; @@ -127,8 +129,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); @@ -147,10 +149,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn non_zero_high_bit() { - let error = decode_instruction(0x94A7800080008000); + let error = decode_instruction(0x214a7800080008000); assert_eq!( error.unwrap_err().to_string(), - "Instruction MSB should be 0", + "Instruction bits 65 to 127 should be 0", ) } @@ -200,10 +202,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_call_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 - // | CALL| ADD| JUMP| ADD| IMM| FP| FP - // 0 0 0 1 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 + // | CALL| ADD| JUMP| ADD| IMM| FP| FP + // 0| 0 0 0 0 1 0 1 0 0 1 0 1 0 0 1 1 1 // 0001 0100 1010 0111 = 0x14A7; offx = 0 let inst = decode_instruction(0x14A7800080008000).unwrap(); assert_matches!(inst.dst_register, Register::FP); @@ -219,10 +221,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_ret_add1_jmp_rel_mul_fp_ap_ap() { - // 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 - // | RET| ADD1| JUMP_REL| MUL| FP| AP| AP - // 0 0 1 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 + // | RET| ADD1| JUMP_REL| MUL| FP| AP| AP + // 0 0 0 0 1 0 1 0 0 1 0 1 0 0 1 0 0 0 // 0010 1001 0100 1000 = 0x2948; offx = 0 let inst = decode_instruction(0x2948800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -238,10 +240,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_assrt_add_jnz_mul_ap_ap_ap() { - // 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 - // |ASSRT_EQ| ADD| JNZ| MUL| AP| AP| AP - // 0 1 0 0 1 0 1 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| JNZ| MUL| AP| AP| AP + // 0 0 0 1 0 0 1 0 1 0 0 1 0 1 0 0 0 0 // 0100 1010 0101 0000 = 0x4A50; offx = 0 let inst = decode_instruction(0x4A50800080008000).unwrap(); assert_matches!(inst.dst_register, Register::AP); @@ -257,10 +259,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_assrt_add2_jnz_uncon_op0_ap_ap() { - // 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 - // |ASSRT_EQ| ADD2| JNZ|UNCONSTRD| OP0| AP| AP - // 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); @@ -276,10 +278,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_flags_nop_regu_regu_op1_op0_ap_ap() { - // 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| REGULAR| REGULAR| OP1| OP0| AP| AP - // 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); @@ -295,10 +297,10 @@ mod decoder_test { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn decode_offset_negative() { - // 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| REGULAR| REGULAR| OP1| OP0| AP| AP - // 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/memory_errors.rs b/vm/src/vm/errors/memory_errors.rs index b6f02b0a85..fa25454fba 100644 --- a/vm/src/vm/errors/memory_errors.rs +++ b/vm/src/vm/errors/memory_errors.rs @@ -101,6 +101,8 @@ pub enum MemoryError { UnrelocatedMemory, #[error("Malformed public memory")] MalformedPublicMemory, + #[error("Expected u32 at address {0}")] + ExpectedU32(Box), } #[derive(Debug, PartialEq, Eq, Error)] diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index c3698eda02..d5a6b48384 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -34,20 +34,20 @@ pub enum VirtualMachineError { MainScopeError(#[from] ExecScopeError), #[error(transparent)] Other(anyhow::Error), - #[error("Instruction MSB should be 0")] - InstructionNonZeroHighBit, + #[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")] @@ -73,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)] @@ -136,6 +136,8 @@ pub enum VirtualMachineError { RelocationNotFound(usize), #[error("{} batch size is not {}", (*.0).0, (*.0).1)] ModBuiltinBatchSize(Box<(BuiltinName, usize)>), + #[error("Last block of blake2s has only zeros in message.")] + Blake2sLastBlockOpcodeZeroMessage, } #[cfg(test)] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 5862aee093..10229ced56 100644 --- a/vm/src/vm/vm_core.rs +++ b/vm/src/vm/vm_core.rs @@ -1,4 +1,7 @@ -use crate::math_utils::signed_felt; +use crate::math_utils::{ + blake2s_utils::{blake2s_last_block_raw, blake2s_non_last_block_raw}, + signed_felt, +}; use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] @@ -11,14 +14,14 @@ use crate::{ instruction::{ is_call_instruction, ApUpdate, FpUpdate, Instruction, Opcode, PcUpdate, Res, }, - relocatable::{MaybeRelocatable, Relocatable}, + relocatable::{MaybeRelocatable, MaybeRelocatable::RelocatableValue, Relocatable}, }, vm::{ context::run_context::RunContext, decoding::decoder::decode_instruction, errors::{ exec_scope_errors::ExecScopeError, memory_errors::MemoryError, - vm_errors::VirtualMachineError, + memory_errors::MemoryError::AddressNotRelocatable, vm_errors::VirtualMachineError, }, runners::builtin_runner::{ BuiltinRunner, OutputBuiltinRunner, RangeCheckBuiltinRunner, SignatureBuiltinRunner, @@ -404,7 +407,9 @@ impl VirtualMachine { fn run_instruction(&mut self, instruction: &Instruction) -> Result<(), VirtualMachineError> { let (operands, operands_addresses, deduced_operands) = self.compute_operands(instruction)?; + self.insert_deduced_operands(deduced_operands, &operands, &operands_addresses)?; + self.opcode_assertions(instruction, &operands)?; if let Some(ref mut trace) = &mut self.trace { @@ -438,18 +443,75 @@ impl VirtualMachine { .memory .mark_as_accessed(operands_addresses.op1_addr); + if instruction.opcode == Opcode::Blake2s || instruction.opcode == Opcode::Blake2sLastBlock { + self.handle_blake2s_instruction( + &operands, + instruction.opcode == Opcode::Blake2sLastBlock, + )?; + } + self.update_registers(instruction, operands)?; self.current_step += 1; Ok(()) } + /// 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). + /// 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 being each either + /// unitialised or containing the Blake2s compression output at that index and the 9th index + /// unitialised or containing the updated byte counter. + /// Deviation from the aforementioned expectations will result in an error. + /// The instruction will update the dst memory segment with the new state and counter. + /// Note: currently in Blake2sLastBlock it is assumed that [op1]!=0 and if 0<=j<=15 is the last + /// index for which [op1+j]!=0 then the final block of the message is only 4*(j+1) bytes long. + 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 = (self.get_u32_range(&operands.op0, 9)?).try_into().unwrap(); + let message = (self.get_u32_range(&operands.op1, 16)?).try_into().unwrap(); + + let mut new_state = vec![]; + + if !is_last_block { + new_state.extend(blake2s_non_last_block_raw(state_and_t0, message)); + } else { + // Assumptions: + // message[0]!=0. + // If message[i]!=0 yet message[i+1]=0 ... message[15]=0 then n_bytes = 4*(i+1). + // alternatively, n_bytes can be stored in state operand and verified. + let last_nonzero_index = message.iter().rposition(|&x| x != 0); + let n_bytes = match last_nonzero_index { + Some(index) => 4 * (index as u32 + 1), + None => return Err(VirtualMachineError::Blake2sLastBlockOpcodeZeroMessage), + }; + new_state.extend(blake2s_last_block_raw(state_and_t0, message, n_bytes)); + } + + for (i, &val) in new_state.iter().enumerate() { + self.segments + .memory + .insert_as_accessed((dst + i)?, MaybeRelocatable::Int(Felt252::from(val)))?; + } + + Ok(()) + } + fn decode_current_instruction(&self) -> Result { let instruction = self .segments .memory .get_integer(self.run_context.pc)? - .to_u64() + .to_u128() .ok_or(VirtualMachineError::InvalidInstructionEncoding)?; decode_instruction(instruction) } @@ -926,6 +988,33 @@ impl VirtualMachine { self.segments.memory.get_integer_range(addr, size) } + /// Gets n u32 values from memory starting from addr (n being size), + /// Verifies that addr is &RelocatableValue and that the values are u32. + pub fn get_u32_range( + &self, + addr: &MaybeRelocatable, + size: usize, + ) -> Result, MemoryError> { + let addr_relocatable: Relocatable = match addr { + &RelocatableValue(relocatable) => relocatable, + _ => return Err(MemoryError::AddressNotRelocatable), + }; + let res_cow = self.get_integer_range(addr_relocatable, size)?; + let mut res = vec![]; + for (i, x) in res_cow.iter().enumerate() { + let le_digits = x.clone().into_owned().to_le_digits(); + if le_digits[0] >= (1 << 32) + || le_digits[1] != 0 + || le_digits[2] != 0 + || le_digits[3] != 0 + { + return Err(MemoryError::ExpectedU32(Box::new((addr_relocatable + i)?))); + } + res.push(le_digits[0] as u32); + } + Ok(res) + } + pub fn get_range_check_builtin( &self, ) -> Result<&RangeCheckBuiltinRunner, VirtualMachineError> { @@ -4122,7 +4211,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)