use core::{
ops,
fmt,
cmp,
};
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct G2Poly(pub u64);
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
pub struct G2PolyProd(pub u128);
impl G2PolyProd {
pub fn to_poly(self) -> G2Poly {
self.try_to_poly().expect("Tried to convert product bigger than G2Poly max")
}
pub fn try_to_poly(self) -> Option<G2Poly> {
if self.0 <= u64::max_value() as u128 {
Some(G2Poly(self.0 as u64))
} else {
None
}
}
}
impl fmt::Debug for G2Poly {
fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result {
write!(f, "G2Poly {{ {:b} }}", self.0)
}
}
impl fmt::Debug for G2PolyProd {
fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result {
write!(f, "G2PolyProd {{ {:b} }}", self.0)
}
}
impl fmt::Display for G2Poly {
fn fmt<'a>(&self, f: &mut fmt::Formatter<'a>) -> fmt::Result {
if self.0 == 0 {
return write!(f, "G2Poly {{ 0 }}");
}
write!(f, "G2Poly {{ ")?;
let start = 63 - self.0.leading_zeros();
let mut check = 1 << start;
let mut append = false;
for p in (0..=start).rev() {
if check & self.0 > 0 {
if append {
write!(f, " + ")?;
}
if p == 0 {
write!(f, "1")?;
} else if p == 1 {
write!(f, "x")?;
} else {
write!(f, "x^{}", p)?;
}
append = true;
}
check >>= 1;
}
write!(f, " }}")
}
}
impl ops::Mul for G2Poly {
type Output = G2PolyProd;
fn mul(self, rhs: G2Poly) -> G2PolyProd {
let mut result = 0;
let smaller = cmp::min(self.0, rhs.0);
let mut bigger = cmp::max(self.0, rhs.0) as u128;
let end = 64 - smaller.leading_zeros();
let mut bitpos = 1;
for _ in 0..end {
if bitpos & smaller > 0 {
result ^= bigger;
}
bigger <<= 1;
bitpos <<= 1;
}
G2PolyProd(result)
}
}
impl ops::Rem for G2Poly {
type Output = G2Poly;
fn rem(self, rhs: G2Poly) -> G2Poly {
G2PolyProd(self.0 as u128) % rhs
}
}
impl ops::Div for G2Poly {
type Output = G2Poly;
fn div(self, rhs: G2Poly) -> G2Poly {
let divisor = rhs.0;
let divisor_degree_p1 = 64 - divisor.leading_zeros();
assert!(divisor_degree_p1 > 0);
let mut quotient = 0;
let mut rem = self.0;
let mut rem_degree_p1 = 64 - self.0.leading_zeros();
while divisor_degree_p1 <= rem_degree_p1 {
let shift_len = rem_degree_p1 - divisor_degree_p1;
quotient |= 1 << shift_len;
rem ^= divisor << shift_len;
rem_degree_p1 = 64 - rem.leading_zeros();
}
G2Poly(quotient)
}
}
impl ops::Add for G2Poly {
type Output = G2Poly;
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: G2Poly) -> G2Poly {
G2Poly(self.0 ^ rhs.0)
}
}
impl ops::Sub for G2Poly {
type Output = G2Poly;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: G2Poly) -> G2Poly {
G2Poly(self.0 ^ rhs.0)
}
}
impl ops::Rem<G2Poly> for G2PolyProd {
type Output = G2Poly;
fn rem(self, rhs: G2Poly) -> G2Poly {
let module = rhs.0 as u128;
let mod_degree_p1 = 128 - module.leading_zeros();
assert!(mod_degree_p1 > 0);
let mut rem = self.0;
let mut rem_degree_p1 = 128 - rem.leading_zeros();
while mod_degree_p1 <= rem_degree_p1 {
let shift_len = rem_degree_p1 - mod_degree_p1;
rem ^= module << shift_len;
rem_degree_p1 = 128 - rem.leading_zeros();
}
G2Poly(rem as u64)
}
}
pub fn gcd(a: G2Poly, b: G2Poly) -> G2Poly {
let (mut a, mut b) = (cmp::max(a, b), cmp::min(a, b));
while b != G2Poly(0) {
let new_b = a % b;
a = b;
b = new_b;
}
a
}
pub fn extended_gcd(a: G2Poly, b: G2Poly) -> (G2Poly, G2Poly, G2Poly) {
let mut s = G2Poly(0);
let mut old_s = G2Poly(1);
let mut t = G2Poly(1);
let mut old_t = G2Poly(0);
let mut r = b;
let mut old_r = a;
while r != G2Poly(0) {
let quotient = old_r / r;
let tmp = old_r - (quotient * r).to_poly();
old_r = r;
r = tmp;
let tmp = old_s - (quotient * s).to_poly();
old_s = s;
s = tmp;
let tmp = old_t - (quotient * t).to_poly();
old_t = t;
t = tmp;
}
(old_r, old_s, old_t)
}
impl G2Poly {
pub const UNIT: Self = G2Poly(1);
pub const ZERO: Self = G2Poly(0);
pub const X: Self = G2Poly(2);
pub fn pow_mod(self, power: u64, modulus: G2Poly) -> G2Poly {
let mut init = G2Poly::UNIT;
let mut max = 0x80_00_00_00_00_00_00_00;
assert_eq!(max << 1, 0);
while max > 0 {
let square = init * init;
init = square % modulus;
if power & max > 0 {
let mult = init * self;
init = mult % modulus;
}
max >>= 1;
}
init
}
pub fn is_irreducible(self) -> bool {
const PRIMES_LE_63: [u64; 11] = [
2,
3,
5,
7,
11,
13,
17,
19,
23,
29,
31,
];
if self == G2Poly::ZERO {
return false;
}
let n = self.degree().expect("Already checked for zero");
let distinct_prime_coprod = PRIMES_LE_63.iter()
.filter(|&&p| p <= n)
.filter(|&&p| n % p == 0)
.map(|&p| n / p);
for p in distinct_prime_coprod {
let q_to_the_p = 1 << p;
let h = G2Poly::X.pow_mod(q_to_the_p, self) - (G2Poly(2) % self);
if gcd(self, h) != G2Poly(1) {
return false;
}
}
let g = G2Poly::X.pow_mod(1 << n, self) - G2Poly(2) % self;
g == G2Poly::ZERO
}
pub fn degree(self) -> Option<u64> {
63_u32.checked_sub(self.0.leading_zeros()).map(|n| n as u64)
}
pub fn is_generator(self, module: G2Poly) -> bool {
assert!(module.is_irreducible());
let order = module.degree().expect("Module is not 0");
let p_minus_1 = (1 << order) - 1;
let mut g_pow = self;
for _ in 1..p_minus_1 {
if g_pow == G2Poly::UNIT {
return false;
}
g_pow = (g_pow * self) % module;
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug_format() {
let a = 0;
let b = 0b0110;
let c = 1;
let d = 49;
assert_eq!(format!("{:?}", G2Poly(a)), "G2Poly { 0 }");
assert_eq!(format!("{:?}", G2Poly(b)), "G2Poly { 110 }");
assert_eq!(format!("{:?}", G2Poly(c)), "G2Poly { 1 }");
assert_eq!(format!("{:?}", G2Poly(d)), "G2Poly { 110001 }");
}
#[test]
fn test_display_format() {
let a = 0;
let b = 0b0110;
let c = 1;
let d = 49;
assert_eq!(format!("{}", G2Poly(a)), "G2Poly { 0 }");
assert_eq!(format!("{}", G2Poly(b)), "G2Poly { x^2 + x }");
assert_eq!(format!("{}", G2Poly(c)), "G2Poly { 1 }");
assert_eq!(format!("{}", G2Poly(d)), "G2Poly { x^5 + x^4 + 1 }");
}
#[test]
fn test_poly_prod() {
let e = G2Poly(1);
let a = G2Poly(0b01101);
let b = G2Poly(0b11111);
let c = G2Poly(0xff_ff_ff_ff_ff_ff_ff_ff);
assert_eq!(e * e, G2PolyProd(1));
assert_eq!(a * e, G2PolyProd(0b01101));
assert_eq!(b * e, G2PolyProd(0b11111));
assert_eq!(c * e, G2PolyProd(0xff_ff_ff_ff_ff_ff_ff_ff));
assert_eq!(a * b, G2PolyProd(0b10011011));
assert_eq!(a * c, G2PolyProd(0b1001111111111111111111111111111111111111111111111111111111111111011));
assert_eq!(c * c, G2PolyProd(0b1010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101));
}
#[test]
fn test_poly_rem() {
let a = G2PolyProd(0b00111);
let b = G2PolyProd(0b10101);
let m = G2Poly(0b01001);
assert_eq!(a % m, G2Poly(0b00111));
assert_eq!(b % m, G2Poly(0b0111));
}
#[test]
fn test_irreducible_check() {
let a = G2Poly(0b11);
let b = G2Poly(0b1101);
let c = G2Poly(0x80_00_00_00_80_00_00_01);
let z = G2Poly(0b1001);
let y = G2Poly(0x80_00_00_00_80_00_00_03);
assert!(a.is_irreducible());
assert!(b.is_irreducible());
assert!(c.is_irreducible());
assert!(!z.is_irreducible());
assert!(!y.is_irreducible());
}
#[test]
fn test_generator_check() {
let m = G2Poly(0b100011011);
let g = G2Poly(0b11);
assert!(g.is_generator(m));
}
#[test]
#[should_panic]
fn test_generator_check_fail() {
let m = G2Poly(0b101);
let g = G2Poly(0b1);
g.is_generator(m);
}
#[test]
fn test_poly_div() {
let a = G2Poly(0b10000001001);
let b = G2Poly(0b1010);
assert_eq!(G2Poly(0b10101011), a / b);
}
#[test]
fn test_extended_euclid() {
let m = G2Poly(0b10000001001);
let a = G2Poly(10);
let (gcd, x, _) = extended_gcd(a, m);
assert_eq!(G2Poly(1), gcd);
assert_eq!(G2Poly(1), a * x % m);
}
}