use rug::Rational;
use super::super::risch::poly_rde::{degree, trim, QPoly};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EllipticCurve {
pub a: Rational,
pub b: Rational,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Point {
Infinity,
Affine(Rational, Rational),
}
impl EllipticCurve {
pub fn new(a: Rational, b: Rational) -> Self {
EllipticCurve { a, b }
}
pub fn discriminant(&self) -> Rational {
let a3 = self.a.clone() * &self.a * &self.a;
let b2 = self.b.clone() * &self.b;
Rational::from(-16) * (Rational::from(4) * a3 + Rational::from(27) * b2)
}
pub fn is_smooth(&self) -> bool {
self.discriminant() != 0
}
pub fn contains(&self, p: &Point) -> bool {
match p {
Point::Infinity => true,
Point::Affine(x, y) => {
let lhs = y.clone() * y;
let rhs = x.clone() * x * x + self.a.clone() * x + &self.b;
lhs == rhs
}
}
}
pub fn neg(&self, p: &Point) -> Point {
match p {
Point::Infinity => Point::Infinity,
Point::Affine(x, y) => Point::Affine(x.clone(), -y.clone()),
}
}
pub fn add(&self, p: &Point, q: &Point) -> Point {
match (p, q) {
(Point::Infinity, _) => q.clone(),
(_, Point::Infinity) => p.clone(),
(Point::Affine(x1, y1), Point::Affine(x2, y2)) => {
if x1 == x2 && (y1.clone() + y2) == 0 {
return Point::Infinity; }
let lambda = if x1 == x2 {
let num = Rational::from(3) * x1.clone() * x1 + &self.a;
let den = Rational::from(2) * y1.clone();
num / den
} else {
(y2.clone() - y1) / (x2.clone() - x1)
};
let x3 = lambda.clone() * &lambda - x1 - x2;
let y3 = lambda * (x1.clone() - &x3) - y1;
Point::Affine(x3, y3)
}
}
}
pub fn mul(&self, mut m: u64, p: &Point) -> Point {
let mut acc = Point::Infinity;
let mut base = p.clone();
while m > 0 {
if m & 1 == 1 {
acc = self.add(&acc, &base);
}
base = self.add(&base, &base);
m >>= 1;
}
acc
}
pub fn order(&self, p: &Point) -> Option<u32> {
let mut cur = p.clone();
for m in 1..=12u32 {
if cur == Point::Infinity {
return Some(m);
}
cur = self.add(&cur, p);
}
None
}
}
#[allow(clippy::type_complexity)] pub fn short_weierstrass(
c: &QPoly,
) -> Option<(
EllipticCurve,
impl Fn(&Rational, &Rational) -> (Rational, Rational),
)> {
let c = trim(c.clone());
if degree(&c) != 3 {
return None;
}
let c0 = c[0].clone();
let c1 = c[1].clone();
let c2 = c[2].clone();
let c3 = c[3].clone();
if c3 == 0 {
return None;
}
let b2 = c2.clone();
let b1 = c3.clone() * &c1;
let b0 = c3.clone() * &c3 * &c0;
let p = b1.clone() - b2.clone() * &b2 / Rational::from(3);
let q = b0 - b1 * &b2 / Rational::from(3)
+ Rational::from(2) * b2.clone() * &b2 * &b2 / Rational::from(27);
let curve = EllipticCurve::new(p, q);
if !curve.is_smooth() {
return None;
}
let c3m = c3.clone();
let c2m = c2;
let map = move |x: &Rational, y: &Rational| -> (Rational, Rational) {
let big_x = c3m.clone() * x + c2m.clone() / Rational::from(3);
let big_y = c3m.clone() * y;
(big_x, big_y)
};
Some((curve, map))
}
#[cfg(test)]
mod tests {
use super::*;
fn r(n: i64) -> Rational {
Rational::from(n)
}
fn pt(x: i64, y: i64) -> Point {
Point::Affine(r(x), r(y))
}
#[test]
fn torsion_z6() {
let e = EllipticCurve::new(r(0), r(1));
assert!(e.is_smooth());
assert!(e.contains(&pt(2, 3)) && e.contains(&pt(0, 1)) && e.contains(&pt(-1, 0)));
assert_eq!(e.order(&Point::Infinity), Some(1));
assert_eq!(e.order(&pt(-1, 0)), Some(2));
assert_eq!(e.order(&pt(0, 1)), Some(3));
assert_eq!(e.order(&pt(2, 3)), Some(6));
assert_eq!(e.mul(6, &pt(2, 3)), Point::Infinity);
}
#[test]
fn full_two_torsion() {
let e = EllipticCurve::new(r(-1), r(0));
for p in [pt(0, 0), pt(1, 0), pt(-1, 0)] {
assert!(e.contains(&p));
assert_eq!(e.order(&p), Some(2));
}
assert_eq!(e.add(&pt(0, 0), &pt(1, 0)), pt(-1, 0));
}
#[test]
fn infinite_order() {
let e = EllipticCurve::new(r(0), r(-2));
assert!(e.contains(&pt(3, 5))); assert_eq!(e.order(&pt(3, 5)), None);
}
#[test]
fn group_axioms() {
let e = EllipticCurve::new(r(-1), r(0));
let p = pt(0, 0);
assert_eq!(e.add(&p, &e.neg(&p)), Point::Infinity);
assert_eq!(e.add(&p, &Point::Infinity), p);
}
#[test]
fn weierstrass_reduction() {
let c = vec![r(1), r(0), r(0), r(1)];
let (e, map) = short_weierstrass(&c).expect("cubic");
assert_eq!(e, EllipticCurve::new(r(0), r(1)));
let (xx, yy) = map(&r(2), &r(3));
assert!(e.contains(&Point::Affine(xx, yy)));
let c2 = vec![r(1), r(0), r(3), r(2)];
let (e2, map2) = short_weierstrass(&c2).expect("cubic");
assert!(e2.is_smooth());
let (xx, yy) = map2(&r(0), &r(1));
assert!(e2.contains(&Point::Affine(xx, yy)));
}
}