use crate::curve::{MontCurve, MontPoint};
use crate::fq::Fq;
const MAX_HALF_KERNEL: usize = 293;
fn kernel_multiples(kernel: &MontPoint, ell: u64, a: &Fq) -> ([MontPoint; MAX_HALF_KERNEL], usize) {
let half = ((ell - 1) / 2) as usize;
let two = Fq::from_u64(2);
let four = Fq::from_u64(4);
let a24 = Fq::mul(&Fq::add(a, &two), &Fq::inv(&four));
let mut points = [MontPoint::inf(); MAX_HALF_KERNEL];
points[0] = *kernel;
if half >= 2 {
points[1] = kernel.xdbl(&a24);
}
let mut idx = 2;
while idx < half {
points[idx] = MontPoint::xadd(&points[idx - 1], kernel, &points[idx - 2]);
idx += 1;
}
(points, half)
}
pub fn isogeny_codomain(curve: &MontCurve, kernel: &MontPoint, ell: u64) -> MontCurve {
let (points, half) = kernel_multiples(kernel, ell, &curve.a);
let a = curve.a; let two = Fq::from_u64(2);
let three = Fq::from_u64(3);
let four = Fq::from_u64(4);
let five = Fq::from_u64(5);
let _seven = Fq::from_u64(7);
let mut v_sum = Fq::ZERO;
let mut w_sum = Fq::ZERO;
let mut idx = 0;
while idx < half {
let xi = Fq::mul(&points[idx].x, &Fq::inv(&points[idx].z));
let xi2 = Fq::square(&xi);
let gx = Fq::add(
&Fq::add(&Fq::mul(&three, &xi2), &Fq::mul(&Fq::mul(&two, &a), &xi)),
&Fq::ONE,
);
let ti = Fq::mul(&two, &gx);
let xi3 = Fq::mul(&xi2, &xi);
let ui = Fq::mul(&four, &Fq::add(&Fq::add(&xi3, &Fq::mul(&a, &xi2)), &xi));
v_sum = Fq::add(&v_sum, &ti);
w_sum = Fq::add(&w_sum, &Fq::add(&ui, &Fq::mul(&xi, &ti)));
idx += 1;
}
let mut x0_image = Fq::ZERO;
idx = 0;
while idx < half {
let xi = Fq::mul(&points[idx].x, &Fq::inv(&points[idx].z));
let xi2 = Fq::square(&xi);
let gx = Fq::add(
&Fq::add(&Fq::mul(&three, &xi2), &Fq::mul(&Fq::mul(&two, &a), &xi)),
&Fq::ONE,
);
let ti = Fq::mul(&two, &gx);
let xi3 = Fq::mul(&xi2, &xi);
let ui = Fq::mul(&four, &Fq::add(&Fq::add(&xi3, &Fq::mul(&a, &xi2)), &xi));
let xi_inv = Fq::inv(&xi);
let xi2_inv = Fq::square(&xi_inv);
let term = Fq::add(&Fq::neg(&Fq::mul(&ti, &xi_inv)), &Fq::mul(&ui, &xi2_inv));
x0_image = Fq::add(&x0_image, &term);
idx += 1;
}
let a4_new = Fq::sub(&Fq::ONE, &Fq::mul(&five, &v_sum));
let x0 = x0_image;
let x0_sq = Fq::square(&x0);
let b_coeff = Fq::add(&Fq::mul(&three, &x0), &a); let c_coeff = Fq::add(
&Fq::add(&Fq::mul(&three, &x0_sq), &Fq::mul(&Fq::mul(&two, &a), &x0)),
&a4_new,
);
let alpha = Fq::sqrt(&c_coeff);
match alpha {
Some(alpha_val) => {
let new_a = Fq::mul(&b_coeff, &Fq::inv(&alpha_val));
MontCurve { a: new_a }
}
None => {
let neg_c = Fq::neg(&c_coeff);
let alpha_neg = Fq::sqrt(&neg_c).unwrap_or(Fq::ONE);
let new_a = Fq::neg(&Fq::mul(&b_coeff, &Fq::inv(&alpha_neg)));
MontCurve { a: new_a }
}
}
}
pub fn isogeny_eval(kernel: &MontPoint, ell: u64, q_pt: &MontPoint, a: &Fq) -> MontPoint {
let (points, half) = kernel_multiples(kernel, ell, a);
let mut num = q_pt.x;
let mut den = q_pt.z;
let mut idx = 0;
while idx < half {
let t1 = Fq::sub(
&Fq::mul(&q_pt.x, &points[idx].z),
&Fq::mul(&q_pt.z, &points[idx].x),
);
let t2 = Fq::sub(
&Fq::mul(&q_pt.x, &points[idx].x),
&Fq::mul(&q_pt.z, &points[idx].z),
);
num = Fq::mul(&num, &Fq::square(&t1));
den = Fq::mul(&den, &Fq::square(&t2));
idx += 1;
}
MontPoint { x: num, z: den }
}
pub fn apply_isogeny(
curve: &MontCurve,
kernel: &MontPoint,
ell: u64,
push_point: Option<&MontPoint>,
) -> (MontCurve, Option<MontPoint>) {
let new_curve = isogeny_codomain(curve, kernel, ell);
let new_point = push_point.map(|q| isogeny_eval(kernel, ell, q, &curve.a));
(new_curve, new_point)
}