use crate::{Choice, CtOption, Int, Limb, Uint};
pub(crate) struct ExtendedUint<const LIMBS: usize, const EXTENSION_LIMBS: usize>(
Uint<LIMBS>,
Uint<EXTENSION_LIMBS>,
);
impl<const LIMBS: usize, const EXTRA: usize> ExtendedUint<LIMBS, EXTRA> {
#[inline]
pub const fn from_product(lhs: Uint<LIMBS>, rhs: Uint<EXTRA>) -> Self {
let (lo, hi) = lhs.widening_mul(&rhs);
ExtendedUint(lo, hi)
}
pub const fn wrapping_mul<const RHS_LIMBS: usize>(&self, rhs: &Uint<RHS_LIMBS>) -> Self {
let (lo, hi) = self.0.widening_mul(rhs);
let hi = self.1.wrapping_mul(rhs).wrapping_add(&hi.resize::<EXTRA>());
Self(lo, hi)
}
#[inline]
pub const fn as_extended_int(&self) -> ExtendedInt<LIMBS, EXTRA> {
ExtendedInt(self.0, self.1)
}
#[inline]
pub const fn is_zero(&self) -> Choice {
self.0.is_nonzero().not().and(self.1.is_nonzero().not())
}
#[inline]
pub const fn checked_drop_extension(&self) -> CtOption<Uint<LIMBS>> {
CtOption::new(self.0, self.1.is_nonzero().not())
}
#[inline]
pub const fn wrapping_neg(&self) -> Self {
let (lhs, carry) = self.0.carrying_neg();
let mut rhs = self.1.not();
rhs = Uint::select(&rhs, &rhs.wrapping_add(&Uint::ONE), carry);
Self(lhs, rhs)
}
#[inline]
pub const fn wrapping_neg_if(&self, negate: Choice) -> Self {
let neg = self.wrapping_neg();
Self(
Uint::select(&self.0, &neg.0, negate),
Uint::select(&self.1, &neg.1, negate),
)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub const fn bounded_shr<const UPPER_BOUND: u32>(&self, shift: u32) -> Self {
assert!(shift < UPPER_BOUND && UPPER_BOUND <= Uint::<EXTRA>::BITS);
let hi = self.1.bounded_shr(shift, UPPER_BOUND);
let carry = self.1.unbounded_shl(Uint::<EXTRA>::BITS - shift);
let mut lo = self.0.bounded_shr(shift, UPPER_BOUND);
let limb_diff = LIMBS.saturating_sub(EXTRA) as u32;
let carry = carry
.resize::<LIMBS>()
.unbounded_shl_by_limbs_vartime(limb_diff);
lo = lo.bitxor(&carry);
Self(lo, hi)
}
#[inline]
#[allow(clippy::cast_possible_truncation)]
pub const fn shr_vartime(&self, shift: u32) -> Self {
debug_assert!(shift <= Uint::<EXTRA>::BITS);
let hi = self.1.shr_vartime(shift);
let carry = self.1.unbounded_shl_vartime(Uint::<EXTRA>::BITS - shift);
let mut lo = self.0.shr_vartime(shift);
let limb_diff = LIMBS.saturating_sub(EXTRA) as u32;
let carry = carry
.resize::<LIMBS>()
.unbounded_shl_by_limbs_vartime(limb_diff);
lo = lo.bitxor(&carry);
Self(lo, hi)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct ExtendedInt<const LIMBS: usize, const EXTENSION_LIMBS: usize>(
Uint<LIMBS>,
Uint<EXTENSION_LIMBS>,
);
impl<const LIMBS: usize, const EXTRA: usize> ExtendedInt<LIMBS, EXTRA> {
pub(super) const ZERO: Self = Self(Uint::ZERO, Uint::ZERO);
pub(super) const ONE: Self = Self(Uint::ONE, Uint::ZERO);
#[inline]
pub const fn from_product(lhs: Uint<LIMBS>, rhs: Uint<EXTRA>) -> Self {
ExtendedUint::from_product(lhs, rhs).as_extended_int()
}
pub(crate) const fn wrapping_mul<const RHS_LIMBS: usize>(
&self,
rhs: (&Uint<RHS_LIMBS>, &Choice),
) -> Self {
let (abs_self, self_is_negative) = self.abs_sign();
let (abs_rhs, rhs_is_negative) = rhs;
let mut abs_val = abs_self.wrapping_mul(abs_rhs);
abs_val.1 = abs_val.1.bitand(Int::<EXTRA>::SIGN_MASK.not().as_uint());
let val_is_negative = self_is_negative.xor(*rhs_is_negative);
abs_val.wrapping_neg_if(val_is_negative).as_extended_int()
}
#[inline]
pub const fn as_extended_uint(&self) -> ExtendedUint<LIMBS, EXTRA> {
ExtendedUint(self.0, self.1)
}
#[inline]
pub const fn wrapping_neg_if(&self, negate: Choice) -> Self {
self.as_extended_uint()
.wrapping_neg_if(negate)
.as_extended_int()
}
#[inline]
pub(crate) const fn wrapping_add(&self, rhs: &Self) -> Self {
let (lo, carry) = self.0.carrying_add(&rhs.0, Limb::ZERO);
let (hi, _) = self.1.carrying_add(&rhs.1, carry);
Self(lo, hi)
}
#[inline]
pub const fn wrapping_sub(&self, rhs: &Self) -> Self {
let (lo, borrow) = self.0.borrowing_sub(&rhs.0, Limb::ZERO);
let (hi, _) = self.1.borrowing_sub(&rhs.1, borrow);
Self(lo, hi)
}
#[inline]
pub const fn dropped_abs_sign(&self) -> (Uint<LIMBS>, Choice) {
let (abs, sgn) = self.abs_sign();
(abs.0, sgn)
}
#[inline]
pub const fn abs_sign(&self) -> (ExtendedUint<LIMBS, EXTRA>, Choice) {
let is_negative = self.1.as_int().is_negative();
(
self.wrapping_neg_if(is_negative).as_extended_uint(),
is_negative,
)
}
#[inline]
pub const fn bounded_div_2k<const UPPER_BOUND: u32>(&self, k: u32) -> Self {
let (abs, sgn) = self.abs_sign();
abs.bounded_shr::<UPPER_BOUND>(k)
.wrapping_neg_if(sgn)
.as_extended_int()
}
#[inline]
pub const fn div_2k_vartime(&self, k: u32) -> Self {
let (abs, sgn) = self.abs_sign();
abs.shr_vartime(k).wrapping_neg_if(sgn).as_extended_int()
}
}
#[cfg(test)]
mod tests {
use super::ExtendedUint;
use crate::{U64, Uint};
impl<const LIMBS: usize, const EXTRA: usize> ExtendedUint<LIMBS, EXTRA> {
#[inline]
const fn as_elements(&self) -> (Uint<LIMBS>, Uint<EXTRA>) {
(self.0, self.1)
}
}
const A: ExtendedUint<{ U64::LIMBS }, { U64::LIMBS }> = ExtendedUint::from_product(
U64::from_u64(68146184546341u64),
U64::from_u64(873817114763u64),
);
const B: ExtendedUint<{ U64::LIMBS }, { U64::LIMBS }> = ExtendedUint::from_product(
U64::from_u64(7772181434148543u64),
U64::from_u64(6665138352u64),
);
#[test]
fn bounded_shr() {
let a = ExtendedUint::<4, 2>(Uint::ZERO, Uint::MAX);
let actual = a.bounded_shr::<32>(20);
assert_eq!(
actual.as_elements(),
(Uint::MAX.shl(Uint::<4>::BITS - 20), Uint::MAX.shr(20)),
);
}
#[test]
fn test_from_product() {
assert_eq!(
A.as_elements(),
(U64::from(13454091406951429143u64), U64::from(3228065u64))
);
assert_eq!(
B.as_elements(),
(U64::from(1338820589698724688u64), U64::from(2808228u64))
);
}
#[test]
fn test_wrapping_sub() {
assert_eq!(
A.as_extended_int()
.wrapping_sub(&B.as_extended_int())
.as_extended_uint()
.as_elements(),
(U64::from(12115270817252704455u64), U64::from(419837u64))
);
}
}