Skip to content

Commit

Permalink
implement Blake2sLastBlock opcode in runner
Browse files Browse the repository at this point in the history
  • Loading branch information
ohad-nir-starkware committed Jan 29, 2025
1 parent 912b050 commit 4919048
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 77 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cairo-vm-tracer/src/tracer_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Check warning on line 146 in cairo-vm-tracer/src/tracer_data.rs

View check run for this annotation

Codecov / codecov/patch

cairo-vm-tracer/src/tracer_data.rs#L146

Added line #L146 was not covered by tests
if instruction_encoding.is_none() {
return Err(TraceDataError::FailedToConvertInstructionEncoding);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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 ();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions vm/src/tests/cairo_run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
7 changes: 4 additions & 3 deletions vm/src/types/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ pub enum Opcode {
Call,
Ret,
Blake2s,
Blake2sLastBlock,
}

impl Instruction {
Expand All @@ -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,
};
Expand Down Expand Up @@ -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);
}
}
112 changes: 64 additions & 48 deletions vm/src/vm/decoding/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Instruction, VirtualMachineError> {
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<Instruction, VirtualMachineError> {
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);
Expand Down Expand Up @@ -91,6 +96,7 @@ pub fn decode_instruction(encoded_instr: u64) -> Result<Instruction, VirtualMach
2 => Opcode::Ret,
4 => Opcode::AssertEq,
8 => Opcode::Blake2s,
16 => Opcode::Blake2sLastBlock,
_ => return Err(VirtualMachineError::InvalidOpcode(opcode_num)),
};

Expand Down Expand Up @@ -144,8 +150,8 @@ pub fn decode_instruction(encoded_instr: u64) -> Result<Instruction, VirtualMach
})
}

fn decode_offset(offset: u64) -> 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);
Expand All @@ -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() {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 4919048

Please sign in to comment.