From 6dd3870ad5f37e069643015423555086c7a6c3d2 Mon Sep 17 00:00:00 2001 From: ohad nir Date: Mon, 10 Feb 2025 16:22:16 +0200 Subject: [PATCH] add qm31 operations to VM --- .../qm31_opcodes_test.cairo | 274 ++++++++++++++ vm/src/math_utils/mod.rs | 350 ++++++++++++++++++ vm/src/tests/cairo_run_test.rs | 8 + vm/src/types/errors/math_errors.rs | 4 + vm/src/types/instruction.rs | 1 + vm/src/vm/decoding/decoder.rs | 34 ++ vm/src/vm/errors/vm_errors.rs | 8 + vm/src/vm/vm_core.rs | 119 ++++-- 8 files changed, 770 insertions(+), 28 deletions(-) create mode 100644 cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo diff --git a/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo b/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo new file mode 100644 index 0000000000..835be07d2f --- /dev/null +++ b/cairo_programs/stwo_exclusive_programs/qm31_opcodes_test.cairo @@ -0,0 +1,274 @@ +from starkware.cairo.common.alloc import alloc +from starkware.cairo.common.bool import FALSE, TRUE + +func main{}() { + alloc_locals; + // x*y coordinates_to_packed([947980980, 1510986506, 623360030, 1260310989]), + // x+y coordinates_to_packed([526650964, 1026816730, 1318309406, 1181635724]), + // x coordinates_to_packed([1414213562, 1732050807, 1618033988, 1234567890]), + // y coordinates_to_packed([1259921049, 1442249570, 1847759065, 2094551481]), + let (local qm31_op0_coordinates) = alloc(); + assert qm31_op0_coordinates[0] = 1414213562; + assert qm31_op0_coordinates[1] = 1732050807; + assert qm31_op0_coordinates[2] = 1618033988; + assert qm31_op0_coordinates[3] = 1234567890; + let qm31_op0 = qm31_op0_coordinates[0] + qm31_op0_coordinates[1]*(2**36) + qm31_op0_coordinates[2]*(2**72) + qm31_op0_coordinates[3]*(2**108); + + let (local qm31_op1_coordinates) = alloc(); + assert qm31_op1_coordinates[0] = 1259921049; + assert qm31_op1_coordinates[1] = 1442249570; + assert qm31_op1_coordinates[2] = 1847759065; + assert qm31_op1_coordinates[3] = 2094551481; + let qm31_op1 = qm31_op1_coordinates[0] + qm31_op1_coordinates[1]*(2**36) + qm31_op1_coordinates[2]*(2**72) + qm31_op1_coordinates[3]*(2**108); + + let (local qm31_add_dst_coordinates) = alloc(); + assert qm31_add_dst_coordinates[0] = 526650964; + assert qm31_add_dst_coordinates[1] = 1026816730; + assert qm31_add_dst_coordinates[2] = 1318309406; + assert qm31_add_dst_coordinates[3] = 1181635724; + let qm31_add_dst = qm31_add_dst_coordinates[0] + qm31_add_dst_coordinates[1]*(2**36) + qm31_add_dst_coordinates[2]*(2**72) + qm31_add_dst_coordinates[3]*(2**108); + + let (local qm31_mul_dst_coordinates) = alloc(); + assert qm31_mul_dst_coordinates[0] = 947980980; + assert qm31_mul_dst_coordinates[1] = 1510986506; + assert qm31_mul_dst_coordinates[2] = 623360030; + assert qm31_mul_dst_coordinates[3] = 1260310989; + let qm31_mul_dst = qm31_mul_dst_coordinates[0] + qm31_mul_dst_coordinates[1]*(2**36) + qm31_mul_dst_coordinates[2]*(2**72) + qm31_mul_dst_coordinates[3]*(2**108); + + let runner_output_mul_dst = run_qm31_operation_get_dst(is_mul=TRUE, op0=qm31_op0, op1=qm31_op1); + assert runner_output_mul_dst = qm31_mul_dst; + let runner_output_add_dst = run_qm31_operation_get_dst(is_mul=FALSE, op0=qm31_op0, op1=qm31_op1); + assert runner_output_add_dst = qm31_add_dst; + //let runner_output_mul_dst = run_qm31_operation_get_dst(is_mul=TRUE, op0=qm31_op0, op1=qm31_op1); + // assert runner_output_mul_dst = qm31_mul_dst; + + let runner_output_mul_op1 = run_qm31_operation_get_op1(is_mul=TRUE, dst=qm31_mul_dst, op0=qm31_op0); + assert runner_output_mul_op1 = qm31_op1; + let runner_output_add_op1 = run_qm31_operation_get_op1(is_mul=FALSE, dst=qm31_add_dst, op0=qm31_op0); + assert runner_output_add_op1 = qm31_op1; + + let runner_output_mul_op0 = run_qm31_operation_get_op0(is_mul=TRUE, dst=qm31_mul_dst, op1=qm31_op1); + assert runner_output_mul_op0 = qm31_op0; + let runner_output_add_op0 = run_qm31_operation_get_op0(is_mul=FALSE, dst=qm31_add_dst, op1=qm31_op1); + assert runner_output_add_op0 = qm31_op0; + + // let runner_output_mul_op0 = run_qm31_operation_get_op0(is_mul=TRUE, dst=qm31_mul_dst, op1=qm31_op1); + // //assert qm31_op1 = qm31_op0; + // assert runner_output_mul_op0 = qm31_op0; + // let runner_output_mul_op1 = run_qm31_operation_get_op1(is_mul=TRUE, dst=qm31_mul_dst, op0=qm31_op0); + // assert runner_output_mul_op1 = qm31_op1; + + return (); +} + +// missing_operand_index +func run_qm31_operation_get_dst( + is_mul: felt, + op0: felt, + op1: felt, +) -> felt { + //alloc_locals; + + let offset0 = 2**15; + let offset1 = (2**15)-4; + let offset2 = (2**15)-3; + + let flag_dst_base_fp = 0; + 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 = is_mul; // + 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 = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32302772004019011584; + static_assert qm31_add_instruction_num==32284757605509529600; + + if (is_mul == TRUE) { + dw 32302772004019011584; + [ap - 1] = [ap -1]; + } else { + dw 32284757605509529600; + } + // dw 32302772004019011584; + return [ap]; +} + +// offset0 = 0; +// offset1 = (2**15)-4 +// offset2 = (2**15)-3 + +// flag_dst_base_fp = 0 +// flag_op0_base_fp = 1 +// flag_op1_imm = 0 +// flag_op1_base_fp = 1 +// flag_op1_base_ap = 0 +// flag_res_add = 0 +// flag_res_mul = is_mul # +// flag_PC_update_jump = 0 +// flag_PC_update_jump_rel = 0 +// flag_PC_update_jnz = 0 +// flag_ap_update_add = 0 +// flag_ap_update_add_1 = 0 +// flag_opcode_call = 0 +// flag_opcode_ret = 0 +// flag_opcode_assert_eq = 1 + +// flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+0*(2**6)+flag_opcode_assert_eq*(2**14) +// flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+1*(2**6)+flag_opcode_assert_eq*(2**14) +// qm31_opcode_extension_num = 3 +// qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63) +// qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63) + +func run_qm31_operation_get_op1( + is_mul: felt, + dst: felt, + op0: felt, +) -> felt { + //alloc_locals; + + let offset0 = (2**15)-4; + let offset1 = (2**15)-3; + let offset2 = 2**15; + + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 1; + let flag_op1_imm = 0; + let flag_op1_base_fp = 0; + let flag_op1_base_ap = 1; + let flag_res_add = 0; + let flag_res_mul = is_mul; // + 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 = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32305305291694374908; + static_assert qm31_add_instruction_num==32287290893184892924; + + if (is_mul == TRUE) { + dw 32305305291694374908; + [ap - 1] = [ap -1]; + } else { + dw 32287290893184892924; + } + return [ap]; +} + +// offset0 = (2**15)-4 +// offset1 = (2**15)-3 +// offset2 = 2**15 + +// flag_dst_base_fp = 1 +// flag_op0_base_fp = 1 +// flag_op1_imm = 0 +// flag_op1_base_fp = 0 +// flag_op1_base_ap = 1 +// flag_res_add = 0 +// flag_res_mul = is_mul // +// flag_PC_update_jump = 0 +// flag_PC_update_jump_rel = 0 +// flag_PC_update_jnz = 0 +// flag_ap_update_add = 0 +// flag_ap_update_add_1 = 0 +// flag_opcode_call = 0 +// flag_opcode_ret = 0 +// flag_opcode_assert_eq = 1 + +// flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**6)+flag_opcode_assert_eq*(2**14) +// flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**6)+flag_opcode_assert_eq*(2**14) +// qm31_opcode_extension_num = 3; +// qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); +// qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + +func run_qm31_operation_get_op0( + is_mul: felt, + dst: felt, + op1: felt, +) -> felt { + //alloc_locals; + + let offset0 = (2**15)-4; + let offset1 = 2**15; + let offset2 = (2**15)-3; + + let flag_dst_base_fp = 1; + let flag_op0_base_fp = 0; + 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 = is_mul; // + 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 = 1; + + let flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**6)+flag_opcode_assert_eq*(2**14); + let flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**6)+flag_opcode_assert_eq*(2**14); + let qm31_opcode_extension_num = 3; + let qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63); + let qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63); + static_assert qm31_mul_instruction_num==32302490529042563068; + static_assert qm31_add_instruction_num==32284476130533081084; + + if (is_mul == TRUE) { + dw 32302490529042563068; + [ap - 1] = [ap -1]; + } else { + dw 32284476130533081084; + } + return [ap]; +} + +// offset0 = (2**15)-4 +// offset1 = 2**15 +// offset2 = (2**15)-3 + +// flag_dst_base_fp = 1 +// flag_op0_base_fp = 0 +// flag_op1_imm = 0 +// flag_op1_base_fp = 1 +// flag_op1_base_ap = 0 +// flag_res_add = 0 +// flag_res_mul = is_mul // +// flag_PC_update_jump = 0 +// flag_PC_update_jump_rel = 0 +// flag_PC_update_jnz = 0 +// flag_ap_update_add = 0 +// flag_ap_update_add_1 = 0 +// flag_opcode_call = 0 +// flag_opcode_ret = 0 +// flag_opcode_assert_eq = 1 + +// flag_num_qm31_add = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+0*(2**6)+flag_opcode_assert_eq*(2**14) +// flag_num_qm31_mul = flag_dst_base_fp+flag_op0_base_fp*(2**1)+flag_op1_imm*(2**2)+flag_op1_base_fp*(2**3)+flag_op1_base_ap*(2**4)+1*(2**6)+flag_opcode_assert_eq*(2**14) +// qm31_opcode_extension_num = 3 +// qm31_add_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_add*(2**48) + qm31_opcode_extension_num*(2**63) +// qm31_mul_instruction_num = offset0 + offset1*(2**16) + offset2*(2**32) + flag_num_qm31_mul*(2**48) + qm31_opcode_extension_num*(2**63) \ No newline at end of file diff --git a/vm/src/math_utils/mod.rs b/vm/src/math_utils/mod.rs index bd5ff0935d..fda5b80e07 100644 --- a/vm/src/math_utils/mod.rs +++ b/vm/src/math_utils/mod.rs @@ -15,6 +15,8 @@ use num_traits::{One, Signed, Zero}; use rand::{rngs::SmallRng, SeedableRng}; use starknet_types_core::felt::NonZeroFelt; +//use stwo-prover::core::fields::m31::M31; + lazy_static! { pub static ref SIGNED_FELT_MAX: BigUint = (&*CAIRO_PRIME).shr(1_u32); static ref POWERS_OF_TWO: Vec = @@ -24,6 +26,8 @@ lazy_static! { .collect::>(); } +const STWO_PRIME: u128 = (1 << 31) - 1; + /// Returns the `n`th (up to the `251`th power) power of 2 as a [`Felt252`] /// in constant time. /// It silently returns `1` if the input is out of bounds. @@ -66,6 +70,216 @@ pub fn signed_felt(felt: Felt252) -> BigInt { } } +fn qm31_packed_reduced_read_coordinates(felt: Felt252) -> Result<[u128; 4], MathError> { + let limbs = felt.to_le_digits(); + if limbs[3] != 0 || limbs[2] >= 1 << 16 { + return Err(MathError::QM31UpackingError(Box::new(felt))); + } + let coordinates = [ + (limbs[0] & ((1 << 36) - 1)) as u128, + ((limbs[0] >> 36) + ((limbs[1] & ((1 << 8) - 1)) << 28)) as u128, + ((limbs[1] >> 8) & ((1 << 36) - 1)) as u128, + ((limbs[1] >> 44) + (limbs[2] << 20)) as u128, + ]; + for x in coordinates.iter() { + if *x >= STWO_PRIME { + return Err(MathError::QM31UnreducedError(Box::new(felt))); + } + } + Ok(coordinates) +} + +fn qm31_coordinates_to_packed_reduced(coordinates: [u128; 4]) -> Felt252 { + let bytes_part1 = + ((coordinates[0] % STWO_PRIME) + ((coordinates[1] % STWO_PRIME) << 36)).to_le_bytes(); + let bytes_part2 = + ((coordinates[2] % STWO_PRIME) + ((coordinates[3] % STWO_PRIME) << 36)).to_le_bytes(); + let mut result_bytes = [0u8; 32]; + result_bytes[0..9].copy_from_slice(&bytes_part1[0..9]); + result_bytes[9..18].copy_from_slice(&bytes_part2[0..9]); + Felt252::from_bytes_le(&result_bytes) +} + +pub fn qm31_packed_reduced_add(felt1: Felt252, felt2: Felt252) -> Result { + let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?; + let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?; + let result_unreduced_coordinates = [ + coordinates1[0] + coordinates2[0], + coordinates1[1] + coordinates2[1], + coordinates1[2] + coordinates2[2], + coordinates1[3] + coordinates2[3], + ]; + Ok(qm31_coordinates_to_packed_reduced( + result_unreduced_coordinates, + )) +} + +pub fn qm31_packed_reduced_neg(felt: Felt252) -> Result { + let coordinates = qm31_packed_reduced_read_coordinates(felt)?; + Ok(qm31_coordinates_to_packed_reduced([ + STWO_PRIME - coordinates[0], + STWO_PRIME - coordinates[1], + STWO_PRIME - coordinates[2], + STWO_PRIME - coordinates[3], + ])) +} + +pub fn qm31_packed_reduced_sub(felt1: Felt252, felt2: Felt252) -> Result { + let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?; + let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?; + let result_unreduced_coordinates = [ + STWO_PRIME + coordinates1[0] - coordinates2[0], + STWO_PRIME + coordinates1[1] - coordinates2[1], + STWO_PRIME + coordinates1[2] - coordinates2[2], + STWO_PRIME + coordinates1[3] - coordinates2[3], + ]; + Ok(qm31_coordinates_to_packed_reduced( + result_unreduced_coordinates, + )) +} + +pub fn qm31_packed_reduced_mul(felt1: Felt252, felt2: Felt252) -> Result { + let coordinates1 = qm31_packed_reduced_read_coordinates(felt1)?; + let coordinates2 = qm31_packed_reduced_read_coordinates(felt2)?; + let result_unreduced_coordinates = [ + coordinates1[0] * coordinates2[0] +STWO_PRIME*STWO_PRIME- coordinates1[1] * coordinates2[1] // how minus? + + 2 * (coordinates1[2] * coordinates2[2] +STWO_PRIME*STWO_PRIME - coordinates1[3] * coordinates2[3]) + - coordinates1[2] * coordinates2[3] + - coordinates1[3] * coordinates2[2], + coordinates1[0] * coordinates2[1] + + coordinates2[0] * coordinates1[1] + + 2 * (coordinates1[2] * coordinates2[3] + coordinates1[3] * coordinates2[2]) + + coordinates1[2] * coordinates2[2] + - coordinates1[3] * coordinates2[3], + coordinates1[0] * coordinates2[2] + STWO_PRIME * STWO_PRIME + - coordinates1[1] * coordinates2[3] + + coordinates1[2] * coordinates2[0] + - coordinates1[3] * coordinates2[1], + coordinates1[0] * coordinates2[3] + + coordinates1[1] * coordinates2[2] + + coordinates1[2] * coordinates2[1] + + coordinates1[3] * coordinates2[0], + ]; + Ok(qm31_coordinates_to_packed_reduced( + result_unreduced_coordinates, + )) +} + +// fn mod_inverse(a: u128, p: u128) -> Option { +// let (g, x) = extended_gcd_u128(a, p); +// if g != 1 { +// // Modular inverse doesn't exist if gcd(a, p) != 1 +// return None; +// } +// // Ensure positive result using modular arithmetic +// Some((x % p + p) % p) +// } + +// // Extended Euclidean algorithm for u128 (no negative values) +// fn extended_gcd_u128(mut a: u128, mut b: u128) -> (u128, u128) { +// let (mut x0, mut x1) = (1u128, 0u128); + +// while b > 0 { +// let q = a / b; +// let (new_a, new_b) = (b, a % b); +// a = new_a; +// b = new_b; + +// let (new_x0, new_x1) = (x1, x0.wrapping_sub(q.wrapping_mul(x1))); +// x0 = new_x0; +// x1 = new_x1; +// } + +// (a, x0) // Return gcd and the modular inverse coefficient +// } + +/// Computes `v^(STWO_PRIME-2) modulo STWO_PRIME`. +pub fn pow2147483645(v: u128) -> u128 { + let t0 = (sqn(v, 2) * v) % STWO_PRIME; + let t1 = (sqn(t0, 1) * t0) % STWO_PRIME; + let t2 = (sqn(t1, 3) * t0) % STWO_PRIME; + let t3 = (sqn(t2, 1) * t0) % STWO_PRIME; + let t4 = (sqn(t3, 8) * t3) % STWO_PRIME; + let t5 = (sqn(t4, 8) * t3) % STWO_PRIME; + (sqn(t5, 7) * t2) % STWO_PRIME +} + +/// Computes `v^(2*n) modulo STWO_PRIME`. +fn sqn(v: u128, n: usize) -> u128 { + let mut u = v; + for _ in 0..n { + u = (u * u) % STWO_PRIME; + } + u +} + +pub fn qm31_packed_reduced_inv(felt: Felt252) -> Result { + if felt.is_zero() { + return Err(MathError::DividedByZero); // + } + let coordinates = qm31_packed_reduced_read_coordinates(felt)?; + + //let b2 = self.b * self.b; + let b2_r = (coordinates[2] * coordinates[2] + STWO_PRIME * STWO_PRIME + - coordinates[3] * coordinates[3]) + % STWO_PRIME; + let b2_i = (2 * coordinates[2] * coordinates[3]) % STWO_PRIME; + + // println!("b2_r: {} b2_i: {}", b2_r, b2_i); + + // let ib2 = CM31 { a: -b2.b, b: b2.a }; + + // let denom_r = coordinates[0] * coordinates[0] - coordinates[1] * coordinates[1] + // - (coordinates[2] * coordinates[2] - coordinates[3] * coordinates[3]) + // - (coordinates[2] * coordinates[3] + coordinates[3] * coordinates[2]); + + // let denom = self.a * self.a - (b2 + b2 + ib2); + let denom_r = (coordinates[0] * coordinates[0] + STWO_PRIME * STWO_PRIME + - coordinates[1] * coordinates[1] + + 2 * STWO_PRIME + - 2 * b2_r + + b2_i) + % STWO_PRIME; // how minus? + let denom_i = + (2 * coordinates[0] * coordinates[1] + 3 * STWO_PRIME - 2 * b2_i - b2_r) % STWO_PRIME; // how minus? pad with multiple of STWO_PRIME? + // implement M31? copy? + // use i128? + //println!("denom_r: {} denom_i: {}", denom_r, denom_i); + + // let denom_norm_inverse_squared = (denom_r*denom_r + denom_i*denom_i).inv; + let denom_norm_squared = (denom_r * denom_r + denom_i * denom_i) % STWO_PRIME; + //println!("denom_norm_squared: {}", denom_norm_squared); + //let denom_norm_inverse_squared = mod_inverse(denom_norm_squared, STWO_PRIME).unwrap(); //pow2147483645 + let denom_norm_inverse_squared = pow2147483645(denom_norm_squared); + //println!("denom_norm_inverse_squared: {}", denom_norm_inverse_squared); + + // let denom_inverse = denom.inverse(); + let denom_inverse_r = (denom_r * denom_norm_inverse_squared) % STWO_PRIME; + let denom_inverse_i = ((STWO_PRIME - denom_i) * denom_norm_inverse_squared) % STWO_PRIME; + // println!( + // "denom_inverse_r: {} denom_inverse_i: {}", + // denom_inverse_r, denom_inverse_i + // ); + + //QM31 { a: self.a * denom_inverse, b: -self.b * denom_inverse } + Ok(qm31_coordinates_to_packed_reduced([ + coordinates[0] * denom_inverse_r + STWO_PRIME * STWO_PRIME + - coordinates[1] * denom_inverse_i, + coordinates[0] * denom_inverse_i + coordinates[1] * denom_inverse_r, + coordinates[3] * denom_inverse_i + STWO_PRIME * STWO_PRIME + - coordinates[2] * denom_inverse_r, + 2 * STWO_PRIME * STWO_PRIME + - coordinates[2] * denom_inverse_i + - coordinates[3] * denom_inverse_r, + ])) +} + +pub fn qm31_packed_reduced_div(felt1: Felt252, felt2: Felt252) -> Result { + //qm31_packed_reduced_inv(felt2).and_then(|inv| qm31_packed_reduced_mul(felt1, inv)) + let felt2_inv = qm31_packed_reduced_inv(felt2)?; + qm31_packed_reduced_mul(felt1, felt2_inv) +} + ///Returns the integer square root of the nonnegative integer n. ///This is the floor of the exact square root of n. ///Unlike math.sqrt(), this function doesn't have rounding error issues. @@ -946,6 +1160,142 @@ mod tests { ) } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_add_test() { + let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let y_coordinates = [1234567890, 1414213562, 1732050807, 1618033988]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let y = qm31_coordinates_to_packed_reduced(y_coordinates); + let res = qm31_packed_reduced_add(x, y).unwrap(); + let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!( + res_coordinates, + Ok([ + (1414213562 + 1234567890) % STWO_PRIME, + (1732050807 + 1414213562) % STWO_PRIME, + (1618033988 + 1732050807) % STWO_PRIME, + (1234567890 + 1618033988) % STWO_PRIME, + ]) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_sub_test() { + let x_coordinates = [ + (1414213562 + 1234567890) % STWO_PRIME, + (1732050807 + 1414213562) % STWO_PRIME, + (1618033988 + 1732050807) % STWO_PRIME, + (1234567890 + 1618033988) % STWO_PRIME, + ]; + let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let y = qm31_coordinates_to_packed_reduced(y_coordinates); + let res = qm31_packed_reduced_sub(x, y).unwrap(); + let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!( + res_coordinates, + Ok([1234567890, 1414213562, 1732050807, 1618033988]) + ); + } + + // x*y coordinates_to_packed([947980980, 1510986506, 623360030, 1260310989]), + // x coordinates_to_packed([1414213562, 1732050807, 1618033988, 1234567890]), + // y coordinates_to_packed([1259921049, 1442249570, 1847759065, 2094551481]), + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_mul_test() { + let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let y = qm31_coordinates_to_packed_reduced(y_coordinates); + let res = qm31_packed_reduced_mul(x, y).unwrap(); + let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!( + res_coordinates, + Ok([947980980, 1510986506, 623360030, 1260310989]) + ); + } + + // #[test] + // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // fn qm31_packed_reduced_mul_test2() { + // let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + // let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + // let x = qm31_coordinates_to_packed_reduced(x_coordinates); + // let y = qm31_coordinates_to_packed_reduced(y_coordinates); + // let res = qm31_packed_reduced_mul(x, y).unwrap(); + // let res_coordinates = qm31_packed_reduced_read_coordinates(res); + // assert_eq!( + // res_coordinates, + // Ok([947980980, 1510986506, 623360030, 1260310989]) + // ); + // } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_inv_test_1() { + let x_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let res = qm31_packed_reduced_inv(x).unwrap(); + // let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!(qm31_packed_reduced_mul(x, res), Ok(Felt252::from(1))); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_inv_test_2() { + let x_coordinates = [1, 2, 3, 4]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let res = qm31_packed_reduced_inv(x).unwrap(); + // let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!(qm31_packed_reduced_mul(x, res), Ok(Felt252::from(1))); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_div_test_1() { + let x_coordinates = [947980980, 1510986506, 623360030, 1260310989]; + let y_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let y = qm31_coordinates_to_packed_reduced(y_coordinates); + let res = qm31_packed_reduced_div(x, y).unwrap(); + let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!( + res_coordinates, + Ok([1259921049, 1442249570, 1847759065, 2094551481]) + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn qm31_packed_reduced_div_test_2() { + let x_coordinates = [947980980, 1510986506, 623360030, 1260310989]; + let y_coordinates = [1259921049, 1442249570, 1847759065, 2094551481]; + let x = qm31_coordinates_to_packed_reduced(x_coordinates); + let y = qm31_coordinates_to_packed_reduced(y_coordinates); + let res = qm31_packed_reduced_div(x, y).unwrap(); + let res_coordinates = qm31_packed_reduced_read_coordinates(res); + assert_eq!( + res_coordinates, + Ok([1414213562, 1732050807, 1618033988, 1234567890]) + ); + } + + // #[test] + // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + // fn qm31_packed_reduced_inv_ok_test() { + // let x_coordinates = [1414213562, 1732050807, 1618033988, 1234567890]; + // let x = qm31_coordinates_to_packed_reduced(x_coordinates); + // let res = qm31_packed_reduced_inv(x).unwrap(); + // let res_coordinates = qm31_packed_reduced_read_coordinates(res); + // assert_eq!( + // res_coordinates, + // Ok([1414213562, 1732050807, 1618033988, 1234567890]) + // ); + // } + #[cfg(feature = "std")] proptest! { #[test] diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index c2dea2f4b2..f5a5668bed 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 qm31_opcodes_test() { + let program_data = + include_bytes!("../../../cairo_programs/stwo_exclusive_programs/qm31_opcodes_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/errors/math_errors.rs b/vm/src/types/errors/math_errors.rs index 2a12efe12a..cff84f8649 100644 --- a/vm/src/types/errors/math_errors.rs +++ b/vm/src/types/errors/math_errors.rs @@ -65,6 +65,10 @@ pub enum MathError { "Operation failed: divmod({}, {}, {}), igcdex({}, {}) != 1 ", (*.0).0, (*.0).1, (*.0).2, (*.0).1, (*.0).2 )] DivModIgcdexNotZero(Box<(BigInt, BigInt, BigInt)>), + #[error("Numbers over 144 bit cannot be packed QM31 elements: {0})")] + QM31UpackingError(Box), + #[error("Number is not a packing of a QM31 in reduced form: {0})")] + QM31UnreducedError(Box), } #[cfg(test)] diff --git a/vm/src/types/instruction.rs b/vm/src/types/instruction.rs index 8f598ccd6e..1dfed2d818 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, + QM31Operations, } impl Instruction { diff --git a/vm/src/vm/decoding/decoder.rs b/vm/src/vm/decoding/decoder.rs index cb050b4415..dcb62da5d0 100644 --- a/vm/src/vm/decoding/decoder.rs +++ b/vm/src/vm/decoding/decoder.rs @@ -105,6 +105,16 @@ pub fn decode_instruction(encoded_instr: u128) -> Result OpcodeExtension::Stone, + 3 => { + if res == Res::Add + || (op1_addr != Op1Addr::FP && op1_addr != Op1Addr::AP) + || pc_update != PcUpdate::Regular + || opcode != Opcode::AssertEq + { + return Err(VirtualMachineError::InvalidQM31AddMulFlags(flags)); + } + OpcodeExtension::QM31Operations + } _ => { return Err(VirtualMachineError::InvalidOpcodeExtension( opcode_extension_num, @@ -407,4 +417,28 @@ mod decoder_test { let error = decode_instruction(0x1104800180018001); 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 + // // 31 ... 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 + // // 31 ... 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))); + // } } diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index 3733c12270..87b0eb9829 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -62,6 +62,10 @@ pub enum VirtualMachineError { "Failed to compute Res.MUL: Could not complete computation of non pure values {} * {}", (*.0).0, (*.0).1 )] ComputeResRelocatableMul(Box<(MaybeRelocatable, MaybeRelocatable)>), + #[error( + "Failed to compute Res.ADD for QM31Operations: Could not complete computation of non pure values {} * {}", (*.0).0, (*.0).1 + )] + ComputeResRelocatableQM31Add(Box<(MaybeRelocatable, MaybeRelocatable)>), #[error("Couldn't compute operand {}. Unknown value for memory cell {}", (*.0).0, (*.0).1)] FailedToComputeOperands(Box<(String, Relocatable)>), #[error("An ASSERT_EQ instruction failed: {} != {}.", (*.0).0, (*.0).1)] @@ -138,6 +142,10 @@ pub enum VirtualMachineError { RelocationNotFound(usize), #[error("{} batch size is not {}", (*.0).0, (*.0).1)] ModBuiltinBatchSize(Box<(BuiltinName, usize)>), + #[error("QM31 add mul opcode invalid flags {0}")] + InvalidQM31AddMulFlags(u128), + #[error("Unimplemented arithmetics with opcode extension")] + OpcodeExtensionUnimplementedArithmetics, } #[cfg(test)] diff --git a/vm/src/vm/vm_core.rs b/vm/src/vm/vm_core.rs index 8a8c794df6..ed59e420b9 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::{ + qm31_packed_reduced_add, qm31_packed_reduced_div, qm31_packed_reduced_mul, + qm31_packed_reduced_sub, signed_felt, +}; use crate::stdlib::{any::Any, borrow::Cow, collections::HashMap, prelude::*}; use crate::types::builtin_name::BuiltinName; #[cfg(feature = "extensive_hints")] @@ -9,7 +12,8 @@ use crate::{ errors::math_errors::MathError, exec_scope::ExecutionScopes, instruction::{ - is_call_instruction, ApUpdate, FpUpdate, Instruction, Opcode, PcUpdate, Res, + is_call_instruction, ApUpdate, FpUpdate, Instruction, Opcode, OpcodeExtension, + PcUpdate, Res, }, relocatable::{MaybeRelocatable, Relocatable}, }, @@ -216,6 +220,9 @@ impl VirtualMachine { } } + ///Returns a tuple (deduced_op0, deduced_res). + ///Deduces the value of op0 if possible (based on dst and op1). Otherwise, returns None. + ///If res was already deduced, returns its deduced value as well. ///Returns a tuple (deduced_op0, deduced_res). ///Deduces the value of op0 if possible (based on dst and op1). Otherwise, returns None. ///If res was already deduced, returns its deduced value as well. @@ -232,20 +239,37 @@ impl VirtualMachine { )), None, )), - Opcode::AssertEq => match (&instruction.res, dst, op1) { - (Res::Add, Some(dst_addr), Some(op1_addr)) => { - Ok((Some(dst_addr.sub(op1_addr)?), dst.cloned())) + Opcode::AssertEq => match (&instruction.res, dst, op1, &instruction.opcode_extension) { + (Res::Add, Some(dst_addr), Some(op1_addr), OpcodeExtension::Stone) => { + Ok((Some(dst_addr.sub(op1_addr)?), dst.cloned())) // } ( - Res::Mul, + Res::Op1, Some(MaybeRelocatable::Int(num_dst)), Some(MaybeRelocatable::Int(num_op1)), - ) if !num_op1.is_zero() => Ok(( - Some(MaybeRelocatable::Int(num_dst.field_div( - &num_op1.try_into().map_err(|_| MathError::DividedByZero)?, - ))), + OpcodeExtension::QM31Operations, + ) => Ok(( + Some(MaybeRelocatable::Int(qm31_packed_reduced_sub( + *num_dst, *num_op1, + )?)), // dst.cloned(), )), + ( + Res::Mul, + Some(MaybeRelocatable::Int(num_dst)), + Some(MaybeRelocatable::Int(num_op1)), + OpcodeExtension::Stone | OpcodeExtension::QM31Operations, + ) if !num_op1.is_zero() => { + let op0_val = + if instruction.opcode_extension == OpcodeExtension::Stone { + MaybeRelocatable::Int(num_dst.field_div( + &num_op1.try_into().map_err(|_| MathError::DividedByZero)?, + )) + } else { + MaybeRelocatable::Int(qm31_packed_reduced_div(*num_dst, *num_op1)?) + }; + Ok((Some(op0_val), dst.cloned())) + } _ => Ok((None, None)), }, _ => Ok((None, None)), @@ -262,28 +286,47 @@ impl VirtualMachine { op0: Option, ) -> Result<(Option, Option), VirtualMachineError> { if let Opcode::AssertEq = instruction.opcode { - match instruction.res { - Res::Op1 => return Ok((dst.cloned(), dst.cloned())), - Res::Add => { + match (instruction.res, instruction.opcode_extension) { + (Res::Op1, OpcodeExtension::Stone) => return Ok((dst.cloned(), dst.cloned())), + (Res::Add, OpcodeExtension::Stone) => { return Ok(( dst.zip(op0).and_then(|(dst, op0)| dst.sub(&op0).ok()), dst.cloned(), )) } - Res::Mul => match (dst, op0) { - ( + (Res::Op1, OpcodeExtension::QM31Operations) => { + if let ( Some(MaybeRelocatable::Int(num_dst)), Some(MaybeRelocatable::Int(num_op0)), - ) if !num_op0.is_zero() => { + ) = (dst, op0) + { return Ok(( - Some(MaybeRelocatable::Int(num_dst.field_div( - &num_op0.try_into().map_err(|_| MathError::DividedByZero)?, - ))), + Some(MaybeRelocatable::Int(qm31_packed_reduced_sub( + *num_dst, num_op0, + )?)), // dst.cloned(), - )) + )); } - _ => (), - }, + } + (Res::Mul, OpcodeExtension::Stone | OpcodeExtension::QM31Operations) => { + match (dst, op0) { + ( + Some(MaybeRelocatable::Int(num_dst)), + Some(MaybeRelocatable::Int(num_op0)), + ) if !num_op0.is_zero() => { + let op1_val = if instruction.opcode_extension == OpcodeExtension::Stone + { + MaybeRelocatable::Int(num_dst.field_div( + &num_op0.try_into().map_err(|_| MathError::DividedByZero)?, + )) + } else { + MaybeRelocatable::Int(qm31_packed_reduced_div(*num_dst, num_op0)?) + }; + return Ok((Some(op1_val), dst.cloned())); + } + _ => (), + } + } _ => (), }; }; @@ -305,6 +348,7 @@ impl VirtualMachine { Ok(None) } + ///Computes the value of res if possible ///Computes the value of res if possible fn compute_res( &self, @@ -312,20 +356,37 @@ impl VirtualMachine { op0: &MaybeRelocatable, op1: &MaybeRelocatable, ) -> Result, VirtualMachineError> { - match instruction.res { - Res::Op1 => Ok(Some(op1.clone())), - Res::Add => Ok(Some(op0.add(op1)?)), - Res::Mul => { + match (instruction.res, instruction.opcode_extension) { + (Res::Op1, OpcodeExtension::Stone) => Ok(Some(op1.clone())), + (Res::Op1, OpcodeExtension::QM31Operations) => { if let (MaybeRelocatable::Int(num_op0), MaybeRelocatable::Int(num_op1)) = (op0, op1) { - return Ok(Some(MaybeRelocatable::Int(num_op0 * num_op1))); + return Ok(Some(MaybeRelocatable::Int(qm31_packed_reduced_add( + *num_op0, *num_op1, + )?))); + } + Err(VirtualMachineError::ComputeResRelocatableQM31Add(Box::new( + (op0.clone(), op1.clone()), + ))) + } + (Res::Add, OpcodeExtension::Stone) => Ok(Some(op0.add(op1)?)), + (Res::Mul, OpcodeExtension::Stone | OpcodeExtension::QM31Operations) => { + if let (MaybeRelocatable::Int(num_op0), MaybeRelocatable::Int(num_op1)) = (op0, op1) + { + if instruction.opcode_extension == OpcodeExtension::Stone { + return Ok(Some(MaybeRelocatable::Int(num_op0 * num_op1))); + } else { + return Ok(Some(MaybeRelocatable::Int(qm31_packed_reduced_mul( + *num_op0, *num_op1, + )?))); + } } Err(VirtualMachineError::ComputeResRelocatableMul(Box::new(( op0.clone(), op1.clone(), )))) } - Res::Unconstrained => Ok(None), + _ => Ok(None), } } @@ -352,6 +413,8 @@ impl VirtualMachine { None => Err(VirtualMachineError::UnconstrainedResAssertEq), Some(res) if res != &operands.dst => Err(VirtualMachineError::DiffAssertValues( Box::new((operands.dst.clone(), res.clone())), + //Box::new((operands.dst.clone(), operands.dst.clone())), + //Box::new((operands.dst.clone(), res.clone())), )), _ => Ok(()), },