diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e2790307..d7153a4691 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack x = pack(ids.x, PRIME) % SECP_P %} + +* Implement hint for `starkware.cairo.common.cairo_keccak.keccak._copy_inputs` as described by whitelist `starknet/security/whitelists/cairo_keccak.json` [#1058](https://github.com/lambdaclass/cairo-rs/pull/1058) + +`BuiltinHintProcessor` now supports the following hint: + + ```python + %{ ids.full_word = int(ids.n_bytes >= 8) %} ``` * Add alternative hint code for nondet_bigint3 hint [#1071](https://github.com/lambdaclass/cairo-rs/pull/1071) @@ -137,6 +144,8 @@ segments.write_arg(ids.blake2s_ptr_end, padding) %} +* Add `Program::iter_identifiers(&self) -> Iterator` to get an iterator over the program's identifiers [#1079](https://github.com/lambdaclass/cairo-rs/pull/1079) + * Implement hint on `assert_le_felt` for versions 0.6.0 and 0.8.2 [#1047](https://github.com/lambdaclass/cairo-rs/pull/1047): `BuiltinHintProcessor` now supports the following hints: @@ -169,6 +178,23 @@ ``` +* Add missing hints on whitelist [#1073](https://github.com/lambdaclass/cairo-rs/pull/1073): + + `BuiltinHintProcessor` now supports the following hints: + + ```python + ids.is_250 = 1 if ids.addr < 2**250 else 0 + ``` + + ```python + # Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. + ADDR_BOUND = ids.ADDR_BOUND % PRIME + assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' + ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 + ``` + * Implement hint on ec_recover.json whitelist [#1038](https://github.com/lambdaclass/cairo-rs/pull/1038): `BuiltinHintProcessor` now supports the following hint: @@ -439,6 +465,20 @@ ids.flag = 1 if k > 0 else 0 ``` +* Add missing hint on cairo_secp lib [#1057](https://github.com/lambdaclass/cairo-rs/pull/1057): + + `BuiltinHintProcessor` now supports the following hint: + + ```python + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + ``` + * Add missing hint on uint256_improvements lib [#1025](https://github.com/lambdaclass/cairo-rs/pull/1025): `BuiltinHintProcessor` now supports the following hint: diff --git a/cairo_programs/_keccak_alternative_hint.cairo b/cairo_programs/_keccak_alternative_hint.cairo index d3f725af38..d89ba6c8d2 100644 --- a/cairo_programs/_keccak_alternative_hint.cairo +++ b/cairo_programs/_keccak_alternative_hint.cairo @@ -113,6 +113,20 @@ func run_cairo_keccak{output_ptr: felt*, range_check_ptr, bitwise_ptr: BitwiseBu _prepare_block{keccak_ptr=output_ptr}(inputs=inputs, n_bytes=n_bytes, state=state); _block_permutation_cairo_keccak{keccak_ptr=output_ptr}(); + local full_word: felt; + %{ ids.full_word = int(ids.n_bytes >= 8) %} + assert full_word = 1; + + let n_bytes = 8; + local full_word: felt; + %{ ids.full_word = int(ids.n_bytes >= 8) %} + assert full_word = 1; + + let n_bytes = 7; + local full_word: felt; + %{ ids.full_word = int(ids.n_bytes >= 8) %} + assert full_word = 0; + return (); } diff --git a/cairo_programs/ec_double_slope.cairo b/cairo_programs/ec_double_slope.cairo new file mode 100644 index 0000000000..1017748233 --- /dev/null +++ b/cairo_programs/ec_double_slope.cairo @@ -0,0 +1,212 @@ +%builtins range_check + +// Source: https://github.com/rdubois-crypto/efficient-secp256r1/blob/4b74807c5e91f1ed4cb00a1c973be05c63986e61/src/secp256r1/ec.cairo +from starkware.cairo.common.cairo_secp.bigint import BigInt3, UnreducedBigInt3, nondet_bigint3 +from starkware.cairo.common.cairo_secp.ec import EcPoint + +// src.secp256r1.constants +// SECP_REM is defined by the equation: +// secp256r1_prime = 2 ** 256 - SECP_REM. +const SECP_REM = 2 ** 224 - 2 ** 192 - 2 ** 96 + 1; + +const BASE = 2 ** 86; + +// A = 0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc +const A0 = 0x3ffffffffffffffffffffc; +const A1 = 0x3ff; +const A2 = 0xffffffff0000000100000; + +// Constants for unreduced_mul/sqr +const s2 = (-(2 ** 76)) - 2 ** 12; +const s1 = (-(2 ** 66)) + 4; +const s0 = 2 ** 56; + +const r2 = 2 ** 54 - 2 ** 22; +const r1 = -(2 ** 12); +const r0 = 4; + +// src.secp256r1.field +// Adapt from starkware.cairo.common.math's assert_250_bit +func assert_165_bit{range_check_ptr}(value) { + const UPPER_BOUND = 2 ** 165; + const SHIFT = 2 ** 128; + const HIGH_BOUND = UPPER_BOUND / SHIFT; + + let low = [range_check_ptr]; + let high = [range_check_ptr + 1]; + + %{ + from starkware.cairo.common.math_utils import as_int + + # Correctness check. + value = as_int(ids.value, PRIME) % PRIME + assert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).' + + # Calculation for the assertion. + ids.high, ids.low = divmod(ids.value, ids.SHIFT) + %} + + assert [range_check_ptr + 2] = HIGH_BOUND - 1 - high; + + assert value = high * SHIFT + low; + + let range_check_ptr = range_check_ptr + 3; + return (); +} + +// src.secp256r1.field +// Computes the multiplication of two big integers, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Arguments: +// x, y - the two BigInt3 to operate on. +// +// Returns: +// x * y in an UnreducedBigInt3 representation (the returned limbs may be above 3 * BASE). +// +// This means that if unreduced_mul is called on the result of nondet_bigint3, or the difference +// between two such results, we have: +// Soundness guarantee: the limbs are in the range (). +// Completeness guarantee: the limbs are in the range (). +func unreduced_mul(a: BigInt3, b: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * b.d2; + tempvar d1d2 = a.d2 * b.d1 + a.d1 * b.d2; + return ( + UnreducedBigInt3( + d0=a.d0 * b.d0 + s0 * twice_d2 + r0 * d1d2, + d1=a.d1 * b.d0 + a.d0 * b.d1 + s1 * twice_d2 + r1 * d1d2, + d2=a.d2 * b.d0 + a.d1 * b.d1 + a.d0 * b.d2 + s2 * twice_d2 + r2 * d1d2, + ), + ); +} + +// src.secp256r1.field +// Computes the square of a big integer, given in BigInt3 representation, modulo the +// secp256r1 prime. +// +// Has the same guarantees as in unreduced_mul(a, a). +func unreduced_sqr(a: BigInt3) -> (res_low: UnreducedBigInt3) { + tempvar twice_d2 = a.d2 * a.d2; + tempvar twice_d1d2 = a.d2 * a.d1 + a.d1 * a.d2; + tempvar d1d0 = a.d1 * a.d0; + return ( + UnreducedBigInt3( + d0=a.d0 * a.d0 + s0 * twice_d2 + r0 * twice_d1d2, + d1=d1d0 + d1d0 + s1 * twice_d2 + r1 * twice_d1d2, + d2=a.d2 * a.d0 + a.d1 * a.d1 + a.d0 * a.d2 + s2 * twice_d2 + r2 * twice_d1d2, + ), + ); +} + +// src.secp256r1.field +// Verifies that the given unreduced value is equal to zero modulo the secp256r1 prime. +// +// Completeness assumption: val's limbs are in the range (-2**210.99, 2**210.99). +// Soundness assumption: val's limbs are in the range (-2**250, 2**250). +func verify_zero{range_check_ptr}(val: UnreducedBigInt3) { + alloc_locals; + local q; + // local q_sign; + let q_sign = 1; + // original: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ + // from starkware.cairo.common.cairo_secp.secp_utils import pack + + // q, r = divmod(pack(ids.val, PRIME), SECP_P) + // assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + // if q >= 0: + // ids.q = q % PRIME + // ids.q_sign = 1 + // else: + // ids.q = (0-q) % PRIME + // ids.q_sign = -1 % PRIME + // %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + q, r = divmod(pack(ids.val, PRIME), SECP_P) + assert r == 0, f"verify_zero: Invalid input {ids.val.d0, ids.val.d1, ids.val.d2}." + ids.q = q % PRIME + %} + // assert_250_bit(q); // 256K steps + // assert_le_felt(q, 2**165); // 275K steps + assert_165_bit(q); + assert q_sign * (val.d2 + val.d1 / BASE + val.d0 / BASE ** 2) = q * ( + (BASE / 4) - SECP_REM / BASE ** 2 + ); + // Multiply by BASE**2 both sides: + // (q_sign) * val = q * (BASE**3 / 4 - SECP_REM) + // = q * (2**256 - SECP_REM) = q * secp256r1_prime = 0 mod secp256r1_prime + return (); +} + +// Computes the slope of the elliptic curve at a given point. +// The slope is used to compute point + point. +// +// Arguments: +// point - the point to operate on. +// +// Returns: +// slope - the slope of the curve at point, in BigInt3 representation. +// +// Assumption: point != 0. +func compute_doubling_slope{range_check_ptr}(point: EcPoint) -> (slope: BigInt3) { + // Note that y cannot be zero: assume that it is, then point = -point, so 2 * point = 0, which + // contradicts the fact that the size of the curve is odd. + // originals: + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_P as SECP_P %} + // %{ from starkware.cairo.common.cairo_secp.secp_utils import SECP256R1_ALPHA as ALPHA %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_ALPHA as ALPHA %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) + %} + let (slope: BigInt3) = nondet_bigint3(); + + let (x_sqr: UnreducedBigInt3) = unreduced_sqr(point.x); + let (slope_y: UnreducedBigInt3) = unreduced_mul(slope, point.y); + verify_zero( + UnreducedBigInt3( + d0=3 * x_sqr.d0 + A0 - 2 * slope_y.d0, + d1=3 * x_sqr.d1 + A1 - 2 * slope_y.d1, + d2=3 * x_sqr.d2 + A2 - 2 * slope_y.d2, + ), + ); + + return (slope=slope); +} + +func test_doubling_slope{range_check_ptr}() { + let point = EcPoint(BigInt3(614323, 5456867, 101208), BigInt3(773712524, 77371252, 5298795)); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 64081873649130491683833713, 34843994309543177837008178, 16548672716077616016846383 + ); + + let point = EcPoint( + BigInt3(51215, 36848548548458, 634734734), BigInt3(26362, 263724839599, 901297012) + ); + + let (slope) = compute_doubling_slope(point); + + assert slope = BigInt3( + 71848883893335852660776740, 75644451964360469099209675, 547087410329256463669633 + ); + + return (); +} + +func main{range_check_ptr}() { + test_doubling_slope(); + return (); +} diff --git a/cairo_programs/normalize_address.cairo b/cairo_programs/normalize_address.cairo new file mode 100644 index 0000000000..b54c4daac4 --- /dev/null +++ b/cairo_programs/normalize_address.cairo @@ -0,0 +1,32 @@ +%builtins range_check + +from starkware.starknet.common.storage import normalize_address +from starkware.cairo.common.math import assert_250_bit +from starkware.cairo.common.alloc import alloc + +func normalize_address_element_array{range_check_ptr: felt}( + array: felt*, array_length: felt, iterator: felt +) { + if (iterator == array_length) { + return (); + } + normalize_address(array[iterator]); + return normalize_address_element_array(array, array_length, iterator + 1); +} + +func fill_array(array: felt*, base: felt, step: felt, array_length: felt, iterator: felt) { + if (iterator == array_length) { + return (); + } + assert array[iterator] = base + step * iterator; + return fill_array(array, base, step, array_length, iterator + 1); +} + +func main{range_check_ptr: felt}() { + alloc_locals; + tempvar array_length = 10; + let (array: felt*) = alloc(); + fill_array(array, 70000000000000000000, 300000000000000000, array_length, 0); + normalize_address_element_array(array, array_length, 0); + return (); +} diff --git a/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs b/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs index 3f8b751ae2..def90f380a 100644 --- a/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs +++ b/src/hint_processor/builtin_hint_processor/builtin_hint_processor_definition.rs @@ -6,8 +6,9 @@ use super::{ field_arithmetic::{u256_get_square_root, u384_get_square_root, uint384_div}, secp::{ ec_utils::{ - compute_slope_and_assing_secp_p, ec_double_assign_new_y, ec_mul_inner, - ec_negate_embedded_secp_p, ec_negate_import_secp_p, + compute_doubling_slope_external_consts, compute_slope_and_assing_secp_p, + ec_double_assign_new_y, ec_mul_inner, ec_negate_embedded_secp_p, + ec_negate_import_secp_p, }, secp_utils::{ALPHA, ALPHA_V2, SECP_P, SECP_P_V2}, }, @@ -26,7 +27,7 @@ use crate::{ }, cairo_keccak::keccak_hints::{ block_permutation_v1, block_permutation_v2, cairo_keccak_finalize_v1, - cairo_keccak_finalize_v2, compare_bytes_in_word_nondet, + cairo_keccak_finalize_v2, cairo_keccak_is_full_word, compare_bytes_in_word_nondet, compare_keccak_full_rate_in_bytes_nondet, keccak_write_args, }, dict_hint_utils::{ @@ -194,6 +195,10 @@ impl HintProcessor for BuiltinHintProcessor { hint_code::ASSERT_250_BITS => { assert_250_bit(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::IS_250_BITS => is_250_bits(vm, &hint_data.ids_data, &hint_data.ap_tracking), + hint_code::IS_ADDR_BOUNDED => { + is_addr_bounded(vm, &hint_data.ids_data, &hint_data.ap_tracking, constants) + } hint_code::IS_POSITIVE => is_positive(vm, &hint_data.ids_data, &hint_data.ap_tracking), hint_code::SPLIT_INT_ASSERT_RANGE => { split_int_assert_range(vm, &hint_data.ids_data, &hint_data.ap_tracking) @@ -455,7 +460,7 @@ impl HintProcessor for BuiltinHintProcessor { &hint_data.ids_data, &hint_data.ap_tracking, ), - hint_code::EC_DOUBLE_SCOPE_V1 => compute_doubling_slope( + hint_code::EC_DOUBLE_SLOPE_V1 => compute_doubling_slope( vm, exec_scopes, &hint_data.ids_data, @@ -464,7 +469,7 @@ impl HintProcessor for BuiltinHintProcessor { &SECP_P, &ALPHA, ), - hint_code::EC_DOUBLE_SCOPE_V2 => compute_doubling_slope( + hint_code::EC_DOUBLE_SLOPE_V2 => compute_doubling_slope( vm, exec_scopes, &hint_data.ids_data, @@ -473,7 +478,7 @@ impl HintProcessor for BuiltinHintProcessor { &SECP_P_V2, &ALPHA_V2, ), - hint_code::EC_DOUBLE_SCOPE_WHITELIST => compute_doubling_slope( + hint_code::EC_DOUBLE_SLOPE_V3 => compute_doubling_slope( vm, exec_scopes, &hint_data.ids_data, @@ -482,6 +487,12 @@ impl HintProcessor for BuiltinHintProcessor { &SECP_P, &ALPHA, ), + hint_code::EC_DOUBLE_SLOPE_EXTERNAL_CONSTS => compute_doubling_slope_external_consts( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + ), hint_code::COMPUTE_SLOPE_V1 => compute_slope_and_assing_secp_p( vm, exec_scopes, @@ -551,6 +562,9 @@ impl HintProcessor for BuiltinHintProcessor { hint_code::SHA256_FINALIZE => { sha256_finalize(vm, &hint_data.ids_data, &hint_data.ap_tracking) } + hint_code::CAIRO_KECCAK_INPUT_IS_FULL_WORD => { + cairo_keccak_is_full_word(vm, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::COMPARE_KECCAK_FULL_RATE_IN_BYTES_NONDET => { compare_keccak_full_rate_in_bytes_nondet( vm, diff --git a/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs b/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs index 1df1b0dcdc..771a0c4361 100644 --- a/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs +++ b/src/hint_processor/builtin_hint_processor/cairo_keccak/keccak_hints.rs @@ -7,7 +7,8 @@ use crate::{ felt::Felt252, hint_processor::{ builtin_hint_processor::hint_utils::{ - get_integer_from_var_name, get_ptr_from_var_name, insert_value_into_ap, + get_integer_from_var_name, get_ptr_from_var_name, insert_value_from_var_name, + insert_value_into_ap, }, hint_processor_definition::HintReference, }, @@ -181,6 +182,24 @@ pub(crate) fn block_permutation_v1( Ok(()) } +/* +Implements hint: + %{ + ids.full_word = int(ids.n_bytes >= 8) + %} +*/ +pub(crate) fn cairo_keccak_is_full_word( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let n_bytes = get_integer_from_var_name("n_bytes", vm, ids_data, ap_tracking)? + .to_usize() + .unwrap_or(8); // Hack: if it doesn't fit `usize` then it's >= 8 + let full_word = Felt252::from((n_bytes >= 8) as usize); + insert_value_from_var_name("full_word", &full_word, vm, ids_data, ap_tracking) +} + /* Implements hint: %{ @@ -362,10 +381,30 @@ mod tests { vm::vm_core::VirtualMachine, }; use assert_matches::assert_matches; + use rstest::*; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; + #[rstest] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + #[case(0, 0)] + #[case(1, 0)] + #[case(7, 0)] + #[case(8, 1)] + #[case(16, 1)] + #[case(usize::MAX as i128, 1)] + #[case(i128::MAX, 1)] + fn cairo_keccak_is_full_word(#[case] n_bytes: i128, #[case] full_bytes: usize) { + let hint_code = "ids.full_word = int(ids.n_bytes >= 8)"; + let mut vm = vm_with_range_check!(); + vm.segments = segments![((1, 1), n_bytes)]; + vm.run_context.fp = 2; + let ids_data = ids_data!["full_word", "n_bytes"]; + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + check_memory![vm.segments.memory, ((1, 0), full_bytes)]; + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn keccak_write_args_valid_test() { diff --git a/src/hint_processor/builtin_hint_processor/hint_code.rs b/src/hint_processor/builtin_hint_processor/hint_code.rs index 0a2894a815..6626a6543e 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -98,6 +98,15 @@ assert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).' # Calculation for the assertion. ids.high, ids.low = divmod(ids.value, ids.SHIFT)"#; +pub const IS_250_BITS: &str = r#"ids.is_250 = 1 if ids.addr < 2**250 else 0"#; + +pub const IS_ADDR_BOUNDED: &str = r#"# Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. +ADDR_BOUND = ids.ADDR_BOUND % PRIME +assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' +ids.is_small = 1 if ids.addr < ADDR_BOUND else 0"#; + pub const SPLIT_INT: &str = r#"memory[ids.output] = res = (int(ids.value) % PRIME) % ids.base assert res < ids.bound, f'split_int(): Limb {res} is out of range.'"#; @@ -618,7 +627,7 @@ y = pack(ids.point.y, PRIME) % SECP_P # The modulo operation in python always returns a nonnegative number. value = (-y) % SECP_P"#; -pub const EC_DOUBLE_SCOPE_V1: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack +pub const EC_DOUBLE_SLOPE_V1: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack from starkware.python.math_utils import ec_double_slope # Compute the slope. @@ -626,7 +635,7 @@ x = pack(ids.point.x, PRIME) y = pack(ids.point.y, PRIME) value = slope = ec_double_slope(point=(x, y), alpha=0, p=SECP_P)"#; -pub const EC_DOUBLE_SCOPE_V2: &str = r#"from starkware.python.math_utils import ec_double_slope +pub const EC_DOUBLE_SLOPE_V2: &str = r#"from starkware.python.math_utils import ec_double_slope from starkware.cairo.common.cairo_secp.secp_utils import pack SECP_P = 2**255-19 @@ -635,7 +644,7 @@ x = pack(ids.point.x, PRIME) y = pack(ids.point.y, PRIME) value = slope = ec_double_slope(point=(x, y), alpha=42204101795669822316448953119945047945709099015225996174933988943478124189485, p=SECP_P)"#; -pub const EC_DOUBLE_SCOPE_WHITELIST: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack +pub const EC_DOUBLE_SLOPE_V3: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack from starkware.python.math_utils import div_mod # Compute the slope. @@ -643,6 +652,14 @@ x = pack(ids.pt.x, PRIME) y = pack(ids.pt.y, PRIME) value = slope = div_mod(3 * x ** 2, 2 * y, SECP_P)"#; +pub const EC_DOUBLE_SLOPE_EXTERNAL_CONSTS: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import pack +from starkware.python.math_utils import ec_double_slope + +# Compute the slope. +x = pack(ids.point.x, PRIME) +y = pack(ids.point.y, PRIME) +value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P)"#; + pub const COMPUTE_SLOPE_V1: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack from starkware.python.math_utils import line_slope @@ -775,6 +792,8 @@ output_values = keccak_func(memory.get_range( ids.keccak_ptr_start, _keccak_state_size_felts)) segments.write_arg(ids.output, output_values)"#; +pub const CAIRO_KECCAK_INPUT_IS_FULL_WORD: &str = r#"ids.full_word = int(ids.n_bytes >= 8)"#; + pub const CAIRO_KECCAK_FINALIZE_V1: &str = r#"# Add dummy pairs of input and output. _keccak_state_size_felts = int(ids.KECCAK_STATE_SIZE_FELTS) _block_size = int(ids.BLOCK_SIZE) diff --git a/src/hint_processor/builtin_hint_processor/math_utils.rs b/src/hint_processor/builtin_hint_processor/math_utils.rs index 54a1797cc0..04e291ec97 100644 --- a/src/hint_processor/builtin_hint_processor/math_utils.rs +++ b/src/hint_processor/builtin_hint_processor/math_utils.rs @@ -32,6 +32,8 @@ use num_traits::{Signed, Zero}; use super::hint_utils::get_maybe_relocatable_from_var_name; +const ADDR_BOUND: &str = "starkware.starknet.common.storage.ADDR_BOUND"; + //Implements hint: memory[ap] = 0 if 0 <= (ids.a % PRIME) < range_check_builtin.bound else 1 pub fn is_nn( vm: &mut VirtualMachine, @@ -565,6 +567,65 @@ pub fn assert_250_bit( insert_value_from_var_name("low", low, vm, ids_data, ap_tracking) } +// Implements hint: +// %{ ids.is_250 = 1 if ids.addr < 2**250 else 0 %} +pub fn is_250_bits( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let addr = get_integer_from_var_name("addr", vm, ids_data, ap_tracking)?; + + // Main logic: ids.is_250 = 1 if ids.addr < 2**250 else 0 + let is_250 = Felt252::from((addr.as_ref().bits() <= 250) as u8); + + insert_value_from_var_name("is_250", is_250, vm, ids_data, ap_tracking) +} + +/* +Implements hint: +%{ + # Verify the assumptions on the relationship between 2**250, ADDR_BOUND and PRIME. + ADDR_BOUND = ids.ADDR_BOUND % PRIME + assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + ADDR_BOUND * 2 > PRIME), \ + 'normalize_address() cannot be used with the current constants.' + ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 +%} +*/ +pub fn is_addr_bounded( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, + constants: &HashMap, +) -> Result<(), HintError> { + let addr = get_integer_from_var_name("addr", vm, ids_data, ap_tracking)?; + let prime = Felt252::prime(); + + let addr_bound = constants + .get(ADDR_BOUND) + .ok_or(HintError::MissingConstant(ADDR_BOUND))? + .to_biguint(); + + let lower_bound = BigUint::one() << 250_u32; + let upper_bound = BigUint::one() << 251_u32; + + // assert (2**250 < ADDR_BOUND <= 2**251) and (2 * 2**250 < PRIME) and ( + // ADDR_BOUND * 2 > PRIME), \ + // 'normalize_address() cannot be used with the current constants.' + // The second check is not needed, as it's true for the CAIRO_PRIME + if !(lower_bound < addr_bound && addr_bound <= upper_bound && (&addr_bound << 1_u32) > prime) { + return Err(HintError::AssertionFailed( + "normalize_address() cannot be used with the current constants.".to_string(), + )); + } + + // Main logic: ids.is_small = 1 if ids.addr < ADDR_BOUND else 0 + let is_small = Felt252::from((addr.as_ref() < &Felt252::from(addr_bound)) as u8); + + insert_value_from_var_name("is_small", is_small, vm, ids_data, ap_tracking) +} + /* Implements hint: %{ @@ -1771,7 +1832,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_assert_250_bit_valid() { - let hint_code = "from starkware.cairo.common.math_utils import as_int\n\n# Correctness check.\nvalue = as_int(ids.value, PRIME) % PRIME\nassert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).'\n\n# Calculation for the assertion.\nids.high, ids.low = divmod(ids.value, ids.SHIFT)"; + let hint_code = hint_code::ASSERT_250_BITS; let mut vm = vm!(); //Initialize fp vm.run_context.fp = 3; @@ -1789,7 +1850,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_assert_250_bit_invalid() { - let hint_code = "from starkware.cairo.common.math_utils import as_int\n\n# Correctness check.\nvalue = as_int(ids.value, PRIME) % PRIME\nassert value < ids.UPPER_BOUND, f'{value} is outside of the range [0, 2**250).'\n\n# Calculation for the assertion.\nids.high, ids.low = divmod(ids.value, ids.SHIFT)"; + let hint_code = hint_code::ASSERT_250_BITS; let mut vm = vm!(); //Initialize fp vm.run_context.fp = 3; @@ -1811,6 +1872,135 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_250_bits_valid() { + let hint_code = "ids.is_250 = 1 if ids.addr < 2**250 else 0"; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![((1, 0), 1152251)]; + //Create ids + let ids_data = ids_data!["addr", "is_250"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_250_bits_invalid() { + let hint_code = "ids.is_250 = 1 if ids.addr < 2**250 else 0"; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + //ids.value + vm.segments = segments![( + (1, 0), + ( + "3618502788666131106986593281521497120414687020801267626233049500247285301248", + 10 + ) + )]; + //Create ids + let ids_data = ids_data!["addr", "is_250"]; + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(())); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 0)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_ok() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + let addr_bound = felt_str!( + "3618502788666131106986593281521497120414687020801267626233049500247285301000" + ); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![( + (1, 0), + ( + "1809251394333067160431340899751024102169435851563236335319518532916477952000", + 10 + ) + ),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!( + vm, + ids_data, + hint_code, + exec_scopes_ref!(), + &[(ADDR_BOUND, addr_bound)] + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect() + ), + Ok(()) + ); + //Check ids.is_low + check_memory![vm.segments.memory, ((1, 1), 1)]; + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_assert_fail() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + let addr_bound = Felt252::one(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![( + (1, 0), + ( + "3618502788666131106986593281521497120414687020801267626233049500247285301000", + 10 + ) + ),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!( + vm, + ids_data, + hint_code, + exec_scopes_ref!(), + &HashMap::from([(ADDR_BOUND.to_string(), addr_bound)]) + ), + Err(HintError::AssertionFailed(msg)) + if msg == "normalize_address() cannot be used with the current constants." + ); + } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_is_addr_bounded_missing_const() { + let hint_code = hint_code::IS_ADDR_BOUNDED; + let mut vm = vm!(); + //Initialize fp + vm.run_context.fp = 2; + //Insert ids into memory + vm.segments = segments![((1, 0), 0),]; + //Create ids + let ids_data = ids_data!["addr", "is_small"]; + //Execute the hint + assert_matches!( + run_hint!(vm, ids_data, hint_code), + Err(HintError::MissingConstant(ADDR_BOUND)) + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_split_felt_ok() { diff --git a/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs b/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs index f80fe6caf1..1888aa8750 100644 --- a/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs +++ b/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs @@ -138,6 +138,35 @@ pub fn compute_doubling_slope( Ok(()) } +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import ec_double_slope + + # Compute the slope. + x = pack(ids.point.x, PRIME) + y = pack(ids.point.y, PRIME) + value = slope = ec_double_slope(point=(x, y), alpha=ALPHA, p=SECP_P) +%} +*/ +pub fn compute_doubling_slope_external_consts( + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + //ids.point + let point = EcPoint::from_var_name("point", vm, ids_data, ap_tracking)?; + let secp_p: BigInt = exec_scopes.get("SECP_P")?; + let alpha: BigInt = exec_scopes.get("ALPHA")?; + + let value = ec_double_slope(&(point.x.pack86(), point.y.pack86()), &alpha, &secp_p); + exec_scopes.insert_value("value", value.clone()); + exec_scopes.insert_value("slope", value); + Ok(()) +} + /* Implements hint: %{ @@ -457,7 +486,6 @@ mod tests { }; use assert_matches::assert_matches; - use num_bigint::BigUint; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -538,14 +566,14 @@ mod tests { ( "value", bigint_str!( - "40442433062102151071094722250325492738932110061897694430475034100717288403728" - ) + "40442433062102151071094722250325492738932110061897694430475034100717288403728" + ) ), ( "slope", bigint_str!( - "40442433062102151071094722250325492738932110061897694430475034100717288403728" - ) + "40442433062102151071094722250325492738932110061897694430475034100717288403728" + ) ) ] ); @@ -554,7 +582,7 @@ mod tests { #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_ec_double_scope_v2_hint_ok() { - let hint_code = hint_code::EC_DOUBLE_SCOPE_V2; + let hint_code = hint_code::EC_DOUBLE_SLOPE_V2; let mut vm = vm_with_range_check!(); vm.segments = segments![ ((1, 0), 512), @@ -579,14 +607,14 @@ mod tests { ( "value", bigint_str!( - "48268701472940295594394094960749868325610234644833445333946260403470540790234" - ) + "48268701472940295594394094960749868325610234644833445333946260403470540790234" + ) ), ( "slope", bigint_str!( - "48268701472940295594394094960749868325610234644833445333946260403470540790234" - ) + "48268701472940295594394094960749868325610234644833445333946260403470540790234" + ) ), ("SECP_P", SECP_P_V2.clone()) ] @@ -621,19 +649,63 @@ mod tests { ( "value", bigint_str!( - "40442433062102151071094722250325492738932110061897694430475034100717288403728" - ) + "40442433062102151071094722250325492738932110061897694430475034100717288403728" + ) ), ( "slope", bigint_str!( - "40442433062102151071094722250325492738932110061897694430475034100717288403728" - ) + "40442433062102151071094722250325492738932110061897694430475034100717288403728" + ) ) ] ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_compute_doubling_slope_with_custom_consts_ok() { + let hint_code = hint_code::EC_DOUBLE_SLOPE_EXTERNAL_CONSTS; + let mut vm = vm_with_range_check!(); + vm.segments = segments![ + ((1, 0), 614323u64), + ((1, 1), 5456867u64), + ((1, 2), 101208u64), + ((1, 3), 773712524u64), + ((1, 4), 77371252u64), + ((1, 5), 5298795u64) + ]; + + //Initialize fp + vm.run_context.fp = 1; + + let ids_data = ids_data!["point"]; + let mut exec_scopes = ExecutionScopes::new(); + + exec_scopes.insert_value("SECP_P", SECP256R1_P.clone()); + exec_scopes.insert_value("ALPHA", SECP256R1_ALPHA.clone()); + + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code, &mut exec_scopes), Ok(())); + check_scope!( + &exec_scopes, + [ + ( + "value", + bigint_str!( + "99065496658741969395000079476826955370154683653966841736214499259699304892273" + ) + ), + ( + "slope", + bigint_str!( + "99065496658741969395000079476826955370154683653966841736214499259699304892273" + ) + ), + ], + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_compute_slope_ok() { @@ -672,14 +744,14 @@ mod tests { ( "value", bigint_str!( - "41419765295989780131385135514529906223027172305400087935755859001910844026631" - ) + "41419765295989780131385135514529906223027172305400087935755859001910844026631" + ) ), ( "slope", bigint_str!( - "41419765295989780131385135514529906223027172305400087935755859001910844026631" - ) + "41419765295989780131385135514529906223027172305400087935755859001910844026631" + ) ) ] ); @@ -727,14 +799,14 @@ mod tests { ( "value", bigint_str!( - "39376930140709393693483102164172662915882483986415749881375763965703119677959" - ) + "39376930140709393693483102164172662915882483986415749881375763965703119677959" + ) ), ( "slope", bigint_str!( - "39376930140709393693483102164172662915882483986415749881375763965703119677959" - ) + "39376930140709393693483102164172662915882483986415749881375763965703119677959" + ) ) ] ); @@ -778,14 +850,14 @@ mod tests { ( "value", bigint_str!( - "41419765295989780131385135514529906223027172305400087935755859001910844026631" - ) + "41419765295989780131385135514529906223027172305400087935755859001910844026631" + ) ), ( "slope", bigint_str!( - "41419765295989780131385135514529906223027172305400087935755859001910844026631" - ) + "41419765295989780131385135514529906223027172305400087935755859001910844026631" + ) ) ] ); @@ -897,14 +969,14 @@ mod tests { ( "value", bigint_str!( - "7948634220683381957329555864604318996476649323793038777651086572350147290350" - ) + "7948634220683381957329555864604318996476649323793038777651086572350147290350" + ) ), ( "new_y", bigint_str!( - "7948634220683381957329555864604318996476649323793038777651086572350147290350" - ) + "7948634220683381957329555864604318996476649323793038777651086572350147290350" + ) ) ] ); @@ -954,14 +1026,14 @@ mod tests { ( "value", bigint_str!( - "8891838197222656627233627110766426698842623939023296165598688719819499152657" - ) + "8891838197222656627233627110766426698842623939023296165598688719819499152657" + ) ), ( "new_x", bigint_str!( - "8891838197222656627233627110766426698842623939023296165598688719819499152657" - ) + "8891838197222656627233627110766426698842623939023296165598688719819499152657" + ) ) ] ); @@ -1009,14 +1081,14 @@ mod tests { ( "value", bigint_str!( - "7948634220683381957329555864604318996476649323793038777651086572350147290350" - ) + "7948634220683381957329555864604318996476649323793038777651086572350147290350" + ) ), ( "new_y", bigint_str!( - "7948634220683381957329555864604318996476649323793038777651086572350147290350" - ) + "7948634220683381957329555864604318996476649323793038777651086572350147290350" + ) ) ] ); @@ -1181,8 +1253,8 @@ mod tests { assert_matches!(run_hint!(vm, ids_data, hint_code, &mut exec_scopes), Ok(())); //Check 'ALPHA' is defined in the vm scope assert_matches!( - exec_scopes.get::("ALPHA"), - Ok(x) if x == biguint_str!( + exec_scopes.get::("ALPHA"), + Ok(x) if x == bigint_str!( "115792089210356248762697446949407573530086143415290314195533631308867097853948" ) ); diff --git a/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs b/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs index 967e6da3e8..03607b8fd5 100644 --- a/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs +++ b/src/hint_processor/builtin_hint_processor/secp/secp_utils.rs @@ -65,7 +65,7 @@ lazy_static! { "115792089210356248762697446949407573529996955224135760342422259061068512044369" ).unwrap(); //SECP256R1_ALPHA = 0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC - pub(crate) static ref SECP256R1_ALPHA: BigUint = BigUint::from_str( + pub(crate) static ref SECP256R1_ALPHA: BigInt = BigInt::from_str( "115792089210356248762697446949407573530086143415290314195533631308867097853948" ).unwrap(); } diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index ef3903621b..03ce1f25b3 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -911,3 +911,17 @@ fn nondet_bigint3_v2() { let program_data = include_bytes!("../../cairo_programs/nondet_bigint3_v2.json"); run_program_simple(program_data.as_slice()); } + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_ec_double_slope() { + let program_data = include_bytes!("../../cairo_programs/ec_double_slope.json"); + run_program_simple_with_memory_holes(program_data.as_slice(), 0); +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_normalize_address() { + let program_data = include_bytes!("../../cairo_programs/normalize_address.json"); + run_program_simple_with_memory_holes(program_data.as_slice(), 110); +} diff --git a/src/types/program.rs b/src/types/program.rs index 3683bb3cfe..3d29aeaa60 100644 --- a/src/types/program.rs +++ b/src/types/program.rs @@ -124,6 +124,13 @@ impl Program { pub fn get_identifier(&self, id: &str) -> Option<&Identifier> { self.shared_program_data.identifiers.get(id) } + + pub fn iter_identifiers(&self) -> impl Iterator { + self.shared_program_data + .identifiers + .iter() + .map(|(cairo_type, identifier)| (cairo_type.as_str(), identifier)) + } } impl Default for Program { @@ -429,6 +436,70 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn iter_identifiers() { + let reference_manager = ReferenceManager { + references: Vec::new(), + }; + + let builtins: Vec = Vec::new(); + + let data: Vec = vec![ + mayberelocatable!(5189976364521848832), + mayberelocatable!(1000), + mayberelocatable!(5189976364521848832), + mayberelocatable!(2000), + mayberelocatable!(5201798304953696256), + mayberelocatable!(2345108766317314046), + ]; + + let mut identifiers: HashMap = HashMap::new(); + + identifiers.insert( + String::from("__main__.main"), + Identifier { + pc: Some(0), + type_: Some(String::from("function")), + value: None, + full_name: None, + members: None, + cairo_type: None, + }, + ); + + identifiers.insert( + String::from("__main__.main.SIZEOF_LOCALS"), + Identifier { + pc: None, + type_: Some(String::from("const")), + value: Some(Felt252::zero()), + full_name: None, + members: None, + cairo_type: None, + }, + ); + + let program = Program::new( + builtins, + data, + None, + HashMap::new(), + reference_manager, + identifiers.clone(), + Vec::new(), + None, + ) + .unwrap(); + + let collected_identifiers: HashMap<_, _> = program + .iter_identifiers() + .map(|(cairo_type, identifier)| (cairo_type.to_string(), identifier.clone())) + .collect(); + + assert_eq!(collected_identifiers, identifiers); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn new_program_with_invalid_identifiers() { diff --git a/src/utils.rs b/src/utils.rs index 0501c463bf..28a06d431b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -424,7 +424,7 @@ pub mod test_utils { pub(crate) use add_segments; macro_rules! check_scope { - ( $exec_scope: expr, [ $( ($name: expr, $val: expr)),* ] ) => { + ( $exec_scope: expr, [ $( ($name: expr, $val: expr)),*$(,)? ] $(,)? ) => { $( check_scope_value($exec_scope, $name, $val); )*