cnfy-uint 0.2.3

Zero-dependency 256-bit unsigned integer arithmetic for cryptographic applications
Documentation
//! Widening multiplication of [`U256`] by a `u64` scalar into a [`U320`].
use super::U256;
use crate::u320::U320;
use core::ops::Mul;

/// Widening multiplication of a 256-bit integer by a `u64` scalar,
/// producing a 320-bit result.
///
/// Each of the four `u64` limbs of `self` is multiplied by `rhs` into a
/// `u128` intermediate, then accumulated with carry propagation across
/// five result limbs. The carry from the most significant limb becomes
/// the fifth (highest) limb of the [`U320`] output.
///
/// This operation cannot overflow: `(2^256 − 1) × (2^64 − 1) < 2^320`.
///
/// # Examples
///
/// ```
/// use cnfy_uint::u256::U256;
/// use cnfy_uint::u320::U320;
///
/// let a = U256::from_be_limbs([0, 0, 0, 7]);
/// let result = a * 6u64;
/// assert_eq!(result, U320::from_be_limbs([0, 0, 0, 0, 42]));
/// ```
impl Mul<u64> for U256 {
    type Output = U320;

    #[inline]
    fn mul(self, rhs: u64) -> U320 {
        let rhs = rhs as u128;

        let p0 = (self.0[0] as u128) * rhs;
        let p1 = (self.0[1] as u128) * rhs + (p0 >> 64);
        let p2 = (self.0[2] as u128) * rhs + (p1 >> 64);
        let p3 = (self.0[3] as u128) * rhs + (p2 >> 64);

        U320([
            p0 as u64,
            p1 as u64,
            p2 as u64,
            p3 as u64,
            (p3 >> 64) as u64,
        ])
    }
}

#[cfg(test)]
mod ai_tests {
    use super::*;

    /// 7 × 6 = 42.
    #[test]
    fn small_product() {
        let a = U256::from_be_limbs([0, 0, 0, 7]);
        assert_eq!(a * 6u64, U320::from_be_limbs([0, 0, 0, 0, 42]));
    }

    /// Anything times zero is zero.
    #[test]
    fn mul_by_zero() {
        let a = U256::from_be_limbs([0x1234, 0x5678, 0x9ABC, 0xDEF0]);
        assert_eq!(a * 0u64, U320::from_be_limbs([0; 5]));
    }

    /// Multiplicative identity: x × 1 = x in the lower 256 bits.
    #[test]
    fn mul_by_one() {
        let a = U256::from_be_limbs([0x1234, 0x5678, 0x9ABC, 0xDEF0]);
        assert_eq!(
            a * 1u64,
            U320::from_be_limbs([0, 0x1234, 0x5678, 0x9ABC, 0xDEF0])
        );
    }

    /// MAX × MAX_u64 produces correct 320-bit result.
    #[test]
    fn max_times_max_u64() {
        // (2^256 - 1) * (2^64 - 1) = 2^320 - 2^256 - 2^64 + 1
        let result = U256::MAX * u64::MAX;
        assert_eq!(
            result,
            U320::from_be_limbs([u64::MAX - 1, u64::MAX, u64::MAX, u64::MAX, 1]),
        );
    }

    /// Single limb × scalar that carries across limb boundaries.
    #[test]
    fn carry_propagation() {
        // 0xFFFFFFFFFFFFFFFF * 2 = 0x1_FFFFFFFFFFFFFFFE
        let a = U256::from_be_limbs([0, 0, 0, u64::MAX]);
        let result = a * 2u64;
        assert_eq!(result, U320::from_be_limbs([0, 0, 0, 1, u64::MAX - 1]));
    }

    /// Multi-limb value × scalar with cascading carries.
    #[test]
    fn full_width_carry() {
        let a = U256::from_be_limbs([u64::MAX, u64::MAX, u64::MAX, u64::MAX]);
        let result = a * 2u64;
        // (2^256 - 1) * 2 = 2^257 - 2
        // = [1, 0xFFFF...FFFF, 0xFFFF...FFFF, 0xFFFF...FFFF, 0xFFFF...FFFE]
        assert_eq!(
            result,
            U320::from_be_limbs([1, u64::MAX, u64::MAX, u64::MAX, u64::MAX - 1]),
        );
    }
}