use core::{fmt::Debug, ops::Neg};
#[cfg(feature = "std")]
use std::io;
use crypto_bigint::{Choice, CtOption};
pub unsafe trait Coefficients {
#[expect(clippy::type_complexity)]
fn a_b_c_discriminant(
self,
) -> (impl AsRef<[u8]>, (Choice, impl AsRef<[u8]>), impl AsRef<[u8]>, impl AsRef<[u8]>);
}
pub trait Element:
Sized + Send + Sync + Clone + PartialEq + Eq + Debug + Neg<Output = Self> + Coefficients
{
fn identity(discriminant_abs: impl AsRef<[u8]>) -> Self;
fn is_identity(&self) -> Choice;
#[must_use]
fn double(&self) -> Self;
#[must_use]
fn add(&self, other: &Self) -> Self;
#[must_use]
fn sub(&self, other: Self) -> Self;
#[must_use]
unsafe fn from_coefficients(
a: impl AsRef<[u8]>,
b: (Choice, impl AsRef<[u8]>),
c: impl AsRef<[u8]>,
discriminant_abs: impl AsRef<[u8]>,
) -> Self;
#[cfg(feature = "std")]
fn compress(self, mut writer: impl io::Write) -> io::Result<()> {
use crypto_bigint::{NonZero, BoxedUint};
let (a, (b_positive, b_abs), _c, discriminant_abs) = self.a_b_c_discriminant();
let a = a.as_ref();
let b_abs = b_abs.as_ref();
let discriminant_abs = discriminant_abs.as_ref();
let a = BoxedUint::from_le_slice_vartime(a);
let a = NonZero::new(a).expect("`a > 0` when `delta < 0`");
let b_abs = BoxedUint::from_le_slice_vartime(b_abs);
let discriminant_abs = BoxedUint::from_le_slice_vartime(discriminant_abs);
writer.write_all(&crate::crypto_bigint::encode_compressed_binary_quadratic_form(
a,
b_positive,
b_abs,
&discriminant_abs,
))
}
#[cfg(feature = "std")]
fn decompress(reader: impl io::Read, discriminant_abs: impl AsRef<[u8]>) -> io::Result<Self> {
let discriminant_abs = discriminant_abs.as_ref();
{
let lsb = discriminant_abs.first().ok_or(io::Error::other("zero-length discriminant"))?;
if (lsb & 1) != 1 {
Err(io::Error::other("non-odd discriminant"))?;
}
}
let (a, (b_positive, b_abs), c) =
crate::crypto_bigint::decode_compressed_binary_quadratic_form(
reader,
&::crypto_bigint::BoxedUint::from_le_slice(
discriminant_abs,
u32::try_from(8 * discriminant_abs.len())
.map_err(|_| io::Error::other("absurdly large discriminant?"))?,
)
.map_err(|e| {
io::Error::other(alloc::format!(
"container overflowed despite precision proportional to length of the encoding: {e:?}"
))
})?,
)
.map_err(|e| io::Error::other(alloc::format!("{e:?}")))?;
let a = a.to_le_bytes();
let b_abs = b_abs.to_le_bytes();
let c = c.to_le_bytes();
Ok(unsafe { Self::from_coefficients(a, (b_positive, b_abs), c, discriminant_abs) })
}
fn uncompressed_encode(&self) -> impl AsRef<[u8]>;
fn uncompressed_decode(
buf: impl AsRef<[u8]>,
discriminant_abs: impl AsRef<[u8]>,
) -> CtOption<Self>;
fn from(source: impl Element) -> Self {
let (a, b, c, discriminant) = source.a_b_c_discriminant();
unsafe { Self::from_coefficients(a, b, c, discriminant) }
}
#[cfg(feature = "alloc")] fn next_prime_ideal_squared(
mut rng: impl rand::CryptoRng,
seed: crypto_bigint::BoxedUint,
discriminant_abs: impl AsRef<[u8]>,
bits_of_security: u32,
) -> Self {
use crypto_bigint::{CtEq as _, NonZero, Odd, Resize as _, ConcatenatingSquare as _, BoxedUint};
use crate::crypto_bigint::sqrt_mod_p_vartime;
let discriminant_abs = discriminant_abs.as_ref();
assert_eq!(discriminant_abs[0] & 1, 1, "discriminant wasn't odd");
let discriminant_abs = BoxedUint::from_le_slice_vartime(discriminant_abs);
assert!(discriminant_abs > BoxedUint::from(200u8));
let inclusive_end = discriminant_abs.wrapping_shr_vartime(2).floor_sqrt();
assert!(seed <= inclusive_end);
let mut seed = seed.resize(inclusive_end.bits_precision());
if seed.bits_vartime() <= 2 {
seed = BoxedUint::from(3u8).resize(inclusive_end.bits_precision());
}
let mut i = 0u64;
seed = match super::primes::next_prime(&mut rng, seed, bits_of_security) {
Ok(seed) => seed,
Err(super::primes::Error::Capacity) => inclusive_end.concatenating_add(BoxedUint::one()),
Err(super::primes::Error::NoMillerRabin) => {
panic!("bits of security effected no Miller-Rabin configuration")
}
};
if seed > inclusive_end {
seed = BoxedUint::from(3u8);
}
let mut b_abs;
while {
let seed = Odd::new(seed.clone()).expect("odd prime wasn't odd?");
let discriminant_abs_mod_a = discriminant_abs.rem(seed.as_nz_ref());
let discriminant_mod_a = discriminant_abs_mod_a.neg_mod(seed.as_nz_ref());
b_abs = sqrt_mod_p_vartime(discriminant_mod_a, &seed);
b_abs.is_none()
} {
i += 1;
seed = match super::primes::next_prime(
&mut rng,
seed.concatenating_add(BoxedUint::one()),
bits_of_security,
) {
Ok(seed) => seed,
Err(super::primes::Error::Capacity) => inclusive_end.concatenating_add(BoxedUint::one()),
Err(super::primes::Error::NoMillerRabin) => {
panic!("bits of security effected no Miller-Rabin configuration")
}
};
if seed > inclusive_end {
seed = BoxedUint::from(3u8);
}
seed = seed.resize(inclusive_end.bits_precision());
}
let (b_positive, b_abs) = {
let b_abs = b_abs.unwrap();
#[cfg(debug_assertions)]
{
let seed = NonZero::new(seed.clone()).unwrap();
debug_assert_eq!(
b_abs.concatenating_square().rem(&seed),
discriminant_abs.rem(&seed).neg_mod(&seed)
);
}
let b_negative = (i & 1).ct_eq(&1);
let b_positive = !b_negative;
(b_positive, b_abs)
};
let a = seed;
let (four_c, zero) = (b_abs.concatenating_square().concatenating_add(&discriminant_abs))
.div_rem(&NonZero::new(a.clone()).unwrap());
assert!(
bool::from(zero.is_zero()),
"didn't correctly sample a prime ideal ($b^2 + |delta|$ not divisible by `a`)"
);
let (c, zero) = four_c.div_rem(&NonZero::new(BoxedUint::from(4u8)).unwrap());
assert!(
bool::from(zero.is_zero()),
"didn't correctly sample a prime ideal ($(b^2 + |delta|)/a$ not divisible by `4`)"
);
let trim = |number: BoxedUint| {
let mut number = number.to_le_bytes().to_vec();
while number.last() == Some(&0) {
number.pop().unwrap();
}
number
};
let prime_ideal = unsafe {
Self::from_coefficients(trim(a), (b_positive, trim(b_abs)), trim(c), trim(discriminant_abs))
};
prime_ideal.double()
}
}