use crate::ec::jacobian::jac_to_xz_add_components;
use crate::fp::{Fp2, FpBackend};
use super::basis_change::{apply_isomorphism, apply_isomorphism_general};
use super::couple::{couple_jac_to_xz, double_couple_jac_point, double_couple_point};
use super::theta_structure::{hadamard, pointwise_square, to_squared_theta};
use super::{
BasisChangeMatrix, ThetaCoupleCurve, ThetaCoupleJacPoint, ThetaCouplePoint, ThetaGluing,
ThetaPoint, TranslationMatrix,
};
#[inline]
fn base_change<L: FpBackend>(phi: &ThetaGluing<L>, t: &ThetaCouplePoint<L>) -> ThetaPoint<L> {
let null_point = ThetaPoint {
x: t.p1.x.mul(&t.p2.x),
y: t.p1.x.mul(&t.p2.z),
z: t.p1.z.mul(&t.p2.x),
t: t.p1.z.mul(&t.p2.z),
};
apply_isomorphism(&phi.basis_change, &null_point)
}
#[inline]
fn action_by_translation_z_and_det<L: FpBackend>(
p4: &crate::ec::EcPoint<L>,
p2: &crate::ec::EcPoint<L>,
) -> (Fp2<L>, Fp2<L>) {
let z_inv = p4.z.clone();
let det = p4.x.mul(&p2.z).sub(&p4.z.mul(&p2.x));
(z_inv, det)
}
#[inline]
fn action_by_translation_compute_matrix<L: FpBackend>(
p4: &crate::ec::EcPoint<L>,
p2: &crate::ec::EcPoint<L>,
z_inv: &Fp2<L>,
det_inv: &Fp2<L>,
) -> TranslationMatrix<L> {
let tmp = p4.x.mul(z_inv);
let g10 = p4.x.mul(&p2.x).mul(det_inv).sub(&tmp);
let g11 = p2.x.mul(det_inv).mul(&p4.z);
let g00 = g11.neg();
let g01 = p2.z.mul(det_inv).mul(&p4.z).neg();
TranslationMatrix { g00, g01, g10, g11 }
}
#[inline]
pub fn verify_two_torsion<L: FpBackend>(
k1_2: &ThetaCouplePoint<L>,
k2_2: &ThetaCouplePoint<L>,
e12: &ThetaCoupleCurve<L>,
) -> bool {
if bool::from(k1_2.p1.is_zero() | k1_2.p2.is_zero() | k2_2.p1.is_zero() | k2_2.p2.is_zero()) {
return false;
}
if bool::from(k1_2.p1.ct_equal(&k2_2.p1) | k1_2.p2.ct_equal(&k2_2.p2)) {
return false;
}
let o1 = double_couple_point(k1_2, e12);
let o2 = double_couple_point(k2_2, e12);
if !bool::from(o1.p1.is_zero() & o1.p2.is_zero() & o2.p1.is_zero() & o2.p2.is_zero()) {
return false;
}
true
}
#[inline]
fn action_by_translation<L: FpBackend>(
k1_4: &ThetaCouplePoint<L>,
k2_4: &ThetaCouplePoint<L>,
e12: &ThetaCoupleCurve<L>,
) -> Option<[TranslationMatrix<L>; 4]> {
let k1_2 = double_couple_point(k1_4, e12);
let k2_2 = double_couple_point(k2_4, e12);
if !verify_two_torsion(&k1_2, &k2_2, e12) {
return None;
}
let (z0, d0) = action_by_translation_z_and_det(&k1_4.p1, &k1_2.p1);
let (z1, d1) = action_by_translation_z_and_det(&k1_4.p2, &k1_2.p2);
let (z2, d2) = action_by_translation_z_and_det(&k2_4.p1, &k2_2.p1);
let (z3, d3) = action_by_translation_z_and_det(&k2_4.p2, &k2_2.p2);
let mut inverses = [z0, z1, z2, z3, d0, d1, d2, d3];
let mut t1 = [
Fp2::<L>::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
];
let mut t2 = [
Fp2::<L>::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
Fp2::zero(),
];
Fp2::batched_inv(&mut inverses, &mut t1, &mut t2);
if bool::from(inverses[0].ct_is_zero()) {
return None;
}
let g0 = action_by_translation_compute_matrix(&k1_4.p1, &k1_2.p1, &inverses[0], &inverses[4]);
let g1 = action_by_translation_compute_matrix(&k1_4.p2, &k1_2.p2, &inverses[1], &inverses[5]);
let g2 = action_by_translation_compute_matrix(&k2_4.p1, &k2_2.p1, &inverses[2], &inverses[6]);
let g3 = action_by_translation_compute_matrix(&k2_4.p2, &k2_2.p2, &inverses[3], &inverses[7]);
Some([g0, g1, g2, g3])
}
#[inline]
pub fn gluing_change_of_basis<L: FpBackend>(
k1_4: &ThetaCouplePoint<L>,
k2_4: &ThetaCouplePoint<L>,
e12: &ThetaCoupleCurve<L>,
) -> Option<BasisChangeMatrix<L>> {
let gi = action_by_translation(k1_4, k2_4, e12)?;
let mut m = BasisChangeMatrix::default();
let t001 = gi[0].g00.mul(&gi[2].g00).add(&gi[0].g01.mul(&gi[2].g10));
let t101 = gi[0].g10.mul(&gi[2].g00).add(&gi[0].g11.mul(&gi[2].g10));
let t002 = gi[1].g00.mul(&gi[3].g00).add(&gi[1].g01.mul(&gi[3].g10));
let t102 = gi[1].g10.mul(&gi[3].g00).add(&gi[1].g11.mul(&gi[3].g10));
m.m[0][0] = Fp2::one()
.add(&t001.mul(&t002))
.add(&gi[2].g00.mul(&gi[3].g00))
.add(&gi[0].g00.mul(&gi[1].g00));
m.m[0][1] = t001
.mul(&t102)
.add(&gi[2].g00.mul(&gi[3].g10))
.add(&gi[0].g00.mul(&gi[1].g10));
m.m[0][2] = t101
.mul(&t002)
.add(&gi[2].g10.mul(&gi[3].g00))
.add(&gi[0].g10.mul(&gi[1].g00));
m.m[0][3] = t101
.mul(&t102)
.add(&gi[2].g10.mul(&gi[3].g10))
.add(&gi[0].g10.mul(&gi[1].g10));
m.m[1][0] = gi[3].g00.mul(&m.m[0][0]).add(&gi[3].g01.mul(&m.m[0][1]));
m.m[1][1] = gi[3].g10.mul(&m.m[0][0]).add(&gi[3].g11.mul(&m.m[0][1]));
m.m[1][2] = gi[3].g00.mul(&m.m[0][2]).add(&gi[3].g01.mul(&m.m[0][3]));
m.m[1][3] = gi[3].g10.mul(&m.m[0][2]).add(&gi[3].g11.mul(&m.m[0][3]));
m.m[2][0] = gi[0].g00.mul(&m.m[0][0]).add(&gi[0].g01.mul(&m.m[0][2]));
m.m[2][1] = gi[0].g00.mul(&m.m[0][1]).add(&gi[0].g01.mul(&m.m[0][3]));
m.m[2][2] = gi[0].g10.mul(&m.m[0][0]).add(&gi[0].g11.mul(&m.m[0][2]));
m.m[2][3] = gi[0].g10.mul(&m.m[0][1]).add(&gi[0].g11.mul(&m.m[0][3]));
m.m[3][0] = gi[0].g00.mul(&m.m[1][0]).add(&gi[0].g01.mul(&m.m[1][2]));
m.m[3][1] = gi[0].g00.mul(&m.m[1][1]).add(&gi[0].g01.mul(&m.m[1][3]));
m.m[3][2] = gi[0].g10.mul(&m.m[1][0]).add(&gi[0].g11.mul(&m.m[1][2]));
m.m[3][3] = gi[0].g10.mul(&m.m[1][1]).add(&gi[0].g11.mul(&m.m[1][3]));
Some(m)
}
#[inline]
pub fn gluing_compute<L: FpBackend>(
e12: &ThetaCoupleCurve<L>,
xy_k1_8: &ThetaCoupleJacPoint<L>,
xy_k2_8: &ThetaCoupleJacPoint<L>,
verify: bool,
) -> Option<ThetaGluing<L>> {
let xy_k1_4 = double_couple_jac_point(xy_k1_8, e12);
let xy_k2_4 = double_couple_jac_point(xy_k2_8, e12);
let k1_8 = couple_jac_to_xz(xy_k1_8);
let k2_8 = couple_jac_to_xz(xy_k2_8);
let k1_4 = couple_jac_to_xz(&xy_k1_4);
let k2_4 = couple_jac_to_xz(&xy_k2_4);
let basis_change = gluing_change_of_basis(&k1_4, &k2_4, e12)?;
let mut out = ThetaGluing {
domain: e12.clone(),
xy_k1_8: xy_k1_8.clone(),
image_k1_8: Default::default(),
basis_change,
precomputation: ThetaPoint::default(),
codomain: ThetaPoint::default(),
};
let tt1 = {
let t = base_change(&out, &k1_8);
to_squared_theta(&t)
};
let tt2 = {
let t = base_change(&out, &k2_8);
to_squared_theta(&t)
};
if !bool::from(tt1.t.ct_is_zero() & tt2.t.ct_is_zero()) {
return None;
}
if bool::from(
tt1.x.ct_is_zero()
| tt2.x.ct_is_zero()
| tt1.y.ct_is_zero()
| tt2.z.ct_is_zero()
| tt1.z.ct_is_zero(),
) {
return None;
}
out.codomain.x = tt1.x.mul(&tt2.x);
out.codomain.y = tt1.y.mul(&tt2.x);
out.codomain.z = tt1.x.mul(&tt2.z);
out.codomain.t = Fp2::zero();
out.precomputation.x = tt1.y.mul(&tt2.z);
out.precomputation.y = out.codomain.z.clone();
out.precomputation.z = out.codomain.y.clone();
out.precomputation.t = Fp2::zero();
out.image_k1_8.x = tt1.x.mul(&out.precomputation.x);
out.image_k1_8.y = tt1.z.mul(&out.precomputation.z);
if verify {
let t1 = tt1.y.mul(&out.precomputation.y);
if !bool::from(t1.ct_equal(&out.image_k1_8.x)) {
return None;
}
let t1 = tt2.x.mul(&out.precomputation.x);
let t2 = tt2.z.mul(&out.precomputation.z);
if !bool::from(t2.ct_equal(&t1)) {
return None;
}
}
out.codomain = hadamard(&out.codomain);
Some(out)
}
#[inline]
pub fn gluing_eval_point<L: FpBackend>(
p: &ThetaCoupleJacPoint<L>,
phi: &ThetaGluing<L>,
) -> ThetaPoint<L> {
let add_comp1 = jac_to_xz_add_components(&p.p1, &phi.xy_k1_8.p1, &phi.domain.e1);
let add_comp2 = jac_to_xz_add_components(&p.p2, &phi.xy_k1_8.p2, &phi.domain.e2);
let u1u2 = add_comp1.u.mul(&add_comp2.u);
let v1v2 = add_comp1.v.mul(&add_comp2.v);
let t1_x = u1u2.add(&v1v2);
let t1_y = add_comp1.u.mul(&add_comp2.w);
let t1_z = add_comp1.w.mul(&add_comp2.u);
let t1_t = add_comp1.w.mul(&add_comp2.w);
let u1_plus_v1 = add_comp1.u.add(&add_comp1.v);
let u2_plus_v2 = add_comp2.u.add(&add_comp2.v);
let t2_x = u1_plus_v1.mul(&u2_plus_v2).sub(&t1_x);
let t2_y = add_comp1.v.mul(&add_comp2.w);
let t2_z = add_comp1.w.mul(&add_comp2.v);
let t1 = ThetaPoint {
x: t1_x,
y: t1_y,
z: t1_z,
t: t1_t,
};
let t2 = ThetaPoint {
x: t2_x,
y: t2_y,
z: t2_z,
t: Fp2::zero(),
};
let t1 = apply_isomorphism_general(&phi.basis_change, &t1, true);
let t2 = apply_isomorphism_general(&phi.basis_change, &t2, false);
let t1 = pointwise_square(&t1);
let t2 = pointwise_square(&t2);
let diff = ThetaPoint {
x: t1.x.sub(&t2.x),
y: t1.y.sub(&t2.y),
z: t1.z.sub(&t2.z),
t: t1.t.sub(&t2.t),
};
let diff = hadamard(&diff);
let image = ThetaPoint {
x: diff.x.mul(&phi.image_k1_8.y),
y: diff.y.mul(&phi.image_k1_8.y),
z: diff.z.mul(&phi.image_k1_8.x),
t: diff.t.mul(&phi.image_k1_8.x),
};
hadamard(&image)
}
#[inline]
pub fn gluing_eval_point_special_case<L: FpBackend>(
p: &ThetaCouplePoint<L>,
phi: &ThetaGluing<L>,
) -> Option<ThetaPoint<L>> {
let t = base_change(phi, p);
let t = to_squared_theta(&t);
if !bool::from(t.t.ct_is_zero()) {
return None;
}
let image = ThetaPoint {
x: t.x.mul(&phi.precomputation.x),
y: t.y.mul(&phi.precomputation.y),
z: t.z.mul(&phi.precomputation.z),
t: Fp2::zero(),
};
Some(hadamard(&image))
}
#[inline]
pub fn gluing_eval_basis<L: FpBackend>(
xy_t1: &ThetaCoupleJacPoint<L>,
xy_t2: &ThetaCoupleJacPoint<L>,
phi: &ThetaGluing<L>,
) -> (ThetaPoint<L>, ThetaPoint<L>) {
let image1 = gluing_eval_point(xy_t1, phi);
let image2 = gluing_eval_point(xy_t2, phi);
(image1, image2)
}