1use anyhow::{Context, Result};
2use clap::Subcommand;
3use num_bigint::{BigInt, BigUint, ToBigInt};
4use num_integer::Integer;
5use num_traits::{One, Zero};
6use std::collections::HashMap;
7
8use crate::crypto::primes;
9
10const MAX_BSGS_ITERATIONS: u64 = 1 << 22;
14
15const MAX_POINT_ORDER_ITERATIONS: u64 = 1 << 22;
17
18#[derive(Subcommand)]
19pub enum EcAction {
20 #[command(about = "Add two points on an elliptic curve (y^2 = x^3 + ax + b mod p)")]
21 Add {
22 #[arg(help = "First point as x,y (or 'inf' for point at infinity)")]
23 point1: String,
24 #[arg(help = "Second point as x,y (or 'inf' for point at infinity)")]
25 point2: String,
26 #[arg(long, help = "Curve parameter a")]
27 a: String,
28 #[arg(long, help = "Curve parameter b")]
29 b: String,
30 #[arg(long, help = "Prime modulus p")]
31 p: String,
32 },
33 #[command(about = "Scalar multiplication of a point (n * P)")]
34 Multiply {
35 #[arg(help = "Point as x,y")]
36 point: String,
37 #[arg(long, help = "Scalar multiplier n")]
38 n: String,
39 #[arg(long, help = "Curve parameter a")]
40 a: String,
41 #[arg(long, help = "Curve parameter b")]
42 b: String,
43 #[arg(long, help = "Prime modulus p")]
44 p: String,
45 },
46 #[command(about = "Find the order of a point on the curve")]
47 Order {
48 #[arg(help = "Point as x,y")]
49 point: String,
50 #[arg(long, help = "Curve parameter a")]
51 a: String,
52 #[arg(long, help = "Curve parameter b")]
53 b: String,
54 #[arg(long, help = "Prime modulus p")]
55 p: String,
56 },
57 #[command(about = "Pohlig-Hellman attack to solve ECDLP (Q = nP) for smooth-order curves")]
58 PohligHellman {
59 #[arg(help = "Generator point P as x,y")]
60 generator: String,
61 #[arg(help = "Target point Q as x,y")]
62 target: String,
63 #[arg(long, help = "Curve parameter a")]
64 a: String,
65 #[arg(long, help = "Curve parameter b")]
66 b: String,
67 #[arg(long, help = "Prime modulus p")]
68 p: String,
69 #[arg(long, help = "Order of the generator point")]
70 order: String,
71 },
72}
73
74pub fn run(action: EcAction) -> Result<()> {
75 match action {
76 EcAction::Add {
77 point1,
78 point2,
79 a,
80 b,
81 p,
82 } => {
83 let p1 = parse_point(&point1)?;
84 let p2 = parse_point(&point2)?;
85 let a = a.parse::<BigInt>().context("Invalid number for a")?;
86 let b = b.parse::<BigInt>().context("Invalid number for b")?;
87 let p = p.parse::<BigInt>().context("Invalid number for p")?;
88 if p.is_zero() {
89 anyhow::bail!("Modulus p must be non-zero");
90 }
91 validate_point_on_curve(&p1, &a, &b, &p)?;
92 validate_point_on_curve(&p2, &a, &b, &p)?;
93 let result = point_add(&p1, &p2, &a, &p)?;
94 println!("{}", format_point(&result));
95 }
96 EcAction::Multiply { point, n, a, b, p } => {
97 let pt = parse_point(&point)?;
98 let n = n.parse::<BigUint>().context("Invalid number for n")?;
99 let a = a.parse::<BigInt>().context("Invalid number for a")?;
100 let b = b.parse::<BigInt>().context("Invalid number for b")?;
101 let p = p.parse::<BigInt>().context("Invalid number for p")?;
102 if p.is_zero() {
103 anyhow::bail!("Modulus p must be non-zero");
104 }
105 validate_point_on_curve(&pt, &a, &b, &p)?;
106 let result = scalar_multiply(&pt, &n, &a, &p)?;
107 println!("{}", format_point(&result));
108 }
109 EcAction::Order { point, a, b, p } => {
110 let pt = parse_point(&point)?;
111 let a = a.parse::<BigInt>().context("Invalid number for a")?;
112 let b = b.parse::<BigInt>().context("Invalid number for b")?;
113 let p = p.parse::<BigInt>().context("Invalid number for p")?;
114 if p.is_zero() {
115 anyhow::bail!("Modulus p must be non-zero");
116 }
117 validate_point_on_curve(&pt, &a, &b, &p)?;
118 let ord = point_order(&pt, &a, &p)?;
119 println!("{}", ord);
120 }
121 EcAction::PohligHellman {
122 generator,
123 target,
124 a,
125 b,
126 p,
127 order,
128 } => {
129 let generator_pt = parse_point(&generator)?;
130 let tgt = parse_point(&target)?;
131 let a = a.parse::<BigInt>().context("Invalid number for a")?;
132 let b = b.parse::<BigInt>().context("Invalid number for b")?;
133 let p = p.parse::<BigInt>().context("Invalid number for p")?;
134 if p.is_zero() {
135 anyhow::bail!("Modulus p must be non-zero");
136 }
137 let order = order
138 .parse::<BigUint>()
139 .context("Invalid number for order")?;
140 validate_point_on_curve(&generator_pt, &a, &b, &p)?;
141 validate_point_on_curve(&tgt, &a, &b, &p)?;
142 let n = pohlig_hellman(&generator_pt, &tgt, &a, &p, &order)?;
143 println!("{}", n);
144 }
145 }
146 Ok(())
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum ECPoint {
152 Infinity,
153 Affine { x: BigInt, y: BigInt },
154}
155
156pub fn parse_point(s: &str) -> Result<ECPoint> {
158 let s = s.trim();
159 if s.eq_ignore_ascii_case("inf") || s.eq_ignore_ascii_case("infinity") || s == "O" {
160 return Ok(ECPoint::Infinity);
161 }
162 let parts: Vec<&str> = s.split(',').collect();
163 if parts.len() != 2 {
164 anyhow::bail!("Point must be 'x,y' or 'inf', got '{}'", s);
165 }
166 let x = parts[0]
167 .trim()
168 .parse::<BigInt>()
169 .context("Invalid x coordinate")?;
170 let y = parts[1]
171 .trim()
172 .parse::<BigInt>()
173 .context("Invalid y coordinate")?;
174 Ok(ECPoint::Affine { x, y })
175}
176
177pub fn format_point(point: &ECPoint) -> String {
179 match point {
180 ECPoint::Infinity => "inf".to_string(),
181 ECPoint::Affine { x, y } => format!("({}, {})", x, y),
182 }
183}
184
185pub fn is_on_curve(point: &ECPoint, a: &BigInt, b: &BigInt, p: &BigInt) -> bool {
187 if p.is_zero() {
188 return false;
189 }
190 match point {
191 ECPoint::Infinity => true,
192 ECPoint::Affine { x, y } => {
193 let lhs = modp(&(y * y), p);
194 let rhs = modp(&(x * x * x + a * x + b), p);
195 lhs == rhs
196 }
197 }
198}
199
200fn validate_point_on_curve(point: &ECPoint, a: &BigInt, b: &BigInt, p: &BigInt) -> Result<()> {
201 if !is_on_curve(point, a, b, p) {
202 anyhow::bail!(
203 "Point {} is not on the curve y^2 = x^3 + {}x + {} (mod {})",
204 format_point(point),
205 a,
206 b,
207 p
208 );
209 }
210 Ok(())
211}
212
213fn mod_inverse(a: &BigInt, p: &BigInt) -> Result<BigInt> {
215 let ext = a.extended_gcd(p);
216 if ext.gcd != BigInt::one() {
217 anyhow::bail!("Modular inverse does not exist");
218 }
219 Ok(((ext.x % p) + p) % p)
220}
221
222fn modp(a: &BigInt, p: &BigInt) -> BigInt {
224 ((a % p) + p) % p
225}
226
227pub fn point_add(p1: &ECPoint, p2: &ECPoint, a: &BigInt, p: &BigInt) -> Result<ECPoint> {
230 if p.is_zero() {
231 anyhow::bail!("Modulus p must be non-zero");
232 }
233 match (p1, p2) {
234 (ECPoint::Infinity, _) => Ok(p2.clone()),
235 (_, ECPoint::Infinity) => Ok(p1.clone()),
236 (ECPoint::Affine { x: x1, y: y1 }, ECPoint::Affine { x: x2, y: y2 }) => {
237 let x1m = modp(x1, p);
238 let y1m = modp(y1, p);
239 let x2m = modp(x2, p);
240 let y2m = modp(y2, p);
241
242 if x1m == x2m && modp(&(&y1m + &y2m), p).is_zero() {
244 return Ok(ECPoint::Infinity);
245 }
246
247 let lambda = if x1m == x2m && y1m == y2m {
248 let num = modp(&(BigInt::from(3) * &x1m * &x1m + a), p);
250 let den = modp(&(BigInt::from(2) * &y1m), p);
251 if den.is_zero() {
252 return Ok(ECPoint::Infinity);
253 }
254 let den_inv = mod_inverse(&den, p)?;
255 modp(&(num * den_inv), p)
256 } else {
257 let num = modp(&(&y2m - &y1m), p);
259 let den = modp(&(&x2m - &x1m), p);
260 let den_inv = mod_inverse(&den, p)?;
261 modp(&(num * den_inv), p)
262 };
263
264 let x3 = modp(&(&lambda * &lambda - &x1m - &x2m), p);
265 let y3 = modp(&(&lambda * &(&x1m - &x3) - &y1m), p);
266 Ok(ECPoint::Affine { x: x3, y: y3 })
267 }
268 }
269}
270
271pub fn scalar_multiply(point: &ECPoint, n: &BigUint, a: &BigInt, p: &BigInt) -> Result<ECPoint> {
273 if p.is_zero() {
274 anyhow::bail!("Modulus p must be non-zero");
275 }
276 if n.is_zero() {
277 return Ok(ECPoint::Infinity);
278 }
279 if *point == ECPoint::Infinity {
280 return Ok(ECPoint::Infinity);
281 }
282
283 let mut result = ECPoint::Infinity;
284 let bit_len = n.bits();
285 for i in (0..bit_len).rev() {
286 result = point_add(&result, &result, a, p)?;
287 if n.bit(i) {
288 result = point_add(&result, point, a, p)?;
289 }
290 }
291
292 Ok(result)
293}
294
295pub fn point_order(point: &ECPoint, a: &BigInt, p: &BigInt) -> Result<BigUint> {
298 if p.is_zero() {
299 anyhow::bail!("Modulus p must be non-zero");
300 }
301 if *point == ECPoint::Infinity {
302 return Ok(BigUint::one());
303 }
304
305 let p_uint: BigUint = p.to_biguint().context("Prime p must be positive")?;
306 let max_order = &p_uint + BigUint::one() + BigUint::from(2u32) * p_uint.sqrt();
308
309 let mut current = point.clone();
310 let mut n = BigUint::one();
311 let mut iterations = 0u64;
312
313 loop {
314 if n > max_order {
315 anyhow::bail!("Could not find point order within Hasse bound");
316 }
317 if iterations > MAX_POINT_ORDER_ITERATIONS {
318 anyhow::bail!(
319 "Point order calculation limit exceeded (limit: {})",
320 MAX_POINT_ORDER_ITERATIONS
321 );
322 }
323 if current == ECPoint::Infinity {
324 return Ok(n);
325 }
326 current = point_add(¤t, point, a, p)?;
327 n += BigUint::one();
328 iterations += 1;
329 }
330}
331
332fn bsgs_ecdlp(
335 generator: &ECPoint,
336 target: &ECPoint,
337 a: &BigInt,
338 p: &BigInt,
339 order: &BigUint,
340) -> Result<BigUint> {
341 let m_val = order.sqrt() + BigUint::one();
342
343 if m_val > BigUint::from(MAX_BSGS_ITERATIONS) {
344 anyhow::bail!(
345 "Order too large for BSGS algorithm (limit: sqrt(order) <= {})",
346 MAX_BSGS_ITERATIONS
347 );
348 }
349
350 let mut table: HashMap<String, BigUint> = HashMap::new();
352 let mut baby = ECPoint::Infinity;
353 let mut j = BigUint::zero();
354 while j < m_val {
355 table.insert(format_point(&baby), j.clone());
356 baby = point_add(&baby, generator, a, p)?;
357 j += BigUint::one();
358 }
359
360 let mg = scalar_multiply(generator, &m_val, a, p)?;
363 let neg_mg = negate_point(&mg, p);
365
366 let mut gamma = target.clone();
367 let mut i = BigUint::zero();
368 while i < m_val {
369 if let Some(j_val) = table.get(&format_point(&gamma)) {
370 let k = (&i * &m_val + j_val) % order;
372 return Ok(k);
373 }
374 gamma = point_add(&gamma, &neg_mg, a, p)?;
375 i += BigUint::one();
376 }
377
378 anyhow::bail!("BSGS failed to find discrete log");
379}
380
381fn negate_point(point: &ECPoint, p: &BigInt) -> ECPoint {
383 match point {
384 ECPoint::Infinity => ECPoint::Infinity,
385 ECPoint::Affine { x, y } => ECPoint::Affine {
386 x: x.clone(),
387 y: modp(&(-y), p),
388 },
389 }
390}
391
392pub fn pohlig_hellman(
395 generator: &ECPoint,
396 target: &ECPoint,
397 a: &BigInt,
398 p: &BigInt,
399 order: &BigUint,
400) -> Result<BigUint> {
401 if p.is_zero() {
402 anyhow::bail!("Modulus p must be non-zero");
403 }
404 let factors = factor_biguint(order)?;
405
406 if factors.is_empty() {
407 anyhow::bail!("Order must be > 1");
408 }
409
410 let mut residues: Vec<BigUint> = Vec::new();
411 let mut moduli: Vec<BigUint> = Vec::new();
412
413 for (prime, exp) in &factors {
414 let prime_power = prime.pow(*exp);
415 let cofactor = order / &prime_power;
417
418 let gen_sub = scalar_multiply(generator, &cofactor, a, p)?;
420 let target_sub = scalar_multiply(target, &cofactor, a, p)?;
421
422 let sub_log = solve_prime_power_ecdlp(&gen_sub, &target_sub, a, p, prime, *exp)?;
425 residues.push(sub_log);
426 moduli.push(prime_power);
427 }
428
429 crt(&residues, &moduli)
431}
432
433fn solve_prime_power_ecdlp(
435 generator: &ECPoint,
436 target: &ECPoint,
437 a: &BigInt,
438 p: &BigInt,
439 prime: &BigUint,
440 exp: u32,
441) -> Result<BigUint> {
442 if exp == 1 {
443 return bsgs_ecdlp(generator, target, a, p, prime);
444 }
445
446 let prime_power = prime.pow(exp);
449 let mut k = BigUint::zero();
450 let mut remainder = target.clone();
451
452 let base_cofactor = prime.pow(exp - 1);
454 let g_base = scalar_multiply(generator, &base_cofactor, a, p)?;
455
456 for i in 0..exp {
457 let cofactor = prime.pow(exp - 1 - i);
459 let projected = scalar_multiply(&remainder, &cofactor, a, p)?;
461
462 let d_i = bsgs_ecdlp(&g_base, &projected, a, p, prime)?;
464
465 let contrib = &d_i * &prime.pow(i);
467 k = (&k + &contrib) % &prime_power;
468
469 let neg_k_gen = negate_point(&scalar_multiply(generator, &k, a, p)?, p);
471 remainder = point_add(target, &neg_k_gen, a, p)?;
472 }
473
474 Ok(k)
475}
476
477fn factor_biguint(n: &BigUint) -> Result<Vec<(BigUint, u32)>> {
480 Ok(primes::factorize_biguint(n.clone()))
482}
483
484fn crt(residues: &[BigUint], moduli: &[BigUint]) -> Result<BigUint> {
488 if residues.is_empty() {
489 anyhow::bail!("CRT requires at least one congruence");
490 }
491
492 let big_m: BigUint = moduli.iter().fold(BigUint::one(), |acc, m| acc * m);
493
494 let mut x = BigInt::zero();
495 let big_m_int = big_m.to_bigint().unwrap();
496
497 for (r_i, m_i) in residues.iter().zip(moduli.iter()) {
498 let mi_big = &big_m / m_i;
499 let mi_int = mi_big.to_bigint().unwrap();
500 let m_i_int = m_i.to_bigint().unwrap();
501 let r_i_int = r_i.to_bigint().unwrap();
502
503 let ext = mi_int.extended_gcd(&m_i_int);
504 if ext.gcd != BigInt::one() {
505 anyhow::bail!("Moduli must be pairwise coprime for CRT");
506 }
507 let yi = ext.x;
508
509 x = (x + r_i_int * mi_int * yi) % &big_m_int;
510 }
511
512 Ok(((x + &big_m_int) % &big_m_int).to_biguint().unwrap())
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518
519 #[test]
520 fn test_point_add_identity() {
521 let p = BigInt::from(23);
522 let a = BigInt::from(1);
523 let pt = ECPoint::Affine {
524 x: BigInt::from(0),
525 y: BigInt::from(1),
526 };
527 let result = point_add(&pt, &ECPoint::Infinity, &a, &p).unwrap();
528 assert_eq!(result, pt);
529 }
530
531 #[test]
532 fn test_parse_point_inf() {
533 let pt = parse_point("inf").unwrap();
534 assert_eq!(pt, ECPoint::Infinity);
535 }
536
537 #[test]
538 fn test_factor_biguint_large_prime() {
539 let n = BigUint::from(18446744073709551557u64);
542 let factors = factor_biguint(&n).unwrap();
543 assert_eq!(factors.len(), 1);
544 assert_eq!(factors[0].0, n);
545 assert_eq!(factors[0].1, 1);
546 }
547}