use crypto_bigint::{CtEq, CtSelect as _, CtAssign, Choice, Limb};
use super::I;
fn sum_assign_half_of_two_numbers_congruent_mod_two(
a: (Choice, &mut [Limb]),
b: (Choice, &[Limb]),
) -> Choice {
let add = a.0.ct_eq(&b.0);
let mut carry = a.1[0] & Limb::ONE;
let mut borrow = Limb::ZERO;
for i in 0 .. (a.1.len() - 1) {
let a_shr1: Limb = (a.1[i + 1] << (Limb::BITS - 1)) | (a.1[i] >> 1);
let b_shr1 = (b.1[i + 1] << (Limb::BITS - 1)) | (b.1[i] >> 1);
let if_add;
(if_add, carry) = a_shr1.carrying_add(b_shr1, carry);
let if_sub;
(if_sub, borrow) = a_shr1.borrowing_sub(b_shr1, borrow);
a.1[i] = Limb::ct_select(&if_sub, &if_add, add);
}
{
let i = a.1.len() - 1;
let a_shr1: Limb = a.1[i] >> 1;
let b_shr1 = b.1[i] >> 1;
let (if_add, _carry) = a_shr1.carrying_add(b_shr1, carry);
let if_sub;
(if_sub, borrow) = a_shr1.borrowing_sub(b_shr1, borrow);
a.1[i] = Limb::ct_select(&if_sub, &if_add, add);
}
let a_gte_b = borrow.is_zero();
{
let a_lt_b = !a_gte_b;
let mut carry = Limb::from(u8::from(a_lt_b & (!add)));
let mask = Limb::ZERO.wrapping_sub(carry);
for limb in a.1 {
let new_limb;
(new_limb, carry) = ((*limb) ^ mask).carrying_add(Limb::ZERO, carry);
*limb = new_limb;
}
}
Choice::ct_select(&b.0, &a.0, a_gte_b)
}
fn diff_assign(a: (Choice, &mut [Limb]), b: (Choice, &[Limb])) -> Choice {
let sub = a.0.ct_eq(&b.0);
let mut carry = Limb::ZERO;
let mut borrow = Limb::ZERO;
for (a_limb, b_limb) in a.1.iter_mut().zip(b.1) {
let if_add;
(if_add, carry) = a_limb.carrying_add(*b_limb, carry);
let if_sub;
(if_sub, borrow) = a_limb.borrowing_sub(*b_limb, borrow);
*a_limb = Limb::ct_select(&if_add, &if_sub, sub);
}
let a_gte_b = borrow.is_zero();
{
let a_lt_b = !a_gte_b;
let mut carry = Limb::from(u8::from(a_lt_b & sub));
let mask = Limb::ZERO.wrapping_sub(carry);
for limb in a.1 {
let new_limb;
(new_limb, carry) = ((*limb) ^ mask).carrying_add(Limb::ZERO, carry);
*limb = new_limb;
}
}
{
let if_add = a.0;
let if_sub = Choice::ct_select(&!b.0, &a.0, a_gte_b);
Choice::ct_select(&if_add, &if_sub, sub)
}
}
pub(super) struct Xgcd<U> {
pub(super) d: U,
pub(super) u: I<U>,
pub(super) v: I<U>,
}
trait LimbHelpers: Sized + AsRef<[Limb]> + AsMut<[Limb]> {
fn double(mut self) -> Self {
let mut carry = Limb::ZERO;
for limb in self.as_mut() {
let new_limb = ((*limb) << 1) | carry;
carry = (*limb) >> 63;
*limb = new_limb;
}
#[cfg(debug_assertions)]
{
debug_assert!(bool::from(carry.is_zero()));
}
self
}
fn ct_neg_mod(mut self, modulus: &Self, negate: Choice) -> (Choice, Self) {
let mut is_zero = Limb::ZERO;
let mut borrow = Limb::ZERO;
for (our_limb, mod_limb) in self.as_mut().iter_mut().zip(modulus.as_ref()) {
is_zero |= *our_limb;
let new_limb;
(new_limb, borrow) = mod_limb.borrowing_sub(*our_limb, borrow);
our_limb.ct_assign(&new_limb, negate);
}
(is_zero.is_zero(), self)
}
fn sub_mod(mut self, b: &Self, modulus: &Self) -> Self {
let mut borrow = Limb::ZERO;
for (our_limb, b_limb) in self.as_mut().iter_mut().zip(b.as_ref()) {
let new_limb;
(new_limb, borrow) = our_limb.borrowing_sub(*b_limb, borrow);
*our_limb = new_limb;
}
let underflowed = !borrow.is_zero();
let mut carry = Limb::ZERO;
for (our_limb, mod_limb) in self.as_mut().iter_mut().zip(modulus.as_ref()) {
let new_limb;
(new_limb, carry) = our_limb.carrying_add(*mod_limb, carry);
our_limb.ct_assign(&new_limb, underflowed);
}
self
}
}
impl<T: Sized + AsRef<[Limb]> + AsMut<[Limb]>> LimbHelpers for T {}
#[expect(private_bounds)]
pub(super) trait WideLimbs<Thin>: Clone + LimbHelpers {
fn rem(self, denom: &Thin) -> Thin;
}
#[expect(private_bounds)]
pub(crate) trait Limbs:
Clone + AsRef<[Limb]> + AsMut<[Limb]> + CtEq + CtAssign + LimbHelpers
{
type Wide: WideLimbs<Self>;
#[expect(private_interfaces)]
fn xgcd(self, other: Self) -> Xgcd<Self>;
fn div(self, denom: &Self) -> Self;
fn mul_mod(&self, other: &Self, modulus: &Self) -> Self {
self.mul(other).rem(modulus)
}
fn mul(&self, other: &Self) -> Self::Wide;
fn square(&self) -> Self::Wide {
self.mul(self)
}
}
#[expect(clippy::needless_pass_by_value)]
pub(crate) fn add<U: Limbs>(
a1: U,
mut b1: I<U>,
a2: U,
b2: I<U>,
c2: U::Wide,
) -> (U::Wide, I<U::Wide>) {
let s: I<U> = {
let sign = sum_assign_half_of_two_numbers_congruent_mod_two(
(b1.0, b1.1.as_mut()),
(b2.0, b2.1.as_ref()),
);
(sign, b1.1)
};
let (d1, x2, y1, y2): (U, I<U>, I<U>, I<U>) = {
let (d, y1) = {
let xgcd = a2.clone().xgcd(a1.clone());
(xgcd.d, xgcd.u)
};
let s_is_zero = {
let mut s_is_zero = Limb::ZERO;
for limb in s.1.as_ref() {
s_is_zero |= *limb;
}
s_is_zero.is_zero()
};
let mut s_for_gcd = s.1.clone();
s_for_gcd.as_mut()[0] |= Limb::from(u8::from(s_is_zero));
let xgcd = s_for_gcd.xgcd(d.clone());
let mut d1 = xgcd.d;
d1.ct_assign(&d, s_is_zero);
let mut x2 = xgcd.u;
x2.0 ^= !s.0;
for x2_limb in x2.1.as_mut() {
x2_limb.ct_assign(&Limb::ZERO, s_is_zero);
}
let mut y2 = (!xgcd.v.0, xgcd.v.1);
y2.0.ct_assign(&Choice::FALSE, s_is_zero);
y2.1.as_mut()[0].ct_assign(&Limb::ONE, s_is_zero);
for y2_limb in &mut y2.1.as_mut()[1 ..] {
y2_limb.ct_assign(&Limb::ZERO, s_is_zero);
}
(d1, x2, y1, y2)
};
let n: I<U> = {
let mut b2 = (b2.0, b2.1.clone());
let sign = diff_assign((b2.0, b2.1.as_mut()), (s.0, s.1.as_ref()));
(sign, b2.1)
};
let v1: U = a1.div(&d1);
let v2: U = a2.div(&d1);
let r = {
let r1 = y1.1.mul_mod(&y2.1, &v1).mul_mod(&n.1, &v1);
let r1_is_negative = (!y1.0) ^ (!y2.0) ^ (!n.0);
let (r1_was_zero, r1) = r1.ct_neg_mod(&v1, r1_is_negative);
let r1_is_modulus = r1_was_zero & r1_is_negative;
let r2 = x2.1.mul_mod(&c2.rem(&v1), &v1);
let (r2_was_zero, r2) = r2.ct_neg_mod(&v1, !x2.0);
let r2_is_zero = r2_was_zero & x2.0;
let mut r_unreduced = r1.sub_mod(&r2, &v1);
let r_unreduced_is_modulus = r1_is_modulus & r2_is_zero;
for limb in r_unreduced.as_mut() {
limb.ct_assign(&Limb::ZERO, r_unreduced_is_modulus);
}
r_unreduced
};
let mut b3 = v2.mul(&r).double();
let b3_is_negative = {
let mut carry = Limb::ZERO;
let mut borrow = Limb::ZERO;
let mut b3_limbs = b3.as_mut().iter_mut();
for (b3_limb, b2_limb) in
(&mut b3_limbs).zip(b2.1.as_ref().iter().chain(core::iter::repeat(&Limb::ZERO)))
{
let if_add;
(if_add, carry) = b3_limb.carrying_add(*b2_limb, carry);
let if_sub;
(if_sub, borrow) = b3_limb.borrowing_sub(*b2_limb, borrow);
*b3_limb = Limb::ct_select(&if_sub, &if_add, b2.0);
}
borrow.ct_assign(&Limb::ZERO, b2.0);
let underflowed = !borrow.is_zero();
let mut carry = Limb::from(u8::from(underflowed));
let mask = Limb::ZERO.wrapping_sub(carry);
for b3_limb in b3.as_mut() {
let new_limb;
(new_limb, carry) = ((*b3_limb) ^ mask).carrying_add(Limb::ZERO, carry);
*b3_limb = new_limb;
}
underflowed
};
let a3: U::Wide = v1.mul(&v2);
(a3, (!b3_is_negative, b3))
}
#[expect(clippy::needless_pass_by_value)]
pub(crate) fn double<U: Limbs>(a: U, b: I<U>, c: U::Wide) -> (U::Wide, I<U::Wide>) {
let s = b.clone();
let (d1, x2): (U, I<U>) = {
let d = a.clone();
let xgcd = s.1.xgcd(d);
let d1 = xgcd.d;
let mut x2 = xgcd.u;
x2.0 ^= !s.0;
(d1, x2)
};
let v1: U = a.div(&d1);
let r = {
let r2 = x2.1.mul_mod(&c.rem(&v1), &v1);
let (r2_was_zero, mut r2) = r2.ct_neg_mod(&v1, !x2.0);
let r2_is_zero = r2_was_zero & x2.0;
let mut borrow = Limb::ZERO;
for (mod_limb, r2_limb) in v1.as_ref().iter().zip(r2.as_mut()) {
let new_limb;
(new_limb, borrow) = mod_limb.borrowing_sub(*r2_limb, borrow);
*r2_limb = <_>::ct_select(&new_limb, &Limb::ZERO, r2_is_zero);
}
r2
};
let mut b3 = v1.mul(&r).double();
let b3_is_negative = {
let mut carry = Limb::ZERO;
let mut borrow = Limb::ZERO;
let mut b3_limbs = b3.as_mut().iter_mut();
for (b3_limb, b_limb) in
(&mut b3_limbs).zip(b.1.as_ref().iter().chain(core::iter::repeat(&Limb::ZERO)))
{
let if_add;
(if_add, carry) = b3_limb.carrying_add(*b_limb, carry);
let if_sub;
(if_sub, borrow) = b3_limb.borrowing_sub(*b_limb, borrow);
*b3_limb = Limb::ct_select(&if_sub, &if_add, b.0);
}
borrow.ct_assign(&Limb::ZERO, b.0);
let underflowed = !borrow.is_zero();
let mut carry = Limb::from(u8::from(underflowed));
let mask = Limb::ZERO.wrapping_sub(carry);
for b3_limb in b3.as_mut() {
let new_limb;
(new_limb, carry) = ((*b3_limb) ^ mask).carrying_add(Limb::ZERO, carry);
*b3_limb = new_limb;
}
underflowed
};
let a3: U::Wide = v1.square();
(a3, (!b3_is_negative, b3))
}