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 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
47pub 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
77pub 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
133pub 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
155fn random_ec_point_seeded(seed_bytes: Vec<u8>) -> Result<(Felt252, Felt252), HintError> {
159 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 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 let y_coef = (-1).pow(seed[0] & 1);
173 let y = recover_y(&x);
174 if let Some(y) = y {
175 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
194fn 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
207fn 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 vm.run_context.fp = 6;
316 let ids_data = non_continuous_ids_data![("p", -6), ("q", -3), ("m", -4), ("s", -1)];
318 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 assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
356 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 vm.run_context.fp = 6;
380 let ids_data =
382 non_continuous_ids_data![("p", -6), ("m", -4), ("q", -3), ("len", -2), ("s", -1)];
383 add_segments!(vm, 4);
394 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 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 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 vm.insert_value((1, 4).into(), Felt252::from(3)).unwrap();
460 assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
462 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 vm.run_context.fp = 3;
486 let ids_data = non_continuous_ids_data![("x", -3), ("p", -1)];
488 add_segments!(vm, 2);
490 vm.insert_value(
491 (1, 0).into(),
492 felt_str!(
493 "3004956058830981475544150447242655232275382685012344776588097793621230049020"
494 ),
495 )
496 .unwrap();
497 assert_matches!(run_hint!(vm, ids_data, hint_code), Ok(()));
499 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}