1use logicaffeine_base::BigInt;
21use crate::factor::{gcd, mod_inverse, modpow};
22use std::collections::HashMap;
23
24#[inline]
25fn i(x: i64) -> BigInt {
26 BigInt::from_i64(x)
27}
28
29#[inline]
31fn rem_pos(a: &BigInt, n: &BigInt) -> BigInt {
32 let r = a.div_rem(n).map(|(_, r)| r).unwrap_or_else(|| a.clone());
33 if r.is_negative() {
34 r.add(n)
35 } else {
36 r
37 }
38}
39
40#[inline]
41fn mulmod(a: &BigInt, b: &BigInt, n: &BigInt) -> BigInt {
42 rem_pos(&a.mul(b), n)
43}
44#[inline]
45fn addmod(a: &BigInt, b: &BigInt, n: &BigInt) -> BigInt {
46 rem_pos(&a.add(b), n)
47}
48#[inline]
49fn submod(a: &BigInt, b: &BigInt, n: &BigInt) -> BigInt {
50 rem_pos(&a.sub(b), n)
51}
52
53pub fn xdbl(x: &BigInt, z: &BigInt, a24: &BigInt, n: &BigInt) -> (BigInt, BigInt) {
56 let xpz = addmod(x, z, n);
57 let xmz = submod(x, z, n);
58 let t1 = mulmod(&xpz, &xpz, n); let t2 = mulmod(&xmz, &xmz, n); let x2 = mulmod(&t1, &t2, n); let t3 = submod(&t1, &t2, n); let inner = addmod(&t2, &mulmod(a24, &t3, n), n); let z2 = mulmod(&t3, &inner, n);
64 (x2, z2)
65}
66
67pub fn xadd(
70 xp: &BigInt,
71 zp: &BigInt,
72 xq: &BigInt,
73 zq: &BigInt,
74 xd: &BigInt,
75 zd: &BigInt,
76 n: &BigInt,
77) -> (BigInt, BigInt) {
78 let a = mulmod(&submod(xp, zp, n), &addmod(xq, zq, n), n); let b = mulmod(&addmod(xp, zp, n), &submod(xq, zq, n), n); let s = addmod(&a, &b, n);
81 let d = submod(&a, &b, n);
82 let xr = mulmod(zd, &mulmod(&s, &s, n), n); let zr = mulmod(xd, &mulmod(&d, &d, n), n); (xr, zr)
85}
86
87pub fn ladder(k: u64, x: &BigInt, z: &BigInt, a24: &BigInt, n: &BigInt) -> (BigInt, BigInt) {
90 if k == 0 {
91 return (i(0), i(0)); }
93 if k == 1 {
94 return (x.clone(), z.clone());
95 }
96 let mut r0 = (x.clone(), z.clone()); let mut r1 = xdbl(x, z, a24, n); for bit in (0..(64 - k.leading_zeros() - 1)).rev() {
99 if (k >> bit) & 1 == 1 {
100 r0 = xadd(&r0.0, &r0.1, &r1.0, &r1.1, x, z, n);
101 r1 = xdbl(&r1.0, &r1.1, a24, n);
102 } else {
103 r1 = xadd(&r0.0, &r0.1, &r1.0, &r1.1, x, z, n);
104 r0 = xdbl(&r0.0, &r0.1, a24, n);
105 }
106 }
107 r0
108}
109
110fn primes_up_to(b: u64) -> Vec<u64> {
112 if b < 2 {
113 return Vec::new();
114 }
115 let mut sieve = vec![true; (b as usize) + 1];
116 sieve[0] = false;
117 sieve[1] = false;
118 let mut p = 2usize;
119 while p * p <= b as usize {
120 if sieve[p] {
121 let mut m = p * p;
122 while m <= b as usize {
123 sieve[m] = false;
124 m += p;
125 }
126 }
127 p += 1;
128 }
129 (2..=b).filter(|&x| sieve[x as usize]).collect()
130}
131
132fn ecm_stage2(qx: &BigInt, qz: &BigInt, a24: &BigInt, b1: u64, b2: u64, n: &BigInt) -> Option<BigInt> {
139 let one = i(1);
140 let d = 210u64; let half = (d / 2) as usize;
142
143 let mut sx = vec![i(0); half + 1];
145 let mut sz = vec![i(0); half + 1];
146 sx[1] = qx.clone();
147 sz[1] = qz.clone();
148 if half >= 2 {
149 let s2 = xdbl(qx, qz, a24, n);
150 sx[2] = s2.0;
151 sz[2] = s2.1;
152 for j in 3..=half {
153 let s = xadd(&sx[j - 1], &sz[j - 1], qx, qz, &sx[j - 2], &sz[j - 2], n);
154 sx[j] = s.0;
155 sz[j] = s.1;
156 }
157 }
158
159 let mut bins: HashMap<u64, Vec<usize>> = HashMap::new();
161 let (mut i_lo, mut i_hi) = (u64::MAX, 0u64);
162 for &ell in primes_up_to(b2).iter().filter(|&&p| p > b1) {
163 let ii = (ell + d / 2) / d;
164 let j = (ell as i64 - (ii * d) as i64).unsigned_abs() as usize;
165 if (1..=half).contains(&j) {
166 bins.entry(ii).or_default().push(j);
167 i_lo = i_lo.min(ii);
168 i_hi = i_hi.max(ii);
169 }
170 }
171 if i_lo > i_hi {
172 return None; }
174
175 let dq = ladder(d, qx, qz, a24, n);
177 let mut t_prev = ladder(i_lo.saturating_sub(1) * d, qx, qz, a24, n);
178 let mut t_cur = ladder(i_lo * d, qx, qz, a24, n);
179 let mut accum = one.clone();
180 for ii in i_lo..=i_hi {
181 if ii > i_lo {
182 let t_next = xadd(&t_cur.0, &t_cur.1, &dq.0, &dq.1, &t_prev.0, &t_prev.1, n);
183 t_prev = t_cur;
184 t_cur = t_next;
185 }
186 if let Some(js) = bins.get(&ii) {
187 for &j in js {
188 let cross = submod(&mulmod(&t_cur.0, &sz[j], n), &mulmod(&sx[j], &t_cur.1, n), n);
189 if !cross.is_zero() {
190 accum = mulmod(&accum, &cross, n);
191 }
192 }
193 }
194 }
195 let g = gcd(&accum, n);
196 (g != one && g != *n).then_some(g)
197}
198
199fn ecm_one_curve(n: &BigInt, sigma_u: u64, b1: u64, b2: u64, primes1: &[u64]) -> Option<BigInt> {
201 let one = i(1);
202 let sigma = i(sigma_u as i64);
203 let u = submod(&mulmod(&sigma, &sigma, n), &i(5), n); let v = mulmod(&i(4), &sigma, n); let u3 = mulmod(&mulmod(&u, &u, n), &u, n);
206 let v3 = mulmod(&mulmod(&v, &v, n), &v, n);
207 let vmu = submod(&v, &u, n);
208 let vmu3 = mulmod(&mulmod(&vmu, &vmu, n), &vmu, n);
209 let num = mulmod(&vmu3, &addmod(&mulmod(&i(3), &u, n), &v, n), n); let den = mulmod(&mulmod(&i(4), &u3, n), &v, n); let g = gcd(&den, n);
214 if g != one && g != *n {
215 return Some(g);
216 }
217 let a24 = mulmod(&num, &mod_inverse(&den, n)?, n);
218 let mut pt = (u3, v3);
219
220 for &p in primes1 {
222 let mut q = p;
223 while q <= b1 {
224 pt = ladder(p, &pt.0, &pt.1, &a24, n);
225 q = q.saturating_mul(p);
226 }
227 }
228 let g = gcd(&pt.1, n);
229 if g != one && g != *n {
230 return Some(g);
231 }
232 if b2 > b1 {
233 return ecm_stage2(&pt.0, &pt.1, &a24, b1, b2, n);
234 }
235 None
236}
237
238#[inline]
240fn suyama_sigma(seed: u64, c: usize) -> u64 {
241 6 + seed.wrapping_add(c as u64).wrapping_mul(2_654_435_761) % 1_000_000_000
242}
243
244pub fn ecm_factor(n: &BigInt, b1: u64, curves: usize, seed: u64) -> Option<BigInt> {
247 if n.to_i64().is_some_and(|v| v <= 3) {
248 return None;
249 }
250 if !n.is_odd() {
251 return Some(i(2));
252 }
253 let primes = primes_up_to(b1);
254 (0..curves).find_map(|c| ecm_one_curve(n, suyama_sigma(seed, c), b1, b1, &primes))
255}
256
257pub fn ecm_two_stage(n: &BigInt, b1: u64, b2: u64, curves: usize, seed: u64) -> Option<BigInt> {
260 if n.to_i64().is_some_and(|v| v <= 3) {
261 return None;
262 }
263 if !n.is_odd() {
264 return Some(i(2));
265 }
266 let primes = primes_up_to(b1);
267 (0..curves).find_map(|c| ecm_one_curve(n, suyama_sigma(seed, c), b1, b2, &primes))
268}
269
270pub fn ecm(n: &BigInt, budget: usize, seed: u64) -> Option<BigInt> {
277 const SCHEDULE: &[(u64, usize)] = &[(2_000, 25), (11_000, 90), (50_000, 300), (250_000, 700)];
278 for (level, &(b1, curves)) in SCHEDULE.iter().enumerate() {
279 let curves = curves.min(budget.max(1));
280 if let Some(f) = ecm_two_stage(n, b1, b1.saturating_mul(100), curves, seed.wrapping_add(level as u64)) {
281 return Some(f);
282 }
283 }
284 None
285}
286
287#[derive(Clone, Debug, PartialEq, Eq)]
299pub struct Curve {
300 pub a: BigInt,
301 pub b: BigInt,
302 pub p: BigInt,
303}
304
305#[derive(Clone, Debug, PartialEq, Eq)]
307pub enum Point {
308 Infinity,
309 Affine(BigInt, BigInt),
310}
311
312impl Curve {
313 pub fn new(a: BigInt, b: BigInt, p: BigInt) -> Curve {
314 Curve { a, b, p }
315 }
316
317 pub fn is_on_curve(&self, pt: &Point) -> bool {
319 match pt {
320 Point::Infinity => true,
321 Point::Affine(x, y) => {
322 let lhs = mulmod(y, y, &self.p);
323 let x3 = mulmod(&mulmod(x, x, &self.p), x, &self.p);
324 let rhs = addmod(&addmod(&x3, &mulmod(&self.a, x, &self.p), &self.p), &self.b, &self.p);
325 lhs == rhs
326 }
327 }
328 }
329
330 pub fn negate(&self, pt: &Point) -> Point {
332 match pt {
333 Point::Infinity => Point::Infinity,
334 Point::Affine(x, y) => Point::Affine(x.clone(), submod(&i(0), y, &self.p)),
335 }
336 }
337
338 pub fn add(&self, p1: &Point, p2: &Point) -> Point {
340 match (p1, p2) {
341 (Point::Infinity, _) => p2.clone(),
342 (_, Point::Infinity) => p1.clone(),
343 (Point::Affine(x1, y1), Point::Affine(x2, y2)) => {
344 if x1 == x2 {
345 if addmod(y1, y2, &self.p).is_zero() {
346 return Point::Infinity; }
348 return self.double(p1); }
350 let den = mod_inverse(&submod(x2, x1, &self.p), &self.p).expect("distinct x invertible over 𝔽_p");
351 let lam = mulmod(&submod(y2, y1, &self.p), &den, &self.p);
352 let x3 = submod(&submod(&mulmod(&lam, &lam, &self.p), x1, &self.p), x2, &self.p);
353 let y3 = submod(&mulmod(&lam, &submod(x1, &x3, &self.p), &self.p), y1, &self.p);
354 Point::Affine(x3, y3)
355 }
356 }
357 }
358
359 pub fn double(&self, pt: &Point) -> Point {
361 match pt {
362 Point::Infinity => Point::Infinity,
363 Point::Affine(x, y) => {
364 if y.is_zero() {
365 return Point::Infinity; }
367 let num = addmod(&mulmod(&i(3), &mulmod(x, x, &self.p), &self.p), &self.a, &self.p);
368 let den = mod_inverse(&mulmod(&i(2), y, &self.p), &self.p).expect("2y invertible over 𝔽_p");
369 let lam = mulmod(&num, &den, &self.p);
370 let x3 = submod(&mulmod(&lam, &lam, &self.p), &mulmod(&i(2), x, &self.p), &self.p);
371 let y3 = submod(&mulmod(&lam, &submod(x, &x3, &self.p), &self.p), y, &self.p);
372 Point::Affine(x3, y3)
373 }
374 }
375 }
376
377 pub fn mul(&self, k: &BigInt, pt: &Point) -> Point {
379 let mut result = Point::Infinity;
380 let mut addend = pt.clone();
381 let (_, bytes) = k.to_le_bytes();
382 for byte in bytes {
383 for b in 0..8 {
384 if (byte >> b) & 1 == 1 {
385 result = self.add(&result, &addend);
386 }
387 addend = self.double(&addend);
388 }
389 }
390 result
391 }
392
393 pub fn count_points(&self) -> u64 {
395 let p_u = self.p.to_i64().expect("small prime") as u64;
396 let exp = self.p.sub(&i(1)).div_rem(&i(2)).unwrap().0; let one = i(1);
398 let mut count = 1u64; for xu in 0..p_u {
400 let x = i(xu as i64);
401 let x3 = mulmod(&mulmod(&x, &x, &self.p), &x, &self.p);
402 let rhs = addmod(&addmod(&x3, &mulmod(&self.a, &x, &self.p), &self.p), &self.b, &self.p);
403 if rhs.is_zero() {
404 count += 1;
405 } else if modpow(&rhs, &exp, &self.p) == one {
406 count += 2; }
408 }
409 count
410 }
411
412 pub fn point_order(&self, pt: &Point, bound: u64) -> Option<u64> {
414 let mut cur = pt.clone();
415 for k in 1..=bound {
416 if cur == Point::Infinity {
417 return Some(k);
418 }
419 cur = self.add(&cur, pt);
420 }
421 None
422 }
423}
424
425fn point_key(pt: &Point) -> Vec<u8> {
426 match pt {
427 Point::Infinity => vec![0xff],
428 Point::Affine(x, y) => {
429 let (_, xb) = x.to_le_bytes();
430 let (_, yb) = y.to_le_bytes();
431 let mut k = vec![0x00, xb.len() as u8];
432 k.extend(xb);
433 k.extend(yb);
434 k
435 }
436 }
437}
438
439pub fn ecdlp_bsgs(curve: &Curve, base: &Point, target: &Point, n: u64) -> Option<BigInt> {
444 let m = (n as f64).sqrt() as u64 + 1;
445 let mut baby: HashMap<Vec<u8>, u64> = HashMap::new();
446 let mut cur = Point::Infinity;
447 for j in 0..m {
448 baby.entry(point_key(&cur)).or_insert(j);
449 cur = curve.add(&cur, base);
450 }
451 let neg_mp = curve.negate(&curve.mul(&i(m as i64), base)); let mut gamma = target.clone();
453 for step in 0..=m {
454 if let Some(&j) = baby.get(&point_key(&gamma)) {
455 return Some(i((step * m + j) as i64));
456 }
457 gamma = curve.add(&gamma, &neg_mp);
458 }
459 None
460}
461
462#[derive(Clone, Debug, PartialEq, Eq)]
465pub enum CurveWeakness {
466 Anomalous,
468 Supersingular,
470 SmoothOrder { largest_prime_factor: u64 },
472}
473
474pub fn curve_security(p: u64, order: u64) -> Option<CurveWeakness> {
477 if order == p {
478 return Some(CurveWeakness::Anomalous);
479 }
480 if order == p + 1 {
481 return Some(CurveWeakness::Supersingular);
482 }
483 let lpf = largest_prime_factor(order);
484 if lpf.saturating_mul(lpf) < order {
485 return Some(CurveWeakness::SmoothOrder { largest_prime_factor: lpf });
486 }
487 None
488}
489
490fn largest_prime_factor(mut m: u64) -> u64 {
491 let mut largest = 1u64;
492 let mut d = 2u64;
493 while d * d <= m {
494 while m % d == 0 {
495 largest = d;
496 m /= d;
497 }
498 d += 1;
499 }
500 largest.max(m)
501}
502
503impl Curve {
514 pub fn j_invariant(&self) -> Option<BigInt> {
518 let p = &self.p;
519 let a3 = mulmod(&mulmod(&self.a, &self.a, p), &self.a, p);
520 let four_a3 = mulmod(&i(4), &a3, p);
521 let disc = addmod(&four_a3, &mulmod(&i(27), &mulmod(&self.b, &self.b, p), p), p);
522 if disc.is_zero() {
523 return None;
524 }
525 Some(mulmod(&mulmod(&i(1728), &four_a3, p), &mod_inverse(&disc, p)?, p))
526 }
527}
528
529#[derive(Clone, Debug)]
533pub struct Isogeny {
534 pub domain: Curve,
535 pub codomain: Curve,
536 pub degree: u64,
537 kernel: Vec<(BigInt, BigInt, BigInt)>, }
539
540impl Isogeny {
541 pub fn from_kernel(curve: &Curve, gen: &Point, ell: u64) -> Option<Isogeny> {
545 if ell < 3 || ell % 2 == 0 {
546 return None;
547 }
548 let p = &curve.p;
549 let mut kernel = Vec::with_capacity(((ell - 1) / 2) as usize);
550 let mut cur = gen.clone();
551 for _ in 0..((ell - 1) / 2) {
552 match &cur {
553 Point::Affine(xq, yq) => {
554 let vq = addmod(&mulmod(&i(6), &mulmod(xq, xq, p), p), &mulmod(&i(2), &curve.a, p), p);
555 let uq = mulmod(&i(4), &mulmod(yq, yq, p), p);
556 kernel.push((xq.clone(), vq, uq));
557 }
558 Point::Infinity => return None, }
560 cur = curve.add(&cur, gen);
561 }
562 let _ = cur;
564 if matches!(gen, Point::Infinity) || curve.mul(&i(ell as i64), gen) != Point::Infinity {
565 return None;
566 }
567 let (mut vsum, mut wsum) = (i(0), i(0));
568 for (xq, vq, uq) in &kernel {
569 vsum = addmod(&vsum, vq, p);
570 wsum = addmod(&wsum, &addmod(uq, &mulmod(xq, vq, p), p), p);
571 }
572 let a2 = submod(&curve.a, &mulmod(&i(5), &vsum, p), p);
573 let b2 = submod(&curve.b, &mulmod(&i(7), &wsum, p), p);
574 Some(Isogeny { domain: curve.clone(), codomain: Curve::new(a2, b2, p.clone()), degree: ell, kernel })
575 }
576
577 pub fn eval(&self, pt: &Point) -> Point {
582 let p = &self.domain.p;
583 match pt {
584 Point::Infinity => Point::Infinity,
585 Point::Affine(x, y) => {
586 let mut xnew = x.clone();
587 let mut yfac = i(1);
588 for (xq, vq, uq) in &self.kernel {
589 let d = submod(x, xq, p);
590 if d.is_zero() {
591 return Point::Infinity; }
593 let di = mod_inverse(&d, p).expect("nonzero over 𝔽_p");
594 let di2 = mulmod(&di, &di, p);
595 let di3 = mulmod(&di2, &di, p);
596 xnew = addmod(&xnew, &addmod(&mulmod(vq, &di, p), &mulmod(uq, &di2, p), p), p);
597 yfac = submod(&yfac, &addmod(&mulmod(vq, &di2, p), &mulmod(&mulmod(&i(2), uq, p), &di3, p), p), p);
598 }
599 Point::Affine(xnew, mulmod(y, &yfac, p))
600 }
601 }
602 }
603}
604
605fn miller_double_lines(c: &Curve, t: &Point, xq: &BigInt, yq: &BigInt) -> Option<(BigInt, BigInt)> {
615 let p = &c.p;
616 let (xt, yt) = match t {
617 Point::Affine(x, y) => (x, y),
618 Point::Infinity => return None,
619 };
620 if yt.is_zero() {
621 return None; }
623 let lam = mulmod(
624 &addmod(&mulmod(&i(3), &mulmod(xt, xt, p), p), &c.a, p),
625 &mod_inverse(&mulmod(&i(2), yt, p), p)?,
626 p,
627 );
628 let ell = submod(&submod(yq, yt, p), &mulmod(&lam, &submod(xq, xt, p), p), p);
629 let t2 = c.double(t);
630 let vert = match &t2 {
631 Point::Affine(x2, _) => submod(xq, x2, p),
632 Point::Infinity => i(1), };
634 Some((ell, vert))
635}
636
637fn miller_add_lines(c: &Curve, t: &Point, pp: &Point, xq: &BigInt, yq: &BigInt) -> Option<(BigInt, BigInt)> {
640 let p = &c.p;
641 let ((xt, yt), (xpp, ypp)) = match (t, pp) {
642 (Point::Affine(a, b), Point::Affine(cc, d)) => ((a, b), (cc, d)),
643 _ => return None,
644 };
645 if xt == xpp {
646 if yt == ypp {
647 return miller_double_lines(c, t, xq, yq); }
649 return Some((submod(xq, xt, p), i(1)));
651 }
652 let lam = mulmod(&submod(ypp, yt, p), &mod_inverse(&submod(xpp, xt, p), p)?, p);
653 let ell = submod(&submod(yq, yt, p), &mulmod(&lam, &submod(xq, xt, p), p), p);
654 let sum = c.add(t, pp);
655 let vert = match &sum {
656 Point::Affine(x3, _) => submod(xq, x3, p),
657 Point::Infinity => i(1),
658 };
659 Some((ell, vert))
660}
661
662fn miller(c: &Curve, pp: &Point, qq: &Point, n: u64) -> Option<BigInt> {
664 let p = &c.p;
665 let (xq, yq) = match qq {
666 Point::Affine(x, y) => (x, y),
667 Point::Infinity => return None,
668 };
669 let (mut num, mut den) = (i(1), i(1));
670 let mut t = pp.clone();
671 for bit in (0..(64 - n.leading_zeros() - 1)).rev() {
672 let (ell, vert) = miller_double_lines(c, &t, xq, yq)?;
673 num = mulmod(&mulmod(&num, &num, p), &ell, p);
674 den = mulmod(&mulmod(&den, &den, p), &vert, p);
675 t = c.double(&t);
676 if (n >> bit) & 1 == 1 {
677 let (ell, vert) = miller_add_lines(c, &t, pp, xq, yq)?;
678 num = mulmod(&num, &ell, p);
679 den = mulmod(&den, &vert, p);
680 t = c.add(&t, pp);
681 }
682 }
683 if den.is_zero() {
684 return None;
685 }
686 Some(mulmod(&num, &mod_inverse(&den, p)?, p))
687}
688
689pub fn weil_pairing(c: &Curve, pp: &Point, qq: &Point, n: u64) -> Option<BigInt> {
693 if pp == qq || matches!(pp, Point::Infinity) || matches!(qq, Point::Infinity) {
694 return Some(i(1)); }
696 let fp = miller(c, pp, qq, n)?;
697 let fq = miller(c, qq, pp, n)?;
698 let ratio = mulmod(&fp, &mod_inverse(&fq, &c.p)?, &c.p);
699 let e = if n % 2 == 1 { submod(&i(0), &ratio, &c.p) } else { ratio }; Some(e)
701}
702
703fn sqrt_fp(a: &BigInt, p: &BigInt) -> Option<BigInt> {
706 let exp = p.add(&i(1)).div_rem(&i(4))?.0;
707 let r = modpow(a, &exp, p);
708 (mulmod(&r, &r, p) == rem_pos(a, p)).then_some(r)
709}
710
711fn all_affine_points(curve: &Curve) -> Vec<Point> {
713 let pu = curve.p.to_i64().expect("small prime") as u64;
714 let mut v = Vec::new();
715 for xu in 0..pu {
716 let x = i(xu as i64);
717 let x3 = mulmod(&mulmod(&x, &x, &curve.p), &x, &curve.p);
718 let rhs = addmod(&addmod(&x3, &mulmod(&curve.a, &x, &curve.p), &curve.p), &curve.b, &curve.p);
719 if rhs.is_zero() {
720 v.push(Point::Affine(x, i(0)));
721 } else if let Some(y) = sqrt_fp(&rhs, &curve.p) {
722 v.push(Point::Affine(x.clone(), submod(&i(0), &y, &curve.p)));
723 v.push(Point::Affine(x, y));
724 }
725 }
726 v
727}
728
729pub fn torsion_basis(curve: &Curve, n: u64) -> Option<(Point, Point)> {
733 let order_n: Vec<Point> = all_affine_points(curve)
734 .into_iter()
735 .filter(|pt| curve.mul(&i(n as i64), pt) == Point::Infinity)
736 .collect();
737 for p in &order_n {
738 let span: Vec<Point> = (0..n).map(|k| curve.mul(&i(k as i64), p)).collect();
739 if let Some(q) = order_n.iter().find(|q| !span.contains(q)) {
740 return Some((p.clone(), q.clone()));
741 }
742 }
743 None
744}
745
746pub fn point_of_order(curve: &Curve, ell: u64) -> Option<Point> {
749 all_affine_points(curve).into_iter().find(|pt| curve.mul(&i(ell as i64), pt) == Point::Infinity)
750}
751
752pub fn kernel_generator(curve: &Curve, p_pt: &Point, q_pt: &Point, s: &BigInt) -> Point {
756 curve.add(p_pt, &curve.mul(s, q_pt))
757}
758
759#[derive(Clone, Debug, PartialEq, Eq)]
762pub struct IsogenyStep {
763 pub domain: Curve,
764 pub kernel: Point,
765 pub codomain: Curve,
766}
767
768pub fn derive_isogeny_path(curve: &Curve, gen: &Point, ell: u64, a: u32) -> Option<Vec<IsogenyStep>> {
775 let mut steps = Vec::with_capacity(a as usize);
776 let mut e = curve.clone();
777 let mut g = gen.clone();
778 for step in 0..a {
779 let mut mult = i(1);
780 for _ in 0..(a - 1 - step) {
781 mult = mult.mul(&i(ell as i64));
782 }
783 let k = e.mul(&mult, &g); let iso = Isogeny::from_kernel(&e, &k, ell)?;
785 steps.push(IsogenyStep { domain: e.clone(), kernel: k, codomain: iso.codomain.clone() });
786 g = iso.eval(&g);
787 e = iso.codomain;
788 }
789 Some(steps)
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 fn big(s: &str) -> BigInt {
797 BigInt::parse_decimal(s).unwrap()
798 }
799
800 fn proj_eq(a: &(BigInt, BigInt), b: &(BigInt, BigInt), n: &BigInt) -> bool {
802 mulmod(&a.0, &b.1, n) == mulmod(&b.0, &a.1, n)
803 }
804
805 #[test]
806 fn ladder_is_a_consistent_scalar_multiplication() {
807 let n = big("1000003"); let a24 = i(7);
811 let p = (i(2), i(1));
812 for (a, b) in [(7u64, 11u64), (13, 5), (100, 37), (1, 999), (255, 256)] {
813 let lhs = ladder(a * b, &p.0, &p.1, &a24, &n);
814 let rhs = ladder(a, &ladder(b, &p.0, &p.1, &a24, &n).0, &ladder(b, &p.0, &p.1, &a24, &n).1, &a24, &n);
815 assert!(proj_eq(&lhs, &rhs, &n), "(a·b)·P = a·(b·P) for a={a} b={b}");
816 }
817 let p2 = xdbl(&p.0, &p.1, &a24, &n);
819 let p3 = xadd(&p2.0, &p2.1, &p.0, &p.1, &p.0, &p.1, &n); let p5 = xadd(&p3.0, &p3.1, &p2.0, &p2.1, &p.0, &p.1, &n); assert!(proj_eq(&p2, &ladder(2, &p.0, &p.1, &a24, &n), &n));
822 assert!(proj_eq(&p3, &ladder(3, &p.0, &p.1, &a24, &n), &n));
823 assert!(proj_eq(&p5, &ladder(5, &p.0, &p.1, &a24, &n), &n));
824 }
825
826 #[test]
827 fn ecm_pulls_a_small_factor_from_a_semiprime() {
828 let p = big("1000003");
831 let q = big("1000000000039");
832 let n = p.mul(&q);
833 let f = ecm_factor(&n, 50_000, 60, 12345).expect("ECM finds a factor");
834 assert!(f != i(1) && f != n, "a nontrivial factor");
835 assert!(n.div_rem(&f).unwrap().1.is_zero(), "and it actually divides N");
836 assert!(f == p || f == q, "recovering one of the true primes");
837 }
838
839 #[test]
840 fn ecm_pulls_a_factor_from_a_larger_modulus() {
841 let p = big("100000007"); let q = big("340282366920938463463374607431768211507"); let n = p.mul(&q);
845 let f = ecm_factor(&n, 100_000, 80, 999).expect("ECM finds the moderate factor");
846 assert_eq!(f, p, "ECM pulls the smaller prime out of a huge modulus");
847 assert!(n.div_rem(&f).unwrap().1.is_zero());
848 }
849
850 #[test]
851 fn ecm_declines_on_a_prime() {
852 let prime = big("1000000000039");
854 assert_eq!(ecm_factor(&prime, 10_000, 40, 7), None, "no factor of a prime");
855 }
856
857 #[test]
858 fn ecm_two_stage_and_driver_factor() {
859 let p = big("100003"); let q = big("1000000000039");
861 let n = p.mul(&q);
862 let f = ecm_two_stage(&n, 500, 20_000, 40, 2024).expect("two-stage ECM finds the factor");
864 assert!(f != i(1) && f != n && n.div_rem(&f).unwrap().1.is_zero(), "a real divisor");
865 assert!(f == p || f == q);
866 let g = ecm(&n, 60, 5).expect("the ECM driver factors by escalation");
868 assert!(g != i(1) && g != n && n.div_rem(&g).unwrap().1.is_zero());
869 }
870
871 fn curve17() -> (Curve, Point) {
873 (Curve::new(i(2), i(2), i(17)), Point::Affine(i(5), i(1)))
874 }
875
876 #[test]
877 fn ecdlp_group_law_is_a_real_abelian_group() {
878 let (c, p) = curve17();
879 assert!(c.is_on_curve(&p));
880 assert_eq!(c.add(&p, &c.negate(&p)), Point::Infinity);
882 let p2 = c.double(&p);
884 assert_eq!(p2, c.add(&p, &p));
885 assert!(c.is_on_curve(&p2));
886 assert_eq!(c.mul(&i(3), &p), c.add(&c.add(&p, &p), &p));
888 let (q, r) = (c.mul(&i(2), &p), c.mul(&i(5), &p));
890 assert_eq!(c.add(&c.add(&p, &q), &r), c.add(&p, &c.add(&q, &r)), "the group law is associative");
891 let ord = c.point_order(&p, 100).expect("a small order");
893 assert_eq!(c.mul(&i(ord as i64), &p), Point::Infinity, "ord(P)·P = O");
894 }
895
896 #[test]
897 fn ecdlp_bsgs_recovers_the_discrete_log() {
898 let (c, p) = curve17();
899 let n = c.point_order(&p, 100).unwrap();
900 for k in [3u64, 7, 11, 15] {
901 let q = c.mul(&i(k as i64), &p);
902 let recovered = ecdlp_bsgs(&c, &p, &q, n).expect("Q is in ⟨P⟩");
903 assert_eq!(c.mul(&recovered, &p), q, "k·P = Q for the recovered k (k mod ord(P))");
904 }
905 }
906
907 #[test]
908 fn point_count_is_hasse_valid_and_annihilates_every_point() {
909 let (c, p) = curve17();
910 let order = c.count_points();
911 assert!((10..=26).contains(&order), "#E = {order} within Hasse");
913 assert_eq!(c.mul(&i(order as i64), &p), Point::Infinity);
915 }
916
917 #[test]
918 fn curve_security_flags_exactly_the_weak_structures() {
919 assert_eq!(curve_security(23, 23), Some(CurveWeakness::Anomalous));
921 assert_eq!(curve_security(23, 24), Some(CurveWeakness::Supersingular));
923 assert_eq!(curve_security(101, 100), Some(CurveWeakness::SmoothOrder { largest_prime_factor: 5 }));
925 assert_eq!(curve_security(101, 103), None, "a prime-order curve resists");
927
928 let mut found = false;
930 'search: for pp in [11u64, 19, 23] {
931 for a in 0..pp {
932 for b in 0..pp {
933 let c = Curve::new(i(a as i64), i(b as i64), i(pp as i64));
934 let disc = (4 * a * a * a + 27 * b * b) % pp;
936 if disc == 0 {
937 continue;
938 }
939 if c.count_points() == pp + 1 {
940 assert_eq!(curve_security(pp, pp + 1), Some(CurveWeakness::Supersingular));
941 found = true;
942 break 'search;
943 }
944 }
945 }
946 }
947 assert!(found, "supersingular curves exist and are detected");
948 }
949
950 fn sqrt_mod(a: &BigInt, p: &BigInt) -> Option<BigInt> {
952 let exp = p.add(&i(1)).div_rem(&i(4)).unwrap().0;
953 let r = modpow(a, &exp, p);
954 (mulmod(&r, &r, p) == rem_pos(a, p)).then_some(r)
955 }
956
957 fn rhs(c: &Curve, x: &BigInt) -> BigInt {
958 let x3 = mulmod(&mulmod(x, x, &c.p), x, &c.p);
959 addmod(&addmod(&x3, &mulmod(&c.a, x, &c.p), &c.p), &c.b, &c.p)
960 }
961
962 fn curve_points(c: &Curve) -> Vec<Point> {
963 let pu = c.p.to_i64().unwrap() as u64;
964 let mut v = vec![Point::Infinity];
965 for xu in 0..pu {
966 let x = i(xu as i64);
967 let r = rhs(c, &x);
968 if r.is_zero() {
969 v.push(Point::Affine(x, i(0)));
970 } else if let Some(y) = sqrt_mod(&r, &c.p) {
971 v.push(Point::Affine(x.clone(), submod(&i(0), &y, &c.p)));
972 v.push(Point::Affine(x, y));
973 }
974 }
975 v
976 }
977
978 fn find_ell_isogeny(pval: u64) -> Option<(Curve, Point, u64)> {
980 let p = i(pval as i64);
981 for a in 1..pval {
982 for b in 1..pval {
983 let c = Curve::new(i(a as i64), i(b as i64), p.clone());
984 if c.j_invariant().is_none() {
985 continue; }
987 let order = c.count_points();
988 for ell in [3u64, 5, 7] {
989 if order % ell != 0 {
990 continue;
991 }
992 let cof = order / ell;
993 for pt in curve_points(&c) {
994 let q = c.mul(&i(cof as i64), &pt);
995 if q != Point::Infinity && c.mul(&i(ell as i64), &q) == Point::Infinity {
996 return Some((c, q, ell));
997 }
998 }
999 }
1000 }
1001 }
1002 None
1003 }
1004
1005 #[test]
1006 fn j_invariant_is_an_isomorphism_invariant() {
1007 let p = i(103);
1008 let c = Curve::new(i(2), i(3), p.clone());
1009 let j = c.j_invariant().unwrap();
1010 let u2 = mulmod(&i(5), &i(5), &p);
1012 let u4 = mulmod(&u2, &u2, &p);
1013 let u6 = mulmod(&u4, &u2, &p);
1014 let twist = Curve::new(mulmod(&u4, &i(2), &p), mulmod(&u6, &i(3), &p), p.clone());
1015 assert_eq!(twist.j_invariant().unwrap(), j, "j is invariant under isomorphism");
1016 assert_ne!(Curve::new(i(1), i(1), p).j_invariant().unwrap(), j, "different curves, different j");
1017 }
1018
1019 fn find_full_torsion(primes: &[u64], n: u64) -> Option<(Curve, Point, Point)> {
1022 for &pval in primes {
1023 let p = i(pval as i64);
1024 for a in 1..pval {
1025 for b in 1..pval {
1026 let c = Curve::new(i(a as i64), i(b as i64), p.clone());
1027 if c.j_invariant().is_none() {
1028 continue;
1029 }
1030 if c.count_points() % (n * n) != 0 {
1031 continue;
1032 }
1033 let onp: Vec<Point> = curve_points(&c)
1034 .into_iter()
1035 .filter(|pt| *pt != Point::Infinity && c.mul(&i(n as i64), pt) == Point::Infinity)
1036 .collect();
1037 if (onp.len() as u64) < n * n - 1 {
1038 continue; }
1040 for pp in &onp {
1041 let span: Vec<Point> = (0..n).map(|k| c.mul(&i(k as i64), pp)).collect();
1042 if let Some(qq) = onp.iter().find(|q| !span.contains(q)) {
1043 return Some((c.clone(), pp.clone(), qq.clone()));
1044 }
1045 }
1046 }
1047 }
1048 }
1049 None
1050 }
1051
1052 #[test]
1053 fn weil_pairing_is_bilinear_alternating_and_nondegenerate() {
1054 let n = 3u64;
1055 let (c, pp, qq) = find_full_torsion(&[7u64, 13, 19, 31], n).expect("a curve with full n-torsion");
1056 let e = weil_pairing(&c, &pp, &qq, n).unwrap();
1057 assert_ne!(e, i(1), "independent points ⟹ nontrivial pairing");
1059 assert_eq!(modpow(&e, &i(n as i64), &c.p), i(1), "e(P,Q) ∈ μ_n");
1060 assert_eq!(weil_pairing(&c, &pp, &pp, n).unwrap(), i(1), "e(P,P) = 1");
1062 assert_eq!(weil_pairing(&c, &qq, &pp, n).unwrap(), mod_inverse(&e, &c.p).unwrap());
1064 for k in 1..n {
1066 let ek = weil_pairing(&c, &c.mul(&i(k as i64), &pp), &qq, n).unwrap();
1067 assert_eq!(ek, modpow(&e, &i(k as i64), &c.p), "e({k}P, Q) = e(P,Q)^{k}");
1068 }
1069 }
1070
1071 #[test]
1072 fn velu_isogeny_is_a_homomorphism_kills_its_kernel_and_preserves_order() {
1073 let (c, gen, ell) = find_ell_isogeny(103).expect("an ℓ-torsion instance exists");
1074 let iso = Isogeny::from_kernel(&c, &gen, ell).expect("Vélu builds the isogeny");
1075 assert_eq!(iso.degree, ell);
1076 let cod = &iso.codomain;
1077
1078 assert_eq!(c.count_points(), cod.count_points(), "isogenous ⟹ equal order");
1080
1081 let mut k = gen.clone();
1083 for _ in 0..ell {
1084 assert_eq!(iso.eval(&k), Point::Infinity, "φ kills its kernel");
1085 k = c.add(&k, &gen);
1086 }
1087
1088 let pts = curve_points(&c);
1090 for u in pts.iter().take(9) {
1091 for v in pts.iter().take(9) {
1092 let (fu, fv) = (iso.eval(u), iso.eval(v));
1093 assert!(cod.is_on_curve(&fu), "φ(P) lands on the codomain");
1094 assert_eq!(iso.eval(&c.add(u, v)), cod.add(&fu, &fv), "φ(P+Q) = φ(P)+φ(Q)");
1095 }
1096 }
1097 }
1098
1099 fn largest_prime_power_generator(curve: &Curve, ell: u64) -> Option<(Point, u32)> {
1101 (1u32..=4).rev().find_map(|a| {
1102 let n = ell.pow(a);
1103 all_affine_points(curve)
1104 .into_iter()
1105 .find(|pt| curve.point_order(pt, n + 1) == Some(n))
1106 .map(|g| (g, a))
1107 })
1108 }
1109
1110 #[test]
1111 fn torsion_image_generator_unfolds_into_the_secret_isogeny_path() {
1112 let p = big("107");
1115 let e = Curve::new(i(0), i(1), p.clone());
1116 assert_eq!(e.count_points(), 108, "#E = p + 1 (supersingular)");
1117
1118 let (gen, a) = largest_prime_power_generator(&e, 3).expect("a 3-power torsion generator");
1120 assert!(a >= 2, "the 3-Sylow (order 27) forces a point of order ≥ 9 ⟹ a genuine multi-step chain");
1121
1122 let path = derive_isogeny_path(&e, &gen, 3, a).expect("a valid 3-isogeny chain");
1124 assert_eq!(path.len() as u32, a, "one ℓ-isogeny per unit of the exponent");
1125
1126 for (idx, step) in path.iter().enumerate() {
1128 assert_ne!(step.kernel, Point::Infinity, "step {idx} kernel is nontrivial");
1129 assert_eq!(step.domain.mul(&i(3), &step.kernel), Point::Infinity, "step {idx} kernel has order 3");
1130 assert!(step.domain.is_on_curve(&step.kernel), "the kernel point lies on the step's domain");
1131 if idx > 0 {
1132 assert_eq!(step.domain, path[idx - 1].codomain, "the chain is connected");
1133 }
1134 }
1135
1136 let outside = all_affine_points(&e)
1139 .into_iter()
1140 .find(|pt| (0..3u64.pow(a)).all(|k| e.mul(&i(k as i64), &gen) != *pt))
1141 .expect("a point outside ⟨gen⟩");
1142 let (mut in_img, mut out_img) = (gen.clone(), outside);
1143 for step in &path {
1144 let iso = Isogeny::from_kernel(&step.domain, &step.kernel, 3).unwrap();
1145 in_img = iso.eval(&in_img);
1146 out_img = iso.eval(&out_img);
1147 }
1148 assert_eq!(in_img, Point::Infinity, "gen generates the kernel of the whole composite");
1149 assert_ne!(out_img, Point::Infinity, "a point outside ⟨gen⟩ survives the composite");
1150 }
1151
1152 #[test]
1153 fn the_secret_scalar_selects_the_isogeny_through_a_torsion_basis() {
1154 let (c, p_pt, q_pt) = [19u64, 31, 43, 67, 79, 103]
1157 .iter()
1158 .find_map(|&pp| {
1159 let p = big(&pp.to_string());
1160 (0..pp).find_map(|a| {
1161 (1..pp).find_map(|b| {
1162 let disc = addmod(
1164 &mulmod(&i(4), &mulmod(&mulmod(&i(a as i64), &i(a as i64), &p), &i(a as i64), &p), &p),
1165 &mulmod(&i(27), &mulmod(&i(b as i64), &i(b as i64), &p), &p),
1166 &p,
1167 );
1168 if disc.is_zero() {
1169 return None;
1170 }
1171 let c = Curve::new(i(a as i64), i(b as i64), p.clone());
1172 torsion_basis(&c, 3)
1173 .filter(|(pt, qt)| {
1174 c.point_order(pt, 4) == Some(3) && c.point_order(qt, 4) == Some(3)
1175 })
1176 .map(|(pt, qt)| (c.clone(), pt, qt))
1177 })
1178 })
1179 })
1180 .expect("a curve with a rank-2 3-torsion basis");
1181
1182 let mut lines = std::collections::HashSet::new();
1185 for s in 0..3u64 {
1186 let gen = kernel_generator(&c, &p_pt, &q_pt, &i(s as i64));
1187 assert!(c.is_on_curve(&gen), "P + [s]Q lies on the curve");
1188 assert_eq!(c.point_order(&gen, 4), Some(3), "P + [s]Q is a genuine order-3 kernel generator");
1189 let path = derive_isogeny_path(&c, &gen, 3, 1).expect("secret s selects a valid 3-isogeny");
1190 assert_eq!(path.len(), 1, "a single secret step is one ℓ-isogeny");
1191 let mut line: Vec<Vec<u8>> = (0..3).map(|k| point_key(&c.mul(&i(k), &gen))).collect();
1193 line.sort();
1194 lines.insert(line);
1195 }
1196 assert!(lines.len() >= 2, "distinct secrets select distinct kernel lines ⟹ distinct isogenies");
1197 }
1198}