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))
}
pub(super) fn quartic_to_cubic(q: &QPoly, r: &Rational) -> Option<QPoly> {
let q = trim(q.clone());
if degree(&q) != 4 {
return None;
}
let mut c = vec![Rational::from(0); 4];
c[3] = q[4].clone();
c[2] = q[3].clone() + r.clone() * &c[3];
c[1] = q[2].clone() + r.clone() * &c[2];
c[0] = q[1].clone() + r.clone() * &c[1];
if q[0].clone() + r.clone() * &c[0] != 0 {
return None; }
let lin = vec![Rational::from(1), r.clone()]; let mut pw = vec![Rational::from(1)]; let mut big_c = vec![Rational::from(0); 4];
for (i, ci) in c.iter().enumerate() {
for (j, pj) in pw.iter().enumerate() {
let k = j + (3 - i);
if k < big_c.len() {
big_c[k] += ci.clone() * pj;
}
}
if i < 3 {
pw = poly_mul_small(&pw, &lin);
}
}
Some(big_c)
}
#[allow(clippy::type_complexity)]
pub fn weierstrass_from_quartic(
q: &QPoly,
r: &Rational,
) -> Option<(
EllipticCurve,
impl Fn(&Rational, &Rational) -> (Rational, Rational),
)> {
let big_c = quartic_to_cubic(q, r)?;
let (e, map_c) = short_weierstrass(&big_c)?;
let rr = r.clone();
let map = move |x: &Rational, y: &Rational| -> (Rational, Rational) {
let d = x.clone() - &rr;
let u = Rational::from(1) / d.clone();
let yy = y.clone() / (d.clone() * &d);
map_c(&u, &yy)
};
Some((e, map))
}
#[derive(Clone, Debug)]
pub(super) struct QuarticPointModel {
pub c: QPoly,
pub x0: Rational,
pub p: Rational,
pub b: Rational,
pub a3: Rational,
pub a4: Rational,
}
impl QuarticPointModel {
pub fn zw(&self, x: &Rational, y: &Rational) -> Option<(Rational, Rational)> {
let xt = x.clone() - &self.x0;
if xt == 0 {
return None;
}
let z = (y.clone() - &self.p - self.b.clone() * &xt) / (xt.clone() * &xt);
let w = Rational::from(2) * (z.clone() * &z - &self.a4) * &xt - self.a3.clone()
+ Rational::from(2) * &self.b * &z;
Some((z, w))
}
}
pub(super) fn quartic_point_model(
q: &QPoly,
x0: &Rational,
y0: &Rational,
) -> Option<QuarticPointModel> {
let q = trim(q.clone());
if degree(&q) != 4 || *y0 == 0 {
return None;
}
let mut qt = vec![Rational::from(0); 5];
let mut pw = vec![Rational::from(1)]; let shift = vec![x0.clone(), Rational::from(1)]; for qi in q.iter() {
for (j, pj) in pw.iter().enumerate() {
qt[j] += qi.clone() * pj;
}
pw = poly_mul_small(&pw, &shift);
pw.truncate(5);
}
let a0 = qt[0].clone();
let a1 = qt[1].clone();
let a2 = qt[2].clone();
let a3 = qt[3].clone();
let a4 = qt[4].clone();
if a0 != y0.clone() * y0 {
return None; }
let p = y0.clone();
let b = a1.clone() / (Rational::from(2) * &p);
let a2p = a2.clone() - b.clone() * &b;
let c = vec![
a3.clone() * &a3 - Rational::from(4) * &a4 * &a2p,
Rational::from(8) * &p * &a4 - Rational::from(4) * &b * &a3,
Rational::from(4) * &a2,
Rational::from(-8) * &p,
];
Some(QuarticPointModel {
c,
x0: x0.clone(),
p,
b,
a3,
a4,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EllFactor {
Vertical(Rational),
Line(Rational, Rational),
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct EllipticFunction {
pub num: Vec<EllFactor>,
pub den: Vec<EllFactor>,
}
impl EllipticCurve {
pub fn miller_function(&self, p: &Point, m: u32) -> Option<EllipticFunction> {
if m == 0 {
return Some(EllipticFunction::default());
}
let bits: Vec<bool> = (0..32).rev().map(|i| (m >> i) & 1 == 1).collect();
let first = bits.iter().position(|&b| b)?; let mut f = EllipticFunction::default();
let mut t = p.clone();
for &bit in &bits[first + 1..] {
f = f.squared();
let (g, two_t) = self.double_factor(&t);
f.compose(g);
t = two_t;
if bit {
let (g, tp) = self.add_factor(&t, p);
f.compose(g);
t = tp;
}
}
f.cancel();
Some(f)
}
pub fn general_miller(&self, divisor: &[(Point, i64)]) -> Option<EllipticFunction> {
let mut f = EllipticFunction::default();
let mut acc = Point::Infinity;
for (p, n) in divisor {
for _ in 0..n.unsigned_abs() {
if *n > 0 {
let (g, new_acc) = self.add_factor(&acc, p);
f.compose(g);
acc = new_acc;
} else {
let am = self.add(&acc, &self.neg(p));
let (g, _back) = self.add_factor(&am, p);
f.compose_inverse(g);
acc = am;
}
}
}
f.cancel();
if acc != Point::Infinity {
return None; }
Some(f)
}
fn double_factor(&self, t: &Point) -> (EllipticFunction, Point) {
let Point::Affine(x, y) = t else {
return (EllipticFunction::default(), Point::Infinity);
};
if *y == 0 {
return (
EllipticFunction::num1(EllFactor::Vertical(x.clone())),
Point::Infinity,
);
}
let lambda =
(Rational::from(3) * x.clone() * x + &self.a) / (Rational::from(2) * y.clone());
let nu = y.clone() - lambda.clone() * x;
let two_t = self.add(t, t);
let mut g = EllipticFunction::num1(EllFactor::Line(lambda, nu));
if let Point::Affine(x2, _) = &two_t {
g.den.push(EllFactor::Vertical(x2.clone()));
}
(g, two_t)
}
fn add_factor(&self, t: &Point, p: &Point) -> (EllipticFunction, Point) {
let (Point::Affine(x1, y1), Point::Affine(x2, y2)) = (t, p) else {
let sum = self.add(t, p);
return (EllipticFunction::default(), sum);
};
if x1 == x2 {
if (y1.clone() + y2) == 0 {
return (
EllipticFunction::num1(EllFactor::Vertical(x1.clone())),
Point::Infinity,
);
}
return self.double_factor(t); }
let lambda = (y2.clone() - y1) / (x2.clone() - x1);
let nu = y1.clone() - lambda.clone() * x1;
let tp = self.add(t, p);
let mut g = EllipticFunction::num1(EllFactor::Line(lambda, nu));
if let Point::Affine(x3, _) = &tp {
g.den.push(EllFactor::Vertical(x3.clone()));
}
(g, tp)
}
}
impl EllipticFunction {
fn num1(f: EllFactor) -> Self {
EllipticFunction {
num: vec![f],
den: Vec::new(),
}
}
fn squared(&self) -> Self {
EllipticFunction {
num: [self.num.clone(), self.num.clone()].concat(),
den: [self.den.clone(), self.den.clone()].concat(),
}
}
fn compose(&mut self, g: EllipticFunction) {
self.num.extend(g.num);
self.den.extend(g.den);
}
fn compose_inverse(&mut self, g: EllipticFunction) {
self.num.extend(g.den);
self.den.extend(g.num);
}
fn cancel(&mut self) {
let mut i = 0;
while i < self.num.len() {
if let Some(j) = self.den.iter().position(|d| *d == self.num[i]) {
self.num.remove(i);
self.den.remove(j);
} else {
i += 1;
}
}
}
}
fn poly_mul_small(a: &QPoly, b: &QPoly) -> QPoly {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r = vec![Rational::from(0); a.len() + b.len() - 1];
for (i, ai) in a.iter().enumerate() {
for (j, bj) in b.iter().enumerate() {
r[i + j] += ai.clone() * bj;
}
}
r
}
#[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)));
}
#[test]
fn quartic_reduction() {
let q = vec![r(4), r(0), r(-5), r(0), r(1)];
let (e, map) = weierstrass_from_quartic(&q, &r(1)).expect("quartic with root");
assert!(e.is_smooth());
let (xx, yy) = map(&r(0), &r(2));
assert!(e.contains(&Point::Affine(xx, yy)));
let (_, y2) = map(&r(2), &r(0));
assert_eq!(y2, r(0));
}
#[test]
fn quartic_point_reduction() {
let q = vec![r(1), r(1), r(1), r(1), r(1)];
let m = quartic_point_model(&q, &r(0), &r(1)).expect("point on curve");
assert_eq!(m.c, vec![r(-2), r(6), r(4), r(-8)]); let (e, _) = short_weierstrass(&m.c).expect("cubic");
let c3 = m.c[3].clone();
let c2 = m.c[2].clone();
for (xv, yv) in [(r(-1), r(1)), (r(3), r(11))] {
let (z, w) = m.zw(&xv, &yv).expect("finite place");
let cz = m.c.iter().rev().fold(r(0), |acc, c| acc * &z + c);
assert_eq!(w.clone() * &w, cz, "w²=C(z) at x={xv}");
let big_x = c3.clone() * &z + c2.clone() / r(3);
let big_y = c3.clone() * &w;
assert!(e.contains(&Point::Affine(big_x, big_y)), "on E at x={xv}");
}
assert!(m.zw(&r(0), &r(-1)).is_none());
}
#[test]
fn miller_log_arguments() {
let e = EllipticCurve::new(r(0), r(1));
let f2 = e.miller_function(&pt(-1, 0), 2).expect("miller");
assert_eq!(f2.num, vec![EllFactor::Vertical(r(-1))]);
assert!(f2.den.is_empty());
let f3 = e.miller_function(&pt(0, 1), 3).expect("miller");
assert_eq!(f3.num, vec![EllFactor::Line(r(0), r(1))]);
assert!(f3.den.is_empty());
}
#[test]
fn miller_order_six_terminates() {
let e = EllipticCurve::new(r(0), r(1));
assert_eq!(e.mul(6, &pt(2, 3)), Point::Infinity); let f = e.miller_function(&pt(2, 3), 6).expect("miller");
assert!(!f.num.is_empty());
assert!(f.num.iter().all(|n| !f.den.contains(n)));
}
#[test]
fn general_miller_multipoint() {
let e = EllipticCurve::new(r(0), r(1));
let div = [(pt(0, 1), 3), (Point::Infinity, -3)];
let f = e.general_miller(&div).expect("principal");
assert_eq!(f.num, vec![EllFactor::Line(r(0), r(1))]); assert!(f.den.is_empty());
assert!(e
.general_miller(&[(pt(0, 1), 1), (Point::Infinity, -1)])
.is_none());
}
}