Skip to content

Commit

Permalink
feat: implement finite field arithmetic over elliptic curves
Browse files Browse the repository at this point in the history
  • Loading branch information
holaguz committed Nov 14, 2024
1 parent 45f9025 commit 6736423
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 594 deletions.
317 changes: 162 additions & 155 deletions src/ec.rs
Original file line number Diff line number Diff line change
@@ -1,209 +1,216 @@
#![allow(dead_code)]

use std::ops;
use std::ops::{Add, Div, Mul, Sub};

use num_bigint::BigUint;

pub trait FieldArithmetic:
Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
// + Rem<Output = Self>
+ Clone
+ PartialEq
+ From<u32>
{
}

// An elliptic curve defined by the equation y**2 = x**3 + Ax + B
#[derive(Debug, Eq, PartialEq)]
pub struct ECurve<const A: i32, const B: i32>;
impl FieldArithmetic for BigUint {}
impl FieldArithmetic for u32 {}
impl FieldArithmetic for u64 {}
impl FieldArithmetic for u128 {}

// Coordinates of a point on the curve
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct Coordinates {
pub x: i32,
pub y: i32,
pub struct Coordinates<T> {
pub x: T,
pub y: T,
}

// A point contained in the elliptic curve defined by A and B
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct EPoint<const A: i32, const B: i32> {
// None represents the point at infinity
p: Option<Coordinates>,
pub enum PointType<T> {
Invalid,
Infinity,
Point(Coordinates<T>),
}

impl<const A: i32, const B: i32> EPoint<A, B> {
// Constructor for the point at infinity
pub const fn infinity() -> Self {
Self { p: None }
}

// Constructor for a regular point
pub const fn new(x: i32, y: i32) -> Self {
Self {
p: Some(Coordinates { x, y }),
}
}

// Check if this is the point at infinity
pub const fn is_infinity(&self) -> bool {
self.p.is_none()
}

// Get the coordinates, or None for point at infinity
pub const fn coords(&self) -> Option<Coordinates> {
self.p
}
// An elliptic curve defined by the equation y**2 = x**3 + Ax + B
#[derive(Debug, Eq, PartialEq)]
pub struct EllipticCurve<T> {
a: T,
b: T,
}

impl<const A: i32, const B: i32> ECurve<A, B> {
pub const fn new() -> Self {
ECurve
}
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub struct ECurvePoint<'a, T> {
curve: &'a EllipticCurve<T>,
p: PointType<T>,
}

pub const fn point_at(&self, x: i32, y: i32) -> Option<EPoint<A, B>> {
if self.contains(x, y) {
Some(EPoint {
p: Some(Coordinates { x, y }),
})
} else {
None
impl<'a, T> EllipticCurve<T>
where
T: FieldArithmetic,
{
pub fn new(a: impl Into<T>, b: impl Into<T>) -> Self {
let a = a.into();
let b = b.into();
Self { a, b }
}

pub fn point_at(&'a self, x: impl Into<T>, y: impl Into<T>) -> ECurvePoint<'a, T> {
let x = x.into();
let y = y.into();
match self.contains(&x, &y) {
false => ECurvePoint::<'a, T> {
curve: self,
p: PointType::Invalid,
},
true => ECurvePoint::<'a, T> {
curve: self,
p: PointType::Point(Coordinates { x, y }),
},
}
}

pub const fn point_at_ifty(&self) -> EPoint<A, B> {
EPoint::<A, B>::infinity()
}

pub const fn a(&self) -> i32 {
A
}

pub const fn b(&self) -> i32 {
B
pub fn infinity(&'a self) -> ECurvePoint<'a, T> {
ECurvePoint::<'a, T> {
curve: self,
p: PointType::Infinity,
}
}

pub const fn contains(&self, x: i32, y: i32) -> bool {
y * y == x * x * x + A * x + B
pub fn contains(&self, x: &T, y: &T) -> bool {
let lhs = y.clone() * y.clone();
let x3 = x.clone() * x.clone() * x.clone();
let rhs = x3 + self.a.clone() * x.clone() + self.b.clone();
return lhs == rhs;
}
}

impl<const A: i32, const B: i32> ops::Add for EPoint<A, B> {
type Output = Self;

fn add(self, other: Self) -> Self {
// ECurve addition is performed by intersecting a line between the two
// points to add. There are three main cases: the intersects the curve
// at either one, two or three points.
// For two intersections, the line is either vertical (one point is at infinity) or tangent to the curve.

// Handle common cases first:
// 1. Either point is an infinity. This point is the identity point, i.e., A + Ifty = A

if self.is_infinity() {
return other;
} else if other.is_infinity() {
return self;
}

// At this point (lol) we know that neither point is at infinity.
let self_coords = self.coords().unwrap();
let other_coords = other.coords().unwrap();
impl<'a, T> Add for ECurvePoint<'a, T>
where
T: FieldArithmetic,
{
type Output = ECurvePoint<'a, T>;

fn add(self, rhs: Self) -> Self::Output {
// Points must be from the same curve
assert!(
self.curve == rhs.curve,
"Cannot add points from different curves"
);

let (p, rhs) = match (&self.p, &rhs.p) {
// Infinity is the additive identity
(PointType::Infinity, _) => return rhs.clone(),
(_, PointType::Infinity) => return self,

// Invalid + anything = Invalid
(PointType::Invalid, _) | (_, PointType::Invalid) => {
return ECurvePoint {
curve: self.curve,
p: PointType::Invalid,
}
}
(PointType::Point(p1), PointType::Point(p2)) => (p1, p2),
};

// 2. Points are additive inverses. The two points have the same x coord but different y.
if self_coords.x == other_coords.x && self_coords.y != other_coords.y {
return EPoint::<A, B>::infinity();
if p.x == rhs.x && p.y != rhs.y {
return ECurvePoint {
curve: self.curve,
p: PointType::Infinity,
};
}

// 3. Either the points are the same point (P1 = P2) or are different (P1 != P2)
// The only difference between the two cases is how we calculate the slope. For P1 == P2,
// the line is tangent to the curve. For P1 != P2 the line intersects the curve at both
// points. Furthermore, when P1 == P2 and P1.y == 0 the tangent line is vertical and the
// resulting point lies at infinity.
let s = match self == other {
let s = match p == rhs {
// 3.1. Points are the same point.
true => match self_coords.y {
true => {
// Special case: If the y coord is 0, the tangent line is vertical since the elliptic
// curve is symmetrical wrt. the x axis. This results on a point on the infinity.
0 => return EPoint::<A, B>::infinity(),
_ => (3 * self_coords.x * self_coords.x + A) / (2 * self_coords.y),
},
// 3.2. Points are different.
// The denominator cannot be zero since if P1.x != P2.x is handled at (2) and
// P1.x == P2.x is handled at 3.1.
false => (other_coords.y - self_coords.y) / (other_coords.x - self_coords.x),
if p.y == 0u32.into() {
return ECurvePoint {
curve: self.curve,
p: PointType::Infinity,
};
}
let three: T = 3u32.into();
let two: T = 2u32.into();
(three * p.x.clone() * p.x.clone() + self.curve.a.clone()) / (two * p.y.clone())
}
false => (rhs.y.clone() - p.y.clone()) / (rhs.x.clone() - p.x.clone()),
};

let x = s * s - self_coords.x - other_coords.x;
let y = s * (self_coords.x - x) - self_coords.y;
let x = s.clone() * s.clone() - p.x.clone() - rhs.x.clone();
let y = s * (p.x.clone() - x.clone()) - p.y.clone();

return EPoint::<A, B>::new(x, y);
ECurvePoint {
curve: self.curve,
p: PointType::Point(Coordinates { x, y }),
}
}
}

pub const SECP256K1: ECurve<0, 7> = ECurve::new();

#[cfg(test)]
mod tests {
use super::*;
use crate::finite_field::{FieldMod, FiniteField};

// This is the example elliptic curve used in the book.
const TEST_EC: ECurve<5, 7> = ECurve::<5, 7>::new();

#[test]
fn test_ec_new() {
_ = ECurve::<0, 7>::new();
}

#[test]
fn test_contains() {
let contained = TEST_EC.contains(-1, 1);
let not_contained = TEST_EC.contains(-1, -2);

assert!(contained);
assert!(!not_contained);
}

#[test]
fn test_point_at() {
let exists = TEST_EC.point_at(-1, 1);
let not_exists = TEST_EC.point_at(-1, -2);

assert!(exists.is_some());
assert!(not_exists.is_none());
}

#[test]
fn test_add_ifty() {
let a = TEST_EC.point_at(-1, 1).unwrap();
let ifty = TEST_EC.point_at_ifty();
mod finite_field {
use super::*;

assert_eq!(a + ifty, a);
assert_eq!(ifty + a, a);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Field223Mod;

#[test]
fn test_add_ident() {
let a = TEST_EC.point_at(-1, 1).unwrap();
let b = TEST_EC.point_at(-1, -1).unwrap();
impl FieldMod for Field223Mod {
fn modulus() -> BigUint {
223u32.into()
}
}

assert_eq!(a + b, TEST_EC.point_at_ifty());
assert_eq!(b + a, TEST_EC.point_at_ifty());
}
type Field223 = FiniteField<Field223Mod>;

#[test]
fn test_add_same() {
let a = TEST_EC.point_at(-1, -1).unwrap();
let res = TEST_EC.point_at(18, 77).unwrap();
assert_eq!(res, a + a);
fn test_curve() -> EllipticCurve<Field223> {
EllipticCurve::new(0u32, 7u32)
}

let a = TEST_EC.point_at(-1, 1).unwrap();
let res = TEST_EC.point_at(18, -77).unwrap();
assert_eq!(res, a + a);
}
#[test]
fn test_contains() {
let c = test_curve();

let valid: Vec<(Field223, Field223)> = vec![
(192u32.into(), 105u32.into()),
(17u32.into(), 56u32.into()),
(1u32.into(), 193u32.into()),
];
let invalid: Vec<(Field223, Field223)> =
vec![(200u32.into(), 119u32.into()), (42u32.into(), 99u32.into())];

valid.iter().for_each(|(x, y)| {
assert!(c.contains(x, y));
});

invalid.iter().for_each(|(x, y)| {
assert!(!c.contains(x, y));
});
}

#[test]
fn test_add_same_at_y0() {
let ec = ECurve::<1, 10>::new();
let p = ec.point_at(-2, 0);
#[test]
fn test_add() {
let c = test_curve();
let a = c.point_at(192u32, 105u32);
let b = c.point_at(17u32, 56u32);

assert_eq!(ec.point_at_ifty(), p.unwrap() + p.unwrap());
}
let result = c.point_at(170u32, 142u32);

#[test]
fn test_add() {
let a = TEST_EC.point_at(-1, -1).unwrap();
let b = TEST_EC.point_at(2, 5).unwrap();
let res = TEST_EC.point_at(3, -7).unwrap();
assert_eq!(res, a + b);
assert_eq!(res, b + a);
assert_eq!(a.clone() + b.clone(), result);
assert_eq!(b + a, result);
}
}
}
Loading

0 comments on commit 6736423

Please sign in to comment.