cairo_vm/hint_processor/builtin_hint_processor/
ec_utils.rs

1use crate::stdlib::{borrow::Cow, boxed::Box, collections::HashMap, prelude::*};
2use crate::utils::CAIRO_PRIME;
3use crate::Felt252;
4use crate::{
5    hint_processor::{
6        builtin_hint_processor::hint_utils::{
7            get_integer_from_var_name, get_relocatable_from_var_name,
8        },
9        hint_processor_definition::HintReference,
10    },
11    serde::deserialize_program::ApTracking,
12    vm::{errors::hint_errors::HintError, vm_core::VirtualMachine},
13};
14use lazy_static::lazy_static;
15use num_bigint::BigUint;
16use num_bigint::ToBigInt;
17use num_traits::{Num, One, Pow, ToPrimitive, Zero};
18use sha2::{Digest, Sha256};
19
20use super::hint_utils::get_ptr_from_var_name;
21
22#[derive(Debug, PartialEq)]
23struct EcPoint<'a> {
24    x: Cow<'a, Felt252>,
25    y: Cow<'a, Felt252>,
26}
27impl EcPoint<'_> {
28    fn from_var_name<'a>(
29        name: &'a str,
30        vm: &'a VirtualMachine,
31        ids_data: &'a HashMap<String, HintReference>,
32        ap_tracking: &'a ApTracking,
33    ) -> Result<EcPoint<'a>, HintError> {
34        // Get first addr of EcPoint struct
35        let point_addr = get_relocatable_from_var_name(name, vm, ids_data, ap_tracking)?;
36        Ok(EcPoint {
37            x: vm.get_integer(point_addr).map_err(|_| {
38                HintError::IdentifierHasNoMember(Box::new((name.to_string(), "x".to_string())))
39            })?,
40            y: vm.get_integer((point_addr + 1)?).map_err(|_| {
41                HintError::IdentifierHasNoMember(Box::new((name.to_string(), "y".to_string())))
42            })?,
43        })
44    }
45}
46
47// Implements hint:
48// from starkware.crypto.signature.signature import ALPHA, BETA, FIELD_PRIME
49// from starkware.python.math_utils import random_ec_point
50// from starkware.python.utils import to_bytes
51
52// # Define a seed for random_ec_point that's dependent on all the input, so that:
53// #   (1) The added point s is deterministic.
54// #   (2) It's hard to choose inputs for which the builtin will fail.
55// seed = b"".join(map(to_bytes, [ids.p.x, ids.p.y, ids.m, ids.q.x, ids.q.y]))
56// ids.s.x, ids.s.y = random_ec_point(FIELD_PRIME, ALPHA, BETA, seed)
57
58pub fn random_ec_point_hint(
59    vm: &mut VirtualMachine,
60    ids_data: &HashMap<String, HintReference>,
61    ap_tracking: &ApTracking,
62) -> Result<(), HintError> {
63    let p = EcPoint::from_var_name("p", vm, ids_data, ap_tracking)?;
64    let q = EcPoint::from_var_name("q", vm, ids_data, ap_tracking)?;
65    let m = Cow::Owned(get_integer_from_var_name("m", vm, ids_data, ap_tracking)?);
66    let bytes: Vec<u8> = [p.x, p.y, m, q.x, q.y]
67        .iter()
68        .flat_map(|x| x.to_bytes_be())
69        .collect();
70    let (x, y) = random_ec_point_seeded(bytes)?;
71    let s_addr = get_relocatable_from_var_name("s", vm, ids_data, ap_tracking)?;
72    vm.insert_value(s_addr, x)?;
73    vm.insert_value((s_addr + 1)?, y)?;
74    Ok(())
75}
76
77// Implements hint:
78// from starkware.crypto.signature.signature import ALPHA, BETA, FIELD_PRIME
79//     from starkware.python.math_utils import random_ec_point
80//     from starkware.python.utils import to_bytes
81
82//     n_elms = ids.len
83//     assert isinstance(n_elms, int) and n_elms >= 0, \
84//         f'Invalid value for len. Got: {n_elms}.'
85//     if '__chained_ec_op_max_len' in globals():
86//         assert n_elms <= __chained_ec_op_max_len, \
87//             f'chained_ec_op() can only be used with len<={__chained_ec_op_max_len}. ' \
88//             f'Got: n_elms={n_elms}.'
89
90//     # Define a seed for random_ec_point that's dependent on all the input, so that:
91//     #   (1) The added point s is deterministic.
92//     #   (2) It's hard to choose inputs for which the builtin will fail.
93//     seed = b"".join(
94//         map(
95//             to_bytes,
96//             [
97//                 ids.p.x,
98//                 ids.p.y,
99//                 *memory.get_range(ids.m, n_elms),
100//                 *memory.get_range(ids.q.address_, 2 * n_elms),
101//             ],
102//         )
103//     )
104//     ids.s.x, ids.s.y = random_ec_point(FIELD_PRIME, ALPHA, BETA, seed)"
105pub fn chained_ec_op_random_ec_point_hint(
106    vm: &mut VirtualMachine,
107    ids_data: &HashMap<String, HintReference>,
108    ap_tracking: &ApTracking,
109) -> Result<(), HintError> {
110    let n_elms = get_integer_from_var_name("len", vm, ids_data, ap_tracking)?;
111    if n_elms.is_zero() || n_elms.to_usize().is_none() {
112        return Err(HintError::InvalidLenValue(Box::new(n_elms)));
113    }
114    let n_elms = n_elms.to_usize().unwrap();
115    let p = EcPoint::from_var_name("p", vm, ids_data, ap_tracking)?;
116    let m = get_ptr_from_var_name("m", vm, ids_data, ap_tracking)?;
117    let q = get_ptr_from_var_name("q", vm, ids_data, ap_tracking)?;
118    let m_range = vm.get_integer_range(m, n_elms)?;
119    let q_range = vm.get_integer_range(q, n_elms * 2)?;
120    let bytes: Vec<u8> = [p.x, p.y]
121        .iter()
122        .chain(m_range.iter())
123        .chain(q_range.iter())
124        .flat_map(|x| x.to_bytes_be())
125        .collect();
126    let (x, y) = random_ec_point_seeded(bytes)?;
127    let s_addr = get_relocatable_from_var_name("s", vm, ids_data, ap_tracking)?;
128    vm.insert_value(s_addr, x)?;
129    vm.insert_value((s_addr + 1)?, y)?;
130    Ok(())
131}
132
133// Implements hint:
134// from starkware.crypto.signature.signature import ALPHA, BETA, FIELD_PRIME
135// from starkware.python.math_utils import recover_y
136// ids.p.x = ids.x
137// # This raises an exception if `x` is not on the curve.
138// ids.p.y = recover_y(ids.x, ALPHA, BETA, FIELD_PRIME)
139pub fn recover_y_hint(
140    vm: &mut VirtualMachine,
141    ids_data: &HashMap<String, HintReference>,
142    ap_tracking: &ApTracking,
143) -> Result<(), HintError> {
144    let p_x = get_integer_from_var_name("x", vm, ids_data, ap_tracking)?;
145    let p_addr = get_relocatable_from_var_name("p", vm, ids_data, ap_tracking)?;
146    vm.insert_value(p_addr, p_x)?;
147    let p_y = Felt252::from(
148        &recover_y(&p_x.to_biguint())
149            .ok_or_else(|| HintError::RecoverYPointNotOnCurve(Box::new(p_x)))?,
150    );
151    vm.insert_value((p_addr + 1)?, p_y)?;
152    Ok(())
153}
154
155// Returns a random non-zero point on the elliptic curve
156//   y^2 = x^3 + alpha * x + beta (mod field_prime).
157// The point is created deterministically from the seed.
158fn random_ec_point_seeded(seed_bytes: Vec<u8>) -> Result<(Felt252, Felt252), HintError> {
159    // Hash initial seed
160    let mut hasher = Sha256::new();
161    hasher.update(seed_bytes);
162    let seed = hasher.finalize_reset().to_vec();
163    for i in 0..100 {
164        // Calculate x
165        let i_bytes = (i as u8).to_le_bytes();
166        let mut input = seed[1..].to_vec();
167        input.extend(i_bytes);
168        input.extend(vec![0; 10 - i_bytes.len()]);
169        hasher.update(input);
170        let x = BigUint::from_bytes_be(&hasher.finalize_reset());
171        // Calculate y
172        let y_coef = (-1).pow(seed[0] & 1);
173        let y = recover_y(&x);
174        if let Some(y) = y {
175            // Conversion from BigUint to BigInt doesnt fail
176            return Ok((
177                Felt252::from(&x),
178                Felt252::from(&(y.to_bigint().unwrap() * y_coef)),
179            ));
180        }
181    }
182    Err(HintError::RandomEcPointNotOnCurve)
183}
184const ALPHA: u32 = 1;
185lazy_static! {
186    static ref BETA: BigUint = BigUint::from_str_radix(
187        "3141592653589793238462643383279502884197169399375105820974944592307816406665",
188        10
189    )
190    .unwrap();
191    static ref FELT_MAX_HALVED: BigUint = Felt252::MAX.to_biguint() / 2_u32;
192}
193
194// Recovers the corresponding y coordinate on the elliptic curve
195//     y^2 = x^3 + alpha * x + beta (mod field_prime)
196//     of a given x coordinate.
197// Returns None if x is not the x coordinate of a point in the curve
198fn recover_y(x: &BigUint) -> Option<BigUint> {
199    let y_squared: BigUint = x.modpow(&BigUint::from(3_u32), &CAIRO_PRIME) + ALPHA * x + &*BETA;
200    if is_quad_residue(&y_squared) {
201        Some(Felt252::from(&y_squared).sqrt()?.to_biguint())
202    } else {
203        None
204    }
205}
206
207// Implementation adapted from sympy implementation
208// Conditions:
209// + prime is ommited as it will be CAIRO_PRIME
210// + a >= 0 < prime (other cases ommited)
211fn is_quad_residue(a: &BigUint) -> bool {
212    a.is_zero() || a.is_one() || a.modpow(&FELT_MAX_HALVED, &CAIRO_PRIME).is_one()
213}
214
215#[cfg(test)]
216mod tests {
217    use crate::any_box;
218    use crate::felt_str;
219    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
220    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::HintProcessorData;
221    use crate::hint_processor::hint_processor_definition::HintProcessorLogic;
222    use crate::relocatable;
223    use crate::types::relocatable::Relocatable;
224    use num_traits::Zero;
225
226    use crate::hint_processor::builtin_hint_processor::hint_code;
227    use crate::utils::test_utils::*;
228    use assert_matches::assert_matches;
229
230    use super::*;
231
232    #[cfg(target_arch = "wasm32")]
233    use wasm_bindgen_test::*;
234
235    #[test]
236    fn test_is_quad_residue_less_than_2() {
237        assert!(is_quad_residue(&BigUint::one()));
238        assert!(is_quad_residue(&BigUint::zero()));
239    }
240
241    #[test]
242    fn test_is_quad_residue_false() {
243        assert!(!is_quad_residue(
244            &BigUint::from_str_radix(
245                "205857351767627712295703269674687767888261140702556021834663354704341414042",
246                10
247            )
248            .unwrap()
249        ));
250    }
251
252    #[test]
253    fn test_is_quad_residue_true() {
254        assert!(is_quad_residue(
255            &BigUint::from_str_radix(
256                "99957092485221722822822221624080199277265330641980989815386842231144616633668",
257                10
258            )
259            .unwrap()
260        ));
261    }
262
263    #[test]
264    fn test_recover_y_valid() {
265        let x = BigUint::from_str_radix(
266            "2497468900767850684421727063357792717599762502387246235265616708902555305129",
267            10,
268        )
269        .unwrap();
270        let y = BigUint::from_str_radix(
271            "205857351767627712295703269674687767888261140702556021834663354704341414042",
272            10,
273        )
274        .unwrap();
275        assert_eq!(recover_y(&x), Some(y));
276    }
277
278    #[test]
279    fn test_recover_y_invalid() {
280        let x = BigUint::from_str_radix(
281            "205857351767627712295703269674687767888261140702556021834663354704341414042",
282            10,
283        )
284        .unwrap();
285        assert_eq!(recover_y(&x), None);
286    }
287
288    #[test]
289    fn get_random_ec_point_seeded() {
290        let seed: Vec<u8> = vec![
291            6, 164, 190, 174, 245, 169, 52, 37, 185, 115, 23, 156, 219, 160, 201, 212, 47, 48, 224,
292            26, 95, 30, 45, 183, 61, 160, 136, 75, 141, 103, 86, 252, 7, 37, 101, 236, 129, 188, 9,
293            255, 83, 251, 250, 217, 147, 36, 169, 42, 165, 179, 159, 181, 130, 103, 227, 149, 232,
294            171, 227, 98, 144, 235, 242, 79, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
295            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 6, 84, 253, 126, 103, 161, 35, 221, 19, 134,
296            128, 147, 179, 183, 119, 127, 31, 254, 245, 150, 194, 227, 36, 242, 92, 234, 249, 20,
297            102, 152, 72, 44, 4, 250, 210, 105, 203, 248, 96, 152, 14, 56, 118, 143, 233, 203, 107,
298            11, 154, 176, 62, 227, 254, 132, 207, 222, 46, 204, 206, 89, 124, 135, 79, 216,
299        ];
300        let x = felt_str!(
301            "2497468900767850684421727063357792717599762502387246235265616708902555305129"
302        );
303        let y = felt_str!(
304            "3412645436898503501401619513420382337734846074629040678138428701431530606439"
305        );
306        assert_eq!(random_ec_point_seeded(seed).unwrap(), (x, y));
307    }
308
309    #[test]
310    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
311    fn run_ec_op_random_ec_point_hint() {
312        let hint_code = hint_code::RANDOM_EC_POINT;
313        let mut vm = vm!();
314        //Initialize fp
315        vm.run_context.fp = 6;
316        //Create hint_data
317        let ids_data = non_continuous_ids_data![("p", -6), ("q", -3), ("m", -4), ("s", -1)];
318        /*  p.x = 3004956058830981475544150447242655232275382685012344776588097793621230049020
319           p.y = 3232266734070744637901977159303149980795588196503166389060831401046564401743
320           m = 34
321           q.x = 2864041794633455918387139831609347757720597354645583729611044800117714995244
322           q.y = 2252415379535459416893084165764951913426528160630388985542241241048300343256
323        */
324        add_segments!(vm, 2);
325        vm.insert_value(
326            (1, 0).into(),
327            felt_str!(
328                "3004956058830981475544150447242655232275382685012344776588097793621230049020"
329            ),
330        )
331        .unwrap();
332        vm.insert_value(
333            (1, 1).into(),
334            felt_str!(
335                "3232266734070744637901977159303149980795588196503166389060831401046564401743"
336            ),
337        )
338        .unwrap();
339        vm.insert_value((1, 2).into(), Felt252::from(34)).unwrap();
340        vm.insert_value(
341            (1, 3).into(),
342            felt_str!(
343                "2864041794633455918387139831609347757720597354645583729611044800117714995244"
344            ),
345        )
346        .unwrap();
347        vm.insert_value(
348            (1, 4).into(),
349            felt_str!(
350                "2252415379535459416893084165764951913426528160630388985542241241048300343256"
351            ),
352        )
353        .unwrap();
354        //Execute the hint
355        assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
356        // Check post-hint memory values
357        // s.x = 96578541406087262240552119423829615463800550101008760434566010168435227837635
358        // s.y = 3412645436898503501401619513420382337734846074629040678138428701431530606439
359        assert_eq!(
360            vm.get_integer((1, 5).into()).unwrap().as_ref(),
361            &felt_str!(
362                "96578541406087262240552119423829615463800550101008760434566010168435227837635"
363            )
364        );
365        assert_eq!(
366            vm.get_integer((1, 6).into()).unwrap().as_ref(),
367            &felt_str!(
368                "3412645436898503501401619513420382337734846074629040678138428701431530606439"
369            )
370        );
371    }
372
373    #[test]
374    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
375    fn run_chained_ec_op_random_ec_point_hint() {
376        let hint_code = hint_code::CHAINED_EC_OP_RANDOM_EC_POINT;
377        let mut vm = vm!();
378        //Initialize fp
379        vm.run_context.fp = 6;
380        //Create hint_data
381        let ids_data =
382            non_continuous_ids_data![("p", -6), ("m", -4), ("q", -3), ("len", -2), ("s", -1)];
383        /*
384            p.x = 3004956058830981475544150447242655232275382685012344776588097793621230049020
385            p.y = 3232266734070744637901977159303149980795588196503166389060831401046564401743
386            _m = 34
387            -q.x = 2864041794633455918387139831609347757720597354645583729611044800117714995244
388            -q.y = 2252415379535459416893084165764951913426528160630388985542241241048300343256
389            q = [q,q,q]
390            m = [m,m,m]
391            len = 3
392        */
393        add_segments!(vm, 4);
394        //p
395        vm.insert_value(
396            (1, 0).into(),
397            felt_str!(
398                "3004956058830981475544150447242655232275382685012344776588097793621230049020"
399            ),
400        )
401        .unwrap();
402        vm.insert_value(
403            (1, 1).into(),
404            felt_str!(
405                "3232266734070744637901977159303149980795588196503166389060831401046564401743"
406            ),
407        )
408        .unwrap();
409        //m
410        vm.insert_value((1, 2).into(), relocatable!(2, 0)).unwrap();
411        vm.insert_value((2, 0).into(), Felt252::from(34)).unwrap();
412        vm.insert_value((2, 1).into(), Felt252::from(34)).unwrap();
413        vm.insert_value((2, 2).into(), Felt252::from(34)).unwrap();
414        //q
415        vm.insert_value((1, 3).into(), relocatable!(3, 0)).unwrap();
416        vm.insert_value(
417            (3, 0).into(),
418            felt_str!(
419                "2864041794633455918387139831609347757720597354645583729611044800117714995244"
420            ),
421        )
422        .unwrap();
423        vm.insert_value(
424            (3, 1).into(),
425            felt_str!(
426                "2252415379535459416893084165764951913426528160630388985542241241048300343256"
427            ),
428        )
429        .unwrap();
430        vm.insert_value(
431            (3, 2).into(),
432            felt_str!(
433                "2864041794633455918387139831609347757720597354645583729611044800117714995244"
434            ),
435        )
436        .unwrap();
437        vm.insert_value(
438            (3, 3).into(),
439            felt_str!(
440                "2252415379535459416893084165764951913426528160630388985542241241048300343256"
441            ),
442        )
443        .unwrap();
444        vm.insert_value(
445            (3, 4).into(),
446            felt_str!(
447                "2864041794633455918387139831609347757720597354645583729611044800117714995244"
448            ),
449        )
450        .unwrap();
451        vm.insert_value(
452            (3, 5).into(),
453            felt_str!(
454                "2252415379535459416893084165764951913426528160630388985542241241048300343256"
455            ),
456        )
457        .unwrap();
458        //len
459        vm.insert_value((1, 4).into(), Felt252::from(3)).unwrap();
460        //Execute the hint
461        assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
462        // Check post-hint memory values
463        // s.x = 1354562415074475070179359167082942891834423311678180448592849484844152837347
464        // s.y = 907662328694455187848008017177970257426839229889571025406355869359245158736
465        assert_eq!(
466            vm.get_integer((1, 5).into()).unwrap().as_ref(),
467            &felt_str!(
468                "1354562415074475070179359167082942891834423311678180448592849484844152837347"
469            )
470        );
471        assert_eq!(
472            vm.get_integer((1, 6).into()).unwrap().as_ref(),
473            &felt_str!(
474                "907662328694455187848008017177970257426839229889571025406355869359245158736"
475            )
476        );
477    }
478
479    #[test]
480    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
481    fn run_recover_y_hint() {
482        let hint_code = hint_code::RECOVER_Y;
483        let mut vm = vm!();
484        //Initialize fp
485        vm.run_context.fp = 3;
486        //Create hint_data
487        let ids_data = non_continuous_ids_data![("x", -3), ("p", -1)];
488        // x = 3004956058830981475544150447242655232275382685012344776588097793621230049020
489        add_segments!(vm, 2);
490        vm.insert_value(
491            (1, 0).into(),
492            felt_str!(
493                "3004956058830981475544150447242655232275382685012344776588097793621230049020"
494            ),
495        )
496        .unwrap();
497        //Execute the hint
498        assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
499        // Check post-hint memory values
500        // p.x = 3004956058830981475544150447242655232275382685012344776588097793621230049020
501        // p.y = 386236054595386575795345623791920124827519018828430310912260655089307618738
502        assert_eq!(
503            vm.get_integer((1, 2).into()).unwrap().as_ref(),
504            &felt_str!(
505                "3004956058830981475544150447242655232275382685012344776588097793621230049020"
506            )
507        );
508        assert_eq!(
509            vm.get_integer((1, 3).into()).unwrap().as_ref(),
510            &felt_str!(
511                "386236054595386575795345623791920124827519018828430310912260655089307618738"
512            )
513        );
514    }
515}