use crate::scalar::analytic::{fp_is_square, fp_sqrt, fq_sqrt};
use crate::scalar::{Fp, Fpn, Qp, Qq, Scalar, WittVec, Zp};
fn newton_sqrt<R: Scalar>(u: &R, seed: R, two_inv: &R) -> R {
let mut y = seed;
for _ in 0..64 {
let yi = y.inv().expect("Newton sqrt: the unit seed must invert");
let next = u.mul(&yi).add(&y).mul(two_inv);
if next == y {
return next;
}
y = next;
}
y
}
fn ipow(p: u128, e: u128) -> u128 {
let mut acc = 1u128;
for _ in 0..e {
acc = acc.checked_mul(p).expect("ipow exceeds u128");
}
acc
}
fn is_square_mod_two_power(a: u128, k: u128) -> bool {
if a == 0 {
return true;
}
let mut u = a;
let mut v = 0u128;
while u.is_multiple_of(2) {
u /= 2;
v += 1;
}
if !v.is_multiple_of(2) {
return false;
}
let m = k - v;
match m {
0 | 1 => true,
2 => u % 4 == 1,
_ => u % 8 == 1,
}
}
fn q2_unit_is_square(unit: u128, k: u128) -> Option<bool> {
match k {
0 => None,
1 => None,
2 => {
if unit % 4 == 3 {
Some(false)
} else {
None
}
}
_ => Some(unit % 8 == 1),
}
}
impl<const P: u128, const K: u128> Zp<P, K> {
fn two_inv() -> Option<Self> {
Self::one().add(&Self::one()).inv()
}
fn is_square_odd(&self) -> bool {
debug_assert!(P != 2);
if self.0 == 0 {
return true; }
let v = self.valuation();
if !v.is_multiple_of(2) {
return false;
}
let unit = self.0 / ipow(P, v);
fp_is_square(unit % P, P)
}
fn sqrt_odd(&self) -> Option<Self> {
debug_assert!(P != 2);
if self.0 == 0 {
return Some(Zp(0));
}
let v = self.valuation();
if !v.is_multiple_of(2) {
return None;
}
let unit_val = self.0 / ipow(P, v);
let seed_res = fp_sqrt(unit_val % P, P)?;
let two_inv = Self::two_inv().expect("odd p ⇒ 2 is a unit");
let root_unit = newton_sqrt(
&Zp::from_int(unit_val as i128),
Zp::from_int(seed_res as i128),
&two_inv,
);
Some(Zp::from_int(ipow(P, v / 2) as i128).mul(&root_unit))
}
pub fn is_square(&self) -> Option<bool> {
if P != 2 {
return Some(self.is_square_odd());
}
Self::assert_supported_params();
Some(is_square_mod_two_power(self.0, K))
}
pub fn sqrt(&self) -> Option<Option<Self>> {
if P != 2 {
return Some(self.sqrt_odd());
}
Self::assert_supported_params();
if self.0 == 0 {
return Some(Some(Zp(0)));
}
if !is_square_mod_two_power(self.0, K) {
return Some(None);
}
None
}
pub fn teichmuller(a: Fp<P>) -> Self {
let mut t = Zp::from_int(a.value() as i128);
for _ in 0..K {
t = t.pow(P);
}
t
}
}
impl<const P: u128, const K: u128> Qp<P, K> {
fn is_square_odd(&self) -> bool {
debug_assert!(P != 2);
match self.valuation() {
None => true, Some(v) if v % 2 != 0 => false,
Some(_) => fp_is_square(self.unit() % P, P),
}
}
fn sqrt_odd(&self) -> Option<Self> {
debug_assert!(P != 2);
let Some(v) = self.valuation() else {
return Some(Qp::zero());
};
if v % 2 != 0 {
return None;
}
let seed_res = fp_sqrt(self.unit() % P, P)?;
let two_inv = Qp::from_int(2).inv().expect("odd p ⇒ 2 invertible");
let unit = Qp::from_int(self.unit() as i128); let root_unit = newton_sqrt(&unit, Qp::from_int(seed_res as i128), &two_inv);
Some(Qp::from_p_power(v / 2).mul(&root_unit))
}
pub fn is_square(&self) -> Option<bool> {
if P != 2 {
return Some(self.is_square_odd());
}
Self::assert_supported_params();
match self.valuation() {
None => Some(true),
Some(v) if v % 2 != 0 => Some(false),
Some(_) => q2_unit_is_square(self.unit(), K),
}
}
pub fn sqrt(&self) -> Option<Option<Self>> {
if P != 2 {
return Some(self.sqrt_odd());
}
Self::assert_supported_params();
if self.is_zero() {
return Some(Some(Qp::zero()));
}
match self.is_square()? {
false => Some(None),
true => None,
}
}
pub fn teichmuller(a: Fp<P>) -> Self {
let mut t = Qp::from_int(a.value() as i128);
for _ in 0..K {
t = t.pow(P);
}
t
}
}
impl<const P: u128, const N: usize, const F: usize> WittVec<P, N, F> {
fn two_inv() -> Option<Self> {
Self::one().add(&Self::one()).inv()
}
fn split_unit(&self) -> Option<(usize, Self)> {
let v = self.p_valuation();
if v >= N {
return None;
}
let mut u = *self;
for _ in 0..v {
u = u.try_divide_by_p().expect("valuation says p | self");
}
Some((v, u))
}
fn is_square_odd(&self) -> bool {
debug_assert!(P != 2);
match self.split_unit() {
None => true, Some((v, _)) if v % 2 != 0 => false,
Some((_, u)) => u.residue().is_square(),
}
}
fn sqrt_odd(&self) -> Option<Self> {
debug_assert!(P != 2);
let Some((v, u)) = self.split_unit() else {
return Some(WittVec::zero());
};
if v % 2 != 0 {
return None;
}
let seed_res = fq_sqrt(u.residue())?;
let two_inv = Self::two_inv().expect("odd p ⇒ 2 is a unit");
let root_unit = newton_sqrt(&u, WittVec(seed_res.into_coeffs()), &two_inv);
let mut acc = root_unit;
let p = WittVec::<P, N, F>::from_int(P as i128);
for _ in 0..(v / 2) {
acc = acc.mul(&p);
}
Some(acc)
}
pub fn is_square(&self) -> Option<bool> {
if P != 2 {
return Some(self.is_square_odd());
}
let v = self.p_valuation();
if v >= N {
return Some(true);
}
if !v.is_multiple_of(2) {
return Some(false);
}
None
}
pub fn sqrt(&self) -> Option<Option<Self>> {
if P != 2 {
return Some(self.sqrt_odd());
}
if self.is_zero() {
return Some(Some(WittVec::zero()));
}
match self.is_square()? {
false => Some(None),
true => None,
}
}
}
impl<const P: u128, const N: usize, const F: usize> Qq<P, N, F> {
fn is_square_odd(&self) -> bool {
debug_assert!(P != 2);
match self.valuation() {
None => true, Some(v) if v % 2 != 0 => false,
Some(_) => self.unit_residue().expect("nonzero ⇒ residue").is_square(),
}
}
fn sqrt_odd(&self) -> Option<Self> {
debug_assert!(P != 2);
let Some(v) = self.valuation() else {
return Some(Qq::zero());
};
if v % 2 != 0 {
return None;
}
let seed_res = fq_sqrt(self.unit_residue().expect("nonzero ⇒ residue"))?;
let two_inv = Qq::from_int(2).inv().expect("odd p ⇒ 2 invertible");
let unit = Qq::from_witt(self.unit()); let root_unit = newton_sqrt(
&unit,
Qq::from_witt(WittVec(seed_res.into_coeffs())),
&two_inv,
);
Some(Qq::from_p_power(v / 2).mul(&root_unit))
}
pub fn is_square(&self) -> Option<bool> {
if P != 2 {
return Some(self.is_square_odd());
}
match self.valuation() {
None => Some(true),
Some(v) if v % 2 != 0 => Some(false),
Some(_) => None,
}
}
pub fn sqrt(&self) -> Option<Option<Self>> {
if P != 2 {
return Some(self.sqrt_odd());
}
if self.is_zero() {
return Some(Some(Qq::zero()));
}
match self.is_square()? {
false => Some(None),
true => None,
}
}
pub fn teichmuller(a: Fpn<P, F>) -> Self {
Qq::from_witt(WittVec::<P, N, F>::teichmuller(a))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zp_sqrt_round_trips_and_matches_is_square() {
fn check<const P: u128, const K: u128>() {
let m = Zp::<P, K>::modulus();
for a in 0..m {
let x = Zp::<P, K>(a);
let is_sq = x.is_square().expect("odd p squarehood is decidable");
match x.sqrt().expect("odd p root construction is implemented") {
Some(r) => {
assert!(is_sq, "sqrt(Some) but !is_square for {x:?}");
assert_eq!(r.mul(&r), x, "({r:?})² ≠ {x:?}");
}
None => assert!(!is_sq, "sqrt(None) but is_square for {x:?}"),
}
}
}
check::<3, 4>();
check::<5, 4>(); check::<7, 3>(); }
#[test]
fn qp_sqrt_handles_valuations() {
type Q = Qp<5, 5>;
let four = Q::from_int(4);
let r = four
.sqrt()
.expect("odd p root construction is implemented")
.expect("4 is a square in Q_5");
assert_eq!(r.mul(&r), four);
let x = Q::from_p_power(2).mul(&four);
let rx = x
.sqrt()
.expect("odd p root construction is implemented")
.expect("5²·4 is a square");
assert_eq!(rx.mul(&rx), x);
assert_eq!(rx.valuation(), Some(1));
assert_eq!(Q::from_int(5).is_square(), Some(false));
assert_eq!(Q::from_int(5).sqrt(), Some(None));
assert_eq!(Q::from_int(2).is_square(), Some(false));
assert_eq!(Q::zero().sqrt(), Some(Some(Q::zero())));
}
#[test]
fn teichmuller_is_a_root_of_unity_lifting_the_residue() {
type Z = Zp<7, 4>;
for a in 1..7u128 {
let t = Z::teichmuller(Fp::<7>::from_u128(a));
assert_eq!(t.0 % 7, a, "τ lifts the residue");
assert_eq!(t.pow(7), t, "τ is Frobenius-fixed (τ^p = τ)");
assert_eq!(t.pow(6), Z::one(), "τ^{{p-1}} = 1");
}
for a in 1..7u128 {
let tq = Qp::<7, 4>::teichmuller(Fp::<7>::from_u128(a));
assert_eq!(tq.unit(), Zp::<7, 4>::teichmuller(Fp::<7>::from_u128(a)).0);
}
}
#[test]
fn wittvec_sqrt_over_f9() {
type W = WittVec<3, 3, 2>;
let q = W::residue_order(); let modu = W::modulus();
for c0 in 0..modu {
for c1 in 0..q {
let x = WittVec::<3, 3, 2>([c0, c1]);
if x.residue().is_zero() {
continue; }
let sq = x.mul(&x);
assert_eq!(sq.is_square(), Some(true), "a square must read as a square");
let r = sq
.sqrt()
.expect("odd p root construction is implemented")
.expect("a square has a root");
assert_eq!(r.mul(&r), sq, "({r:?})² ≠ {sq:?}");
}
}
}
#[test]
fn qq_sqrt_and_teichmuller_over_f9() {
type Q = Qq<3, 3, 2>;
let g = WittVec::<3, 3, 2>([0, 1]); let x = Q::from_witt(g);
let sq = x.mul(&x);
let r = sq
.sqrt()
.expect("odd p root construction is implemented")
.expect("a square inverts");
assert_eq!(r.mul(&r), sq);
let y = Q::from_p_power(2).mul(&sq);
let ry = y
.sqrt()
.expect("odd p root construction is implemented")
.expect("3²·square is a square");
assert_eq!(ry.mul(&ry), y);
assert_eq!(ry.valuation(), Some(1));
assert_eq!(Q::from_p_power(1).mul(&sq).is_square(), Some(false));
let a = g.residue();
let t = Q::teichmuller(a);
assert_eq!(t.unit_residue(), Some(a));
assert_eq!(t.valuation(), Some(0));
}
#[test]
fn qq_f1_matches_qp() {
let x = Qq::<5, 5, 1>::from_int(4);
let r = x.sqrt().unwrap().unwrap();
assert_eq!(r.mul(&r), x);
assert_eq!(Qq::<5, 5, 1>::from_int(2).sqrt(), Some(None)); }
#[test]
fn dyadic_square_apis_are_checked_and_honest() {
type Z = Zp<2, 5>;
assert_eq!(Zp::<2, 5>(0).is_square(), Some(true));
assert_eq!(Zp::<2, 5>(1).is_square(), Some(true));
assert_eq!(Zp::<2, 5>(4).is_square(), Some(true));
assert_eq!(Zp::<2, 5>(2).is_square(), Some(false)); assert_eq!(Zp::<2, 5>(3).is_square(), Some(false)); assert_eq!(Zp::<2, 5>(0).sqrt(), Some(Some(Z::zero())));
assert_eq!(Zp::<2, 5>(3).sqrt(), Some(None));
assert_eq!(Zp::<2, 5>(1).sqrt(), None);
type Q = Qp<2, 5>;
assert_eq!(Q::zero().is_square(), Some(true));
assert_eq!(Q::from_int(1).is_square(), Some(true));
assert_eq!(Q::from_int(2).is_square(), Some(false)); assert_eq!(Q::from_int(3).is_square(), Some(false)); assert_eq!(Qp::<2, 2>::from_int(1).is_square(), None); assert_eq!(Q::zero().sqrt(), Some(Some(Q::zero())));
assert_eq!(Q::from_int(3).sqrt(), Some(None));
assert_eq!(Q::from_int(1).sqrt(), None);
type W = WittVec<2, 4, 2>;
assert_eq!(W::zero().is_square(), Some(true));
assert_eq!(W::from_int(2).is_square(), Some(false)); assert_eq!(W::one().is_square(), None);
type R = Qq<2, 4, 2>;
assert_eq!(R::zero().is_square(), Some(true));
assert_eq!(R::from_p_power(1).is_square(), Some(false));
assert_eq!(R::one().is_square(), None);
assert_eq!(R::from_p_power(1).sqrt(), Some(None));
}
}