use num_bigint::BigUint;
use crate::finite_fields;
#[derive(Clone, PartialEq, Debug, )]
pub enum CurvePoint {
Coordinate(BigUint, BigUint),
Identity
}
#[derive(PartialEq, Debug)]
pub enum EllipticCurveError {
InvalidPoint(CurvePoint),
InvalidScalar(BigUint),
}
#[derive(PartialEq, Clone, Debug)]
pub struct EllipticCurve {
pub a: BigUint,
pub b: BigUint,
pub p: BigUint,
}
impl EllipticCurve {
pub fn add(&self, a: &CurvePoint, b: &CurvePoint) -> Result<CurvePoint, EllipticCurveError> {
if !self.is_on_curve(a) {
return Err(EllipticCurveError::InvalidPoint(a.clone()));
}
if !self.is_on_curve(b) {
return Err(EllipticCurveError::InvalidPoint(b.clone()));
}
if *a == *b {
return self.double(a);
}
match (a, b) {
(CurvePoint::Identity, _) => Ok(b.clone()),
(_, CurvePoint::Identity) => Ok(a.clone()),
(CurvePoint::Coordinate(x1, y1), CurvePoint::Coordinate(x2, y2)) => {
let y1_plus_y2 = finite_fields::add(y1, y2, &self.p).unwrap();
if x1 == x2 && y1_plus_y2 == BigUint::from(0u32) {
return Ok(CurvePoint::Identity);
}
let numerator = finite_fields::subtract(y2, y1, &self.p).unwrap();
let denominator = finite_fields::subtract(x2, x1, &self.p).unwrap();
let s = finite_fields::divide(&numerator, &denominator, &self.p).unwrap();
let (x3, y3) = self.compute_x3_y3(x1, y1, x2, &s);
Ok(CurvePoint::Coordinate(x3, y3))
}
}
}
pub fn double(&self, a: &CurvePoint) -> Result<CurvePoint, EllipticCurveError> {
if !self.is_on_curve(a) {
return Err(EllipticCurveError::InvalidPoint(a.clone()));
}
match a {
CurvePoint::Identity => Ok(CurvePoint::Identity),
CurvePoint::Coordinate(x1, y1) => {
if *y1 == BigUint::from(0u32) {
return Ok(CurvePoint::Identity);
}
let numerator = x1.modpow(&BigUint::from(2u32), &self.p);
let numerator =
finite_fields::multiplicate(&BigUint::from(3u32), &numerator, &self.p).unwrap();
let numerator = finite_fields::add(&self.a, &numerator, &self.p).unwrap();
let denominator = finite_fields::multiplicate(&BigUint::from(2u32), y1, &self.p).unwrap();
let s = finite_fields::divide(&numerator, &denominator, &self.p).unwrap();
let (x3, y3) = self.compute_x3_y3(x1, y1, x1, &s);
Ok(CurvePoint::Coordinate(x3, y3))
}
}
}
fn compute_x3_y3(
&self,
x1: &BigUint,
y1: &BigUint,
x2: &BigUint,
s: &BigUint,
) -> (BigUint, BigUint) {
let s2 = s.modpow(&BigUint::from(2u32), &self.p);
let x3 = finite_fields::subtract(&s2, x1, &self.p).unwrap();
let x3 = finite_fields::subtract(&x3, x2, &self.p).unwrap();
let y3 = finite_fields::subtract(x1, &x3, &self.p).unwrap();
let y3 = finite_fields::multiplicate(s, &y3, &self.p).unwrap();
let y3 = finite_fields::subtract(&y3, y1, &self.p).unwrap();
(x3, y3)
}
pub fn scalar_mul(&self, a: &CurvePoint, d: &BigUint) -> Result<CurvePoint, EllipticCurveError> {
if *d == BigUint::from(0u32) {
return Err(EllipticCurveError::InvalidScalar(d.clone()));
}
let mut t = a.clone();
for i in (0..(d.bits() - 1)).rev() {
t = self.double(&t)?;
if d.bit(i) {
t = self.add(&t, a)?;
}
}
Ok(t)
}
pub fn is_on_curve(&self, a: &CurvePoint) -> bool {
match a {
CurvePoint::Coordinate(x, y) => {
let y2 = y.modpow(&BigUint::from(2u32), &self.p);
let x3 = x.modpow(&BigUint::from(3u32), &self.p);
let a_x = finite_fields::multiplicate(&self.a, x, &self.p).unwrap();
let x3_plus_ax = finite_fields::add(&x3, &a_x, &self.p).unwrap();
y2 == finite_fields::add(&x3_plus_ax, &self.b, &self.p).unwrap()
}
CurvePoint::Identity => true, }
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_point_in_curve() {
let ec = EllipticCurve {
a: BigUint::from(2u32),
b: BigUint::from(2u32),
p: BigUint::from(17u32),
};
let p1 = CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let p3 = CurvePoint::Coordinate(BigUint::from(10u32), BigUint::from(6u32));
assert!(ec.is_on_curve(&p1));
assert!(ec.is_on_curve(&p2));
assert!(ec.is_on_curve(&p3));
let p4 = CurvePoint::Coordinate(BigUint::from(4u32), BigUint::from(1u32));
let p5 = CurvePoint::Coordinate(BigUint::from(1u32), BigUint::from(1u32));
let p6 = CurvePoint::Coordinate(BigUint::from(0u32), BigUint::from(1u32));
assert!(!ec.is_on_curve(&p4));
assert!(!ec.is_on_curve(&p5));
assert!(!ec.is_on_curve(&p6));
}
#[test]
fn test_point_addition() {
let ec = EllipticCurve {
a: BigUint::from(2u32),
b: BigUint::from(2u32),
p: BigUint::from(17u32),
};
let p1 = CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(10u32), BigUint::from(6u32)));
let res = ec.add(&p1, &p2);
assert_eq!(res, pr);
let res = ec.add(&p2, &p1);
assert_eq!(res, pr);
let p1 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32)));
let res = ec.add(&p1, &p2);
assert_eq!(res, pr);
let p1 = CurvePoint::Coordinate(BigUint::from(10u32), BigUint::from(6u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(3u32), BigUint::from(1u32)));
let res = ec.add(&p1, &p2);
assert_eq!(res, pr);
let p1 = CurvePoint::Coordinate(BigUint::from(16u32), BigUint::from(13u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(0u32), BigUint::from(6u32)));
let res = ec.add(&p1, &p2);
assert_eq!(res, pr);
let p1 = CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32));
let res = ec.add(&p1, &CurvePoint::Identity);
assert_eq!(res, Ok(p1.clone()));
let res = ec.add(&CurvePoint::Identity, &p1);
assert_eq!(res, Ok(p1.clone()));
let res = ec.add(&CurvePoint::Identity, &CurvePoint::Identity);
assert_eq!(res, Ok(CurvePoint::Identity));
let p1 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(16u32));
let p2 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let res = ec.add(&p1, &p2);
assert_eq!(res, Ok(CurvePoint::Identity));
let res = ec.add(&p2, &p1);
assert_eq!(res, Ok(CurvePoint::Identity));
}
#[test]
fn test_point_doubling() {
let ec = EllipticCurve {
a: BigUint::from(2u32),
b: BigUint::from(2u32),
p: BigUint::from(17u32),
};
let p1 = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32)));
let res = ec.double(&p1);
assert_eq!(res, pr);
let res = ec.double(&CurvePoint::Identity);
assert_eq!(res, Ok(CurvePoint::Identity));
}
#[test]
fn test_scalar_multiplication() {
let ec = EllipticCurve {
a: BigUint::from(2u32),
b: BigUint::from(2u32),
p: BigUint::from(17u32),
};
let a = CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(1u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(3u32)));
let res = ec.scalar_mul(&a, &BigUint::from(2u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Coordinate(BigUint::from(7u32), BigUint::from(11u32)));
let res = ec.scalar_mul(&a, &BigUint::from(10u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Coordinate(BigUint::from(3u32), BigUint::from(16u32)));
let res = ec.scalar_mul(&a, &BigUint::from(15u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Coordinate(BigUint::from(10u32), BigUint::from(11u32)));
let res = ec.scalar_mul(&a, &BigUint::from(16u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Coordinate(BigUint::from(6u32), BigUint::from(14u32)));
let res = ec.scalar_mul(&a, &BigUint::from(17u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Coordinate(BigUint::from(5u32), BigUint::from(16u32)));
let res = ec.scalar_mul(&a, &BigUint::from(18u32));
assert_eq!(res, pr);
let pr = Ok(CurvePoint::Identity);
let res = ec.scalar_mul(&a, &BigUint::from(19u32));
assert_eq!(res, pr);
let p1 = CurvePoint::Coordinate(BigUint::from(10u32), BigUint::from(6u32));
let pr = Ok(CurvePoint::Coordinate(BigUint::from(16u32), BigUint::from(13u32)));
let res = ec.double(&p1);
assert_eq!(res, pr);
}
#[test]
fn test_ec_secp256k1() {
let p = BigUint::parse_bytes(
b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
16,
)
.expect("could not convert p");
let n = BigUint::parse_bytes(
b"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141",
16,
)
.expect("could not convert n");
let gx = BigUint::parse_bytes(
b"79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798",
16,
)
.expect("could not convert gx");
let gy = BigUint::parse_bytes(
b"483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8",
16,
)
.expect("could not convert gy");
let ec = EllipticCurve {
a: BigUint::from(0u32),
b: BigUint::from(7u32),
p,
};
let g = CurvePoint::Coordinate(gx, gy);
let res = ec.scalar_mul(&g, &n); assert_eq!(res, Ok(CurvePoint::Identity));
let p = ec.scalar_mul(&g, &BigUint::from(1201u32)).unwrap();
let res = ec.scalar_mul(&p, &n); assert_eq!(res, Ok(CurvePoint::Identity));
}
}