diff --git a/CHANGELOG.md b/CHANGELOG.md index e234880dc2..59cfd6cff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -150,6 +150,19 @@ ``` +* Add missing hint on vrf.json lib [#1054](https://github.com/lambdaclass/cairo-rs/pull/1054): + + `BuiltinHintProcessor` now supports the following hint: + + ```python + from starkware.cairo.common.cairo_secp.secp_utils import pack + SECP_P = 2**255-19 + + y = pack(ids.point.y, PRIME) % SECP_P + # The modulo operation in python always returns a nonnegative number. + value = (-y) % SECP_P + ``` + * Implement hint on ec_recover.json whitelist [#1032](https://github.com/lambdaclass/cairo-rs/pull/1032): `BuiltinHintProcessor` now supports the following hint: diff --git a/cairo_programs/ec_negate.cairo b/cairo_programs/ec_negate.cairo new file mode 100644 index 0000000000..cccda1ba3d --- /dev/null +++ b/cairo_programs/ec_negate.cairo @@ -0,0 +1,115 @@ +%builtins range_check + +// Source: https://github.com/Astraly-Labs/Starknet-VRF/blob/33175b179627fdf1f12e32b197a368c1fefcd34c/lib/ed25519.cairo +from starkware.cairo.common.cairo_secp.bigint import BigInt3, UnreducedBigInt3, nondet_bigint3 +from starkware.cairo.common.cairo_secp.ec import EcPoint + +const BASE = 2 ** 86; +const SECP_REM = 19; + +func verify_zero{range_check_ptr}(val: UnreducedBigInt3) { + let q = [ap]; + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + SECP_P = 2**255-19 + to_assert = pack(ids.val, PRIME) + 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 + %} + let q_biased = [ap + 1]; + q_biased = q + 2 ** 127, ap++; + [range_check_ptr] = q_biased, ap++; + // This implies that q is in the range [-2**127, 2**127). + + tempvar r1 = (val.d0 + q * SECP_REM) / BASE; + assert [range_check_ptr + 1] = r1 + 2 ** 127; + // This implies that r1 is in the range [-2**127, 2**127). + // Therefore, r1 * BASE is in the range [-2**213, 2**213). + // By the soundness assumption, val.d0 is in the range (-2**250, 2**250). + // This implies that r1 * BASE = val.d0 + q * SECP_REM (as integers). + + tempvar r2 = (val.d1 + r1) / BASE; + assert [range_check_ptr + 2] = r2 + 2 ** 127; + // Similarly, this implies that r2 * BASE = val.d1 + r1 (as integers). + // Therefore, r2 * BASE**2 = val.d1 * BASE + r1 * BASE. + + assert val.d2 = q * (BASE / 8) - r2; + // Similarly, this implies that q * BASE / 4 = val.d2 + r2 (as integers). + // Therefore, + // q * BASE**3 / 4 = val.d2 * BASE**2 + r2 * BASE ** 2 = + // val.d2 * BASE**2 + val.d1 * BASE + r1 * BASE = + // val.d2 * BASE**2 + val.d1 * BASE + val.d0 + q * SECP_REM = + // val + q * SECP_REM. + // Hence, val = q * (BASE**3 / 4 - SECP_REM) = q * (2**256 - SECP_REM) = q * secp256k1_prime. + + let range_check_ptr = range_check_ptr + 3; + return (); +} + +// Computes the negation of a point on the elliptic curve, which is a point with the same x value +// and the negation of the y value. If the point is the zero point, returns the zero point. +// +// Arguments: +// point - The point to operate on. +// +// Returns: +// point - The negation of the given point. +func ec_negate{range_check_ptr}(point: EcPoint) -> (point: EcPoint) { + alloc_locals; + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + SECP_P = 2**255-19 + + y = pack(ids.point.y, PRIME) % SECP_P + # The modulo operation in python always returns a nonnegative number. + value = (-y) % SECP_P + %} + let (minus_y) = nondet_bigint3(); + + // This check fails. cairo-lang's uses a different modulus, and the one used + // by this library uses a hint that's not implemented + // verify_zero( + // UnreducedBigInt3( + // d0=minus_y.d0 + point.y.d0, d1=minus_y.d1 + point.y.d1, d2=minus_y.d2 + point.y.d2 + // ), + // ); + + return (point=EcPoint(x=point.x, y=minus_y)); +} + +func test_ec_negate{range_check_ptr}() { + let p = EcPoint(BigInt3(1, 2, 3), BigInt3(1, 2, 3)); + + let (minus_p) = ec_negate(p); + + assert minus_p.x.d0 = 1; + assert minus_p.x.d1 = 2; + assert minus_p.x.d2 = 3; + + assert minus_p.y.d0 = 77371252455336267181195244; + assert minus_p.y.d1 = 77371252455336267181195261; + assert minus_p.y.d2 = 9671406556917033397649404; + + let p = EcPoint( + BigInt3(12424, 53151, 363737), + BigInt3(77371252455336267181195244, 77371252455336267181195261, 9671406556917033397649404), + ); + + let (minus_p) = ec_negate(p); + + assert minus_p.x.d0 = 12424; + assert minus_p.x.d1 = 53151; + assert minus_p.x.d2 = 363737; + + assert minus_p.y.d0 = 1; + assert minus_p.y.d1 = 2; + assert minus_p.y.d2 = 3; + + return (); +} + +func main{range_check_ptr}() { + test_ec_negate(); + 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 3aae66f875..1265ae020e 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 @@ -4,12 +4,15 @@ use super::{ ec_recover_sub_a_b, }, field_arithmetic::uint384_div, - secp::secp_utils::{SECP_P, SECP_P_V2}, - vrf::{ - fq::{inv_mod_p_uint256, uint512_unsigned_div_rem}, - inv_mod_p_uint512::inv_mod_p_uint512, + secp::ec_utils::{ec_negate_embedded_secp_p, ec_negate_import_secp_p}, + secp::{ + ec_utils::{compute_slope_and_assing_secp_p, ec_double_assign_new_y}, + secp_utils::{SECP_P, SECP_P_V2}, }, + vrf::fq::inv_mod_p_uint256, + vrf::{fq::uint512_unsigned_div_rem, inv_mod_p_uint512::inv_mod_p_uint512}, }; +use crate::hint_processor::builtin_hint_processor::secp::ec_utils::ec_double_assign_new_x; use crate::{ hint_processor::{ builtin_hint_processor::{ @@ -45,8 +48,7 @@ use crate::{ secp::{ bigint_utils::{bigint_to_uint256, hi_max_bitlen, nondet_bigint3}, ec_utils::{ - compute_doubling_slope, compute_slope, compute_slope_and_assing_secp_p, di_bit, - ec_double_assign_new_x, ec_double_assign_new_y, ec_mul_inner, ec_negate, + compute_doubling_slope, compute_slope, di_bit, ec_mul_inner, fast_ec_add_assign_new_x, fast_ec_add_assign_new_y, import_secp256r1_p, quad_bit, }, @@ -438,9 +440,18 @@ impl HintProcessor for BuiltinHintProcessor { &hint_data.ap_tracking, constants, ), - hint_code::EC_NEGATE => { - ec_negate(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) - } + hint_code::EC_NEGATE => ec_negate_import_secp_p( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + ), + hint_code::EC_NEGATE_EMBEDDED_SECP => ec_negate_embedded_secp_p( + vm, + exec_scopes, + &hint_data.ids_data, + &hint_data.ap_tracking, + ), hint_code::EC_DOUBLE_SCOPE => compute_doubling_slope( vm, exec_scopes, diff --git a/src/hint_processor/builtin_hint_processor/hint_code.rs b/src/hint_processor/builtin_hint_processor/hint_code.rs index 55e62991e3..5fec60e473 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -605,6 +605,13 @@ 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_NEGATE_EMBEDDED_SECP: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import pack +SECP_P = 2**255-19 + +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: &str = r#"from starkware.cairo.common.cairo_secp.secp_utils import SECP_P, pack from starkware.python.math_utils import ec_double_slope 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 6f363dda22..bb1aae8307 100644 --- a/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs +++ b/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs @@ -43,6 +43,26 @@ impl EcPoint<'_> { } } +/* +Implements main logic for `EC_NEGATE` and `EC_NEGATE_EMBEDDED_SECP` hints +*/ +pub fn ec_negate( + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, + secp_p: BigInt, +) -> Result<(), HintError> { + //ids.point + let point_y = (get_relocatable_from_var_name("point", vm, ids_data, ap_tracking)? + 3i32)?; + let y_bigint3 = BigInt3::from_base_addr(point_y, "point.y", vm)?; + let y = y_bigint3.pack86(); + let value = (-y).mod_floor(&secp_p); + exec_scopes.insert_value("value", value); + exec_scopes.insert_value("SECP_P", secp_p); + Ok(()) +} + /* Implements hint: %{ @@ -53,20 +73,34 @@ Implements hint: value = (-y) % SECP_P %} */ -pub fn ec_negate( +pub fn ec_negate_import_secp_p( vm: &mut VirtualMachine, exec_scopes: &mut ExecutionScopes, ids_data: &HashMap, ap_tracking: &ApTracking, ) -> Result<(), HintError> { - exec_scopes.insert_value("SECP_P", SECP_P.clone()); - //ids.point - let point_y = (get_relocatable_from_var_name("point", vm, ids_data, ap_tracking)? + 3i32)?; - let y_bigint3 = BigInt3::from_base_addr(point_y, "point.y", vm)?; - let y = y_bigint3.pack86(); - let value = (-y).mod_floor(&SECP_P); - exec_scopes.insert_value("value", value); - Ok(()) + ec_negate(vm, exec_scopes, ids_data, ap_tracking, SECP_P.clone()) +} + +/* +Implements hint: +%{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + SECP_P = 2**255-19 + + y = pack(ids.point.y, PRIME) % SECP_P + # The modulo operation in python always returns a nonnegative number. + value = (-y) % SECP_P +%} +*/ +pub fn ec_negate_embedded_secp_p( + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let secp_p = (BigInt::one() << 255) - 19; + ec_negate(vm, exec_scopes, ids_data, ap_tracking, secp_p) } /* @@ -424,6 +458,32 @@ mod tests { ); } + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_ec_negate_embedded_secp_p_ok() { + let hint_code = hint_code::EC_NEGATE_EMBEDDED_SECP; + let mut vm = vm_with_range_check!(); + + let (y0, y1, y2) = (2645i32, 454i32, 206i32); + + let y = (BigInt::from(y2) << (86 * 2)) + (BigInt::from(y1) << 86) + y0; + let minus_y = (BigInt::one() << 255) - 19 - y; + + vm.segments = segments![((1, 3), y0), ((1, 4), y1), ((1, 5), y2)]; + //Initialize fp + vm.run_context.fp = 1; + //Create hint_data + let ids_data = ids_data!["point"]; + let mut exec_scopes = ExecutionScopes::new(); + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code, &mut exec_scopes), Ok(())); + //Check 'value' is defined in the vm scope + assert_matches!( + exec_scopes.get::("value"), + Ok(x) if x == minus_y + ); + } + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn run_compute_doubling_slope_ok() { diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index 992a2c4c99..f0a4273c9a 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -862,3 +862,10 @@ fn cairo_run_compute_slope_v2_test() { let program_data = include_bytes!("../../cairo_programs/compute_slope_v2.json"); run_program_simple(program_data.as_slice()); } + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn cairo_run_ec_negate() { + let program_data = include_bytes!("../../cairo_programs/ec_negate.json"); + run_program_simple_with_memory_holes(program_data.as_slice(), 0); +}