Skip to main content

happy_cracking/crypto/
ec.rs

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
10// Safety limit for BSGS algorithm (approx 4 million entries in hash map).
11// Prevents OOM when users provide large prime orders.
12// 2^22 = 4,194,304.
13const MAX_BSGS_ITERATIONS: u64 = 1 << 22;
14
15// Safety limit for brute force point order calculation.
16const 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// A point on an elliptic curve, or the point at infinity.
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum ECPoint {
152    Infinity,
153    Affine { x: BigInt, y: BigInt },
154}
155
156// Parse a point from a string. "inf" for the point at infinity, "x,y" for an affine point.
157pub 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
177// Format a point for display.
178pub 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
185// Check whether a point lies on the curve y^2 = x^3 + ax + b (mod p).
186pub 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
213// Modular inverse for BigInt. Returns a^-1 mod p.
214fn 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
222// Reduce a BigInt modulo p into the range [0, p).
223fn modp(a: &BigInt, p: &BigInt) -> BigInt {
224    ((a % p) + p) % p
225}
226
227// Add two points on the elliptic curve y^2 = x^3 + ax + b (mod p).
228// Parameter b is not needed for point addition.
229pub 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 P = -Q (same x, opposite y), result is the point at infinity
243            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                // Point doubling: lambda = (3*x1^2 + a) / (2*y1) mod p
249                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                // Point addition: lambda = (y2 - y1) / (x2 - x1) mod p
258                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
271// Scalar multiplication using double-and-add (left-to-right binary method).
272pub 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
295// Find the order of a point on the curve by brute force.
296// Iterates from 1 until n*P = O. Only practical for small orders.
297pub 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    // Hasse's theorem: group order is at most p + 1 + 2*sqrt(p)
307    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(&current, point, a, p)?;
327        n += BigUint::one();
328        iterations += 1;
329    }
330}
331
332// Baby-step Giant-step algorithm for ECDLP in a subgroup of known prime order.
333// Solves Q = k*P for k, where P has order `order`.
334fn 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    // Baby step: store j -> j*G for j in [0, m)
351    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    // Giant step: compute Q - i*m*G for i in [0, m)
361    // m*G precomputed
362    let mg = scalar_multiply(generator, &m_val, a, p)?;
363    // Negate m*G for subtraction
364    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            // k = i*m + j mod order
371            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
381// Negate a point: -(x, y) = (x, -y mod p).
382fn 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
392// Pohlig-Hellman attack for solving ECDLP when the group order is smooth.
393// Given Q = n*P with P having the given order, finds n.
394pub 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        // Compute cofactor = order / prime_power
416        let cofactor = order / &prime_power;
417
418        // Project to subgroup of order prime_power
419        let gen_sub = scalar_multiply(generator, &cofactor, a, p)?;
420        let target_sub = scalar_multiply(target, &cofactor, a, p)?;
421
422        // Solve in subgroup of order prime^exp
423        // For prime powers, solve digit by digit
424        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    // Combine using CRT
430    crt(&residues, &moduli)
431}
432
433// Solve ECDLP in a subgroup of order prime^exp using successive refinement.
434fn 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    // For prime^exp, solve one digit at a time.
447    // k = d0 + d1*prime + d2*prime^2 + ... + d_{exp-1}*prime^{exp-1}
448    let prime_power = prime.pow(exp);
449    let mut k = BigUint::zero();
450    let mut remainder = target.clone();
451
452    // g_base = (prime^(exp-1)) * gen, which has order prime
453    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        // cofactor = prime^(exp-1-i)
458        let cofactor = prime.pow(exp - 1 - i);
459        // Project remainder to subgroup of order prime
460        let projected = scalar_multiply(&remainder, &cofactor, a, p)?;
461
462        // Solve projected = d_i * g_base in subgroup of order prime
463        let d_i = bsgs_ecdlp(&g_base, &projected, a, p, prime)?;
464
465        // k += d_i * prime^i
466        let contrib = &d_i * &prime.pow(i);
467        k = (&k + &contrib) % &prime_power;
468
469        // Update remainder: remainder = target - k*gen
470        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
477// Factor a BigUint into prime factors with exponents.
478// Uses recursive Pollard's Rho algorithm.
479fn factor_biguint(n: &BigUint) -> Result<Vec<(BigUint, u32)>> {
480    // Delegate to the comprehensive implementation in primes module
481    Ok(primes::factorize_biguint(n.clone()))
482}
483
484// Chinese Remainder Theorem for BigUint.
485// Given residues[i] and pairwise coprime moduli[i], finds x such that
486// x ≡ residues[i] (mod moduli[i]) for all i.
487fn 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        // Test with a large 64-bit prime: 18446744073709551557 (largest u64 prime)
540        // This would take very long with trial division but should be instant with Pollard's Rho.
541        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}