diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ce44edd94..44e4b70a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ #### Upcoming Changes +* Add missing hint on cairo_secp lib [#1089](https://github.com/lambdaclass/cairo-rs/pull/1089): + `BuiltinHintProcessor` now supports the following hint: + + ```python + + from starkware.cairo.common.cairo_secp.secp_utils import pack + + slope = pack(ids.slope, PRIME) + x0 = pack(ids.point0.x, PRIME) + x1 = pack(ids.point1.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + + value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P + ``` + * Add missing hint on vrf.json whitelist [#1055](https://github.com/lambdaclass/cairo-rs/pull/1055): `BuiltinHintProcessor` now supports the following hint: diff --git a/cairo_programs/secp256r1_fast_ec_add.cairo b/cairo_programs/secp256r1_fast_ec_add.cairo new file mode 100644 index 0000000000..3a4f09584e --- /dev/null +++ b/cairo_programs/secp256r1_fast_ec_add.cairo @@ -0,0 +1,225 @@ +%builtins range_check + +// Source: https://github.com/myBraavos/efficient-secp256r1/blob/main/src/secp256r1/ec.cairo#L188 + +from starkware.cairo.common.cairo_secp.bigint import BigInt3, nondet_bigint3, BASE, bigint_mul, UnreducedBigInt3 +from starkware.cairo.common.cairo_secp.ec import EcPoint + +// N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 +const N0 = 0x179e84f3b9cac2fc632551; +const N1 = 0x3ffffffffffef39beab69c; +const N2 = 0xffffffff00000000fffff; + +// 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; + +// SECP_REM = 2**224 - 2**192 - 2**96 + 1 +const SECP_REM0 = 1; +const SECP_REM1 = -2**10; +const SECP_REM2 = 0xffffffff00000; + +func assert_165_bit{range_check_ptr}(value) { + const UPPER_BOUND = 2 ** 165; + const SHIFT = 2 ** 128; + const HIGH_BOUND = SHIFT - 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 + HIGH_BOUND; + + assert value = high * SHIFT + low; + + let range_check_ptr = range_check_ptr + 3; + return (); +} + +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, + ), + ); +} + +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, + ), + ); +} + +func verify_zero{range_check_ptr}(val: UnreducedBigInt3) { + alloc_locals; + local q; + %{ 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_165_bit(q + 2**164); + // q in [-2**164, 2**164) + + tempvar r1 = (val.d0 + q * SECP_REM0) / BASE; + assert_165_bit(r1 + 2**164); + // r1 in [-2**164, 2**164) also meaning + // numerator divides BASE which is the case when val divides secp256r1 + // so r1 * BASE = val.d0 + q*SECP_REM0 in the integers + + tempvar r2 = (val.d1 + q * SECP_REM1 + r1) / BASE; + assert_165_bit(r2 + 2**164); + // r2 in [-2**164, 2**164) following the same reasoning + // so r2 * BASE = val.d1 + q*SECP_REM1 + r1 in the integers + // so r2 * BASE ** 2 = val.d1 * BASE + q*SECP_REM1 * BASE + r1 * BASE + + assert val.d2 + q * SECP_REM2 = q * (BASE / 4) - r2; + // both lhs and rhs are in (-2**250, 2**250) so assertion valid in the integers + // multiply both sides by BASE**2 + // val.d2*BASE**2 + q * SECP_REM2*BASE**2 + // = q * (2**256) - val.d1 * BASE + q*SECP_REM1 * BASE + val.d0 + q*SECP_REM0 + // collect val on one side and all the rest on the other => + // val = q*(2**256 - SECP_REM) = q * secp256r1 = 0 mod secp256r1 + + return (); +} + +func compute_slope{range_check_ptr}(point0: EcPoint, point1: EcPoint) -> (slope: BigInt3) { + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + from starkware.python.math_utils import line_slope + + # Compute the slope. + x0 = pack(ids.point0.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + x1 = pack(ids.point1.x, PRIME) + y1 = pack(ids.point1.y, PRIME) + value = slope = line_slope(point1=(x0, y0), point2=(x1, y1), p=SECP_P) + %} + let (slope) = nondet_bigint3(); + + let x_diff = BigInt3( + d0=point0.x.d0 - point1.x.d0, d1=point0.x.d1 - point1.x.d1, d2=point0.x.d2 - point1.x.d2 + ); + let (x_diff_slope: UnreducedBigInt3) = unreduced_mul(x_diff, slope); + verify_zero( + UnreducedBigInt3( + d0=x_diff_slope.d0 - point0.y.d0 + point1.y.d0, + d1=x_diff_slope.d1 - point0.y.d1 + point1.y.d1, + d2=x_diff_slope.d2 - point0.y.d2 + point1.y.d2, + ), + ); + + return (slope=slope); +} + +func fast_ec_add{range_check_ptr}(point0: EcPoint, point1: EcPoint) -> (res: EcPoint) { + // Check whether point0 is the zero point. + if (point0.x.d0 == 0) { + if (point0.x.d1 == 0) { + if (point0.x.d2 == 0) { + return (res=point1); + } + } + } + + // Check whether point1 is the zero point. + if (point1.x.d0 == 0) { + if (point1.x.d1 == 0) { + if (point1.x.d2 == 0) { + return (res=point0); + } + } + } + + let (slope: BigInt3) = compute_slope(point0, point1); + let (slope_sqr: UnreducedBigInt3) = unreduced_sqr(slope); + %{ from starkware.cairo.common.cairo_secp.secp256r1_utils import SECP256R1_P as SECP_P %} + // Hint #21 + %{ + from starkware.cairo.common.cairo_secp.secp_utils import pack + + slope = pack(ids.slope, PRIME) + x0 = pack(ids.point0.x, PRIME) + x1 = pack(ids.point1.x, PRIME) + y0 = pack(ids.point0.y, PRIME) + + value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P + %} + let (new_x: BigInt3) = nondet_bigint3(); + + %{ value = new_y = (slope * (x0 - new_x) - y0) % SECP_P %} + let (new_y: BigInt3) = nondet_bigint3(); + verify_zero( + UnreducedBigInt3( + d0=slope_sqr.d0 - new_x.d0 - point0.x.d0 - point1.x.d0, + d1=slope_sqr.d1 - new_x.d1 - point0.x.d1 - point1.x.d1, + d2=slope_sqr.d2 - new_x.d2 - point0.x.d2 - point1.x.d2, + ), + ); + + let (x_diff_slope: UnreducedBigInt3) = unreduced_mul( + BigInt3(d0=point0.x.d0 - new_x.d0, d1=point0.x.d1 - new_x.d1, d2=point0.x.d2 - new_x.d2), + slope, + ); + verify_zero( + UnreducedBigInt3( + d0=x_diff_slope.d0 - point0.y.d0 - new_y.d0, + d1=x_diff_slope.d1 - point0.y.d1 - new_y.d1, + d2=x_diff_slope.d2 - point0.y.d2 - new_y.d2, + ), + ); + + return (res=EcPoint(new_x, new_y)); +} + +func main{range_check_ptr}(){ + let x = BigInt3(1, 5, 10); + let y = BigInt3(2, 4, 20); + + let point_a = EcPoint(x, y); + let point_e = EcPoint( + BigInt3(55117564152931927789817182, 33048130247267262167865975, 14533608608654363688616034), + BigInt3(54056253314096377704781816, 68158355584365770862343034, 3052322168655618600739346), + ); + + // fast_ec_add + let (point_f) = fast_ec_add(point_a, point_e); + assert point_f = EcPoint( + BigInt3(49699015624329293412442365, 46510866771824701261167999, 1989434117861440887085793), + BigInt3(214124551187530669800637, 1052132420873960207582277, 4516480956028272815500807), + ); + + 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 d280408672..0943da7610 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 @@ -9,7 +9,7 @@ use super::{ ec_utils::{ 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, + ec_negate_import_secp_p, square_slope_minus_xs, }, secp_utils::{ALPHA, ALPHA_V2, SECP_P, SECP_P_V2}, }, @@ -511,6 +511,9 @@ impl HintProcessor for BuiltinHintProcessor { "point1", &SECP_P, ), + hint_code::SQUARE_SLOPE_X_MOD_P => { + square_slope_minus_xs(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking) + } hint_code::COMPUTE_SLOPE_V2 => compute_slope_and_assing_secp_p( 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 f2bc90fec4..026119b789 100644 --- a/src/hint_processor/builtin_hint_processor/hint_code.rs +++ b/src/hint_processor/builtin_hint_processor/hint_code.rs @@ -1373,6 +1373,16 @@ ids.b_inverse_mod_p.high = b_inverse_mod_p_split[1]"#; pub const EC_RECOVER_PRODUCT_DIV_M: &str = "value = k = product // m"; +pub const SQUARE_SLOPE_X_MOD_P: &str = + "from starkware.cairo.common.cairo_secp.secp_utils import pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.point0.x, PRIME) +x1 = pack(ids.point1.x, PRIME) +y0 = pack(ids.point0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P"; + pub const SPLIT_XX: &str = "PRIME = 2**255 - 19 II = pow(2, (PRIME - 1) // 4, PRIME) 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 006b72abac..2295a0d685 100644 --- a/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs +++ b/src/hint_processor/builtin_hint_processor/secp/ec_utils.rs @@ -226,6 +226,46 @@ pub fn compute_slope( Ok(()) } +/* +Implements hint: +%{from starkware.cairo.common.cairo_secp.secp_utils import pack + +slope = pack(ids.slope, PRIME) +x0 = pack(ids.point0.x, PRIME) +x1 = pack(ids.point1.x, PRIME) +y0 = pack(ids.point0.y, PRIME) + +value = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P +%} +*/ +pub fn square_slope_minus_xs( + vm: &mut VirtualMachine, + exec_scopes: &mut ExecutionScopes, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result<(), HintError> { + let secp_p = exec_scopes.get::("SECP_P")?; + let point0 = EcPoint::from_var_name("point0", vm, ids_data, ap_tracking)?; + let point1 = EcPoint::from_var_name("point1", vm, ids_data, ap_tracking)?; + + let slope = BigInt3::from_var_name("slope", vm, ids_data, ap_tracking)?; + let slope = slope.pack86(); + let x0 = point0.x.pack86(); + let x1 = point1.x.pack86(); + let y0 = point0.y.pack86(); + + let value = (slope.pow(2) - &x0 - &x1).mod_floor(&secp_p); + + exec_scopes.insert_value("slope", slope); + exec_scopes.insert_value("x0", x0); + exec_scopes.insert_value("x1", x1); + exec_scopes.insert_value("y0", y0); + exec_scopes.insert_value("value", value.clone()); + exec_scopes.insert_value("new_x", value); + + Ok(()) +} + /* Implements hint: %{ @@ -1262,4 +1302,62 @@ mod tests { ) ); } + + #[test] + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] + fn run_square_slope_minus_xs_ok() { + let hint_code = "from starkware.cairo.common.cairo_secp.secp_utils import pack\n\nslope = pack(ids.slope, PRIME)\nx0 = pack(ids.point0.x, PRIME)\nx1 = pack(ids.point1.x, PRIME)\ny0 = pack(ids.point0.y, PRIME)\n\nvalue = new_x = (pow(slope, 2, SECP_P) - x0 - x1) % SECP_P"; + let mut vm = vm_with_range_check!(); + + //Insert ids.point0, ids.point1.x and ids.slope into memory + vm.segments = segments![ + //ids.point0 + ((1, 0), 89712), + ((1, 1), 56), + ((1, 2), 1233409), + ((1, 3), 980126), + ((1, 4), 10), + ((1, 5), 8793), + //ids.point0.x + ((1, 6), 1235216451), + ((1, 7), 5967), + ((1, 8), 2171381), + //ids.slope + ((1, 9), 67470097831679799377177424_i128), + ((1, 10), 43370026683122492246392730_i128), + ((1, 11), 16032182557092050689870202_i128) + ]; + + //Initialize run_context + run_context!(vm, 0, 20, 15); + + let ids_data = HashMap::from([ + ("point0".to_string(), HintReference::new_simple(-15)), + ("point1".to_string(), HintReference::new_simple(-9)), + ("slope".to_string(), HintReference::new_simple(-6)), + ]); + let mut exec_scopes = ExecutionScopes::new(); + exec_scopes.insert_value("SECP_P", SECP_P.clone()); + + //Execute the hint + assert_matches!(run_hint!(vm, ids_data, hint_code, &mut exec_scopes), Ok(())); + + check_scope!( + &exec_scopes, + [ + ( + "value", + bigint_str!( + "8891838197222656627233627110766426698842623939023296165598688719819499152657" + ) + ), + ( + "new_x", + bigint_str!( + "8891838197222656627233627110766426698842623939023296165598688719819499152657" + ) + ) + ] + ); + } } diff --git a/src/tests/cairo_run_test.rs b/src/tests/cairo_run_test.rs index cdc227039a..4ef39906fa 100644 --- a/src/tests/cairo_run_test.rs +++ b/src/tests/cairo_run_test.rs @@ -912,6 +912,13 @@ fn ec_double_assign_new_x_v3() { run_program_simple(program_data.as_slice()); } +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn secp256r1_fast_ec_add() { + let program_data = include_bytes!("../../cairo_programs/secp256r1_fast_ec_add.json"); + run_program_simple(program_data.as_slice()); +} + #[test] #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] fn split_xx_hint() {