diff --git a/curve25519/solana-ed25519/src/backend.rs b/curve25519/solana-ed25519/src/backend.rs index 0993c4f..ee71035 100644 --- a/curve25519/solana-ed25519/src/backend.rs +++ b/curve25519/solana-ed25519/src/backend.rs @@ -238,7 +238,8 @@ pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> Edwa /// Compute \\(a_1 A_1 + a_2 A_2 + b B\\) in variable time, where \\(B\\) is the Ed25519 basepoint. /// -/// This function is optimized for the case where \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\). +/// This function uses an optimized path when \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\), +/// and falls back to general scalar multiplication otherwise. #[allow(non_snake_case)] pub fn vartime_triple_base_mul_128_128_256( a1: &Scalar, @@ -246,14 +247,39 @@ pub fn vartime_triple_base_mul_128_128_256( a2: &Scalar, A2: &EdwardsPoint, b: &Scalar, +) -> EdwardsPoint { + if !scalar_fits_in_128_bits(a1) || !scalar_fits_in_128_bits(a2) { + return (a1 * A1) + (a2 * A2) + EdwardsPoint::mul_base(b); + } + + vartime_triple_base_mul_128_128_256_prechecked(a1, A1, a2, A2, b) +} + +/// Compute \\(a_1 A_1 + a_2 A_2 + b B\\) using the optimized 128/128/256-bit path. +/// +/// Callers must ensure \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\). +#[allow(non_snake_case)] +pub(crate) fn vartime_triple_base_mul_128_128_256_prechecked( + a1: &Scalar, + A1: &EdwardsPoint, + a2: &Scalar, + A2: &EdwardsPoint, + b: &Scalar, ) -> EdwardsPoint { match get_selected_backend() { #[cfg(target_arch = "x86_64")] BackendKind::Avx2 => { - vector::scalar_mul::vartime_triple_base::spec_avx2::mul_128_128_256(a1, A1, a2, A2, b) + vector::scalar_mul::vartime_triple_base::spec_avx2::mul_128_128_256_prechecked( + a1, A1, a2, A2, b, + ) } BackendKind::Serial => { - serial::scalar_mul::vartime_triple_base::mul_128_128_256(a1, A1, a2, A2, b) + serial::scalar_mul::vartime_triple_base::mul_128_128_256_prechecked(a1, A1, a2, A2, b) } } } + +#[inline] +fn scalar_fits_in_128_bits(scalar: &Scalar) -> bool { + scalar.as_bytes()[16..32].iter().all(|&byte| byte == 0) +} diff --git a/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs b/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs index 3fae118..1eb81a6 100644 --- a/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs +++ b/curve25519/solana-ed25519/src/backend/serial/scalar_mul/vartime_triple_base.rs @@ -24,6 +24,12 @@ use crate::window::NafLookupTable5; /// This function is optimized for the case where \\(a_1\\) and \\(a_2\\) are known to be less than /// \\(2^{128}\\), while \\(b\\) is a full 256-bit scalar. /// +/// # Precondition +/// +/// Callers must ensure \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\). Use +/// `vartime_triple_base_mul_128_128_256` for a checked wrapper that falls back +/// to general scalar multiplication for full-width scalars. +/// /// # Optimization Strategy /// /// The function decomposes the 256-bit scalar \\(b\\) as \\(b = b_{lo} + b_{hi} \cdot 2^{128}\\), @@ -50,17 +56,13 @@ use crate::window::NafLookupTable5; /// /// The algorithm shares doublings across all four scalar multiplications, processing /// only 128 bits instead of 256, providing approximately 2x speedup over the naive approach. -pub fn mul_128_128_256( +pub(crate) fn mul_128_128_256_prechecked( a1: &Scalar, A1: &EdwardsPoint, a2: &Scalar, A2: &EdwardsPoint, b: &Scalar, ) -> EdwardsPoint { - // assert that a1 and a2 are less than 2^128 - debug_assert!(a1.as_bytes()[16..32].iter().all(|&b| b == 0)); - debug_assert!(a2.as_bytes()[16..32].iter().all(|&b| b == 0)); - // Decompose b into b_lo (lower 128 bits) and b_hi (upper 128 bits) // b = b_lo + b_hi * 2^128 let b_bytes = b.as_bytes(); @@ -183,7 +185,7 @@ mod test { let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(3u64); // Compute using the optimized triple-base function - let result = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); // Compute using naive addition let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; @@ -217,7 +219,7 @@ mod test { let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(7u64); // Test the optimized 128-bit version - let result_128 = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result_128 = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); // Compute expected result let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; @@ -234,7 +236,7 @@ mod test { let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(2u64); let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(3u64); - let result = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); let expected = a2 * A2 + b * constants::ED25519_BASEPOINT_POINT; assert_eq!(result, expected); @@ -249,7 +251,7 @@ mod test { let A1 = EdwardsPoint::identity(); let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(3u64); - let result = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); let expected = a2 * A2 + b * constants::ED25519_BASEPOINT_POINT; assert_eq!(result, expected); @@ -265,7 +267,7 @@ mod test { let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(11u64); let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(13u64); - let result_optimized = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result_optimized = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); let result_general = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; assert_eq!(result_optimized, result_general); @@ -287,7 +289,7 @@ mod test { let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(17u64); let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(19u64); - let result = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; assert_eq!(result, expected); @@ -322,7 +324,7 @@ mod test { let A2 = constants::ED25519_BASEPOINT_POINT * A2_scalar; // Compute using the optimized triple-base function - let result_optimized = mul_128_128_256(&a1, &A1, &a2, &A2, &b); + let result_optimized = mul_128_128_256_prechecked(&a1, &A1, &a2, &A2, &b); // Compute using raw operations: a1*A1 + a2*A2 + b*B let expected = (a1 * A1 + a2 * A2) + b * constants::ED25519_BASEPOINT_POINT; diff --git a/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs b/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs index 0066c68..fd70973 100644 --- a/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs +++ b/curve25519/solana-ed25519/src/backend/vector/scalar_mul/vartime_triple_base.rs @@ -35,6 +35,12 @@ pub mod spec { /// This function is optimized for the case where \\(a_1\\) and \\(a_2\\) are known to be less than /// \\(2^{128}\\), while \\(b\\) is a full 256-bit scalar. /// + /// # Precondition + /// + /// Callers must ensure \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\). Use + /// `vartime_triple_base_mul_128_128_256` for a checked wrapper that falls back + /// to general scalar multiplication for full-width scalars. + /// /// # Optimization Strategy /// /// The function decomposes the 256-bit scalar \\(b\\) as \\(b = b_{lo} + b_{hi} \cdot 2^{128}\\), @@ -63,17 +69,13 @@ pub mod spec { /// /// This SIMD implementation uses vectorized point operations (AVX2 or AVX512-IFMA) for /// improved performance over the serial backend. - pub fn mul_128_128_256( + pub(crate) fn mul_128_128_256_prechecked( a1: &Scalar, A1: &EdwardsPoint, a2: &Scalar, A2: &EdwardsPoint, b: &Scalar, ) -> EdwardsPoint { - // assert that a1 and a2 are less than 2^128 - debug_assert!(a1.as_bytes()[16..32].iter().all(|&b| b == 0)); - debug_assert!(a2.as_bytes()[16..32].iter().all(|&b| b == 0)); - // Decompose b into b_lo (lower 128 bits) and b_hi (upper 128 bits) // b = b_lo + b_hi * 2^128 let b_bytes = b.as_bytes(); diff --git a/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs b/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs index 6e6a361..5dd4a97 100644 --- a/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs +++ b/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs @@ -411,8 +411,11 @@ impl VerificationKey { // Compute τs let ts = tau * s; let A = if flip_h { -self.minus_A } else { self.minus_A }; - // Compute the multi-scalar multiplication - let result = EdwardsPoint::vartime_triple_scalar_mul_basepoint(&tau, &neg_R, &rho, &A, &ts); + // HEEA decomposition guarantees tau and rho fit the optimized + // 128/128/256-bit multiplication path. + let result = crate::backend::vartime_triple_base_mul_128_128_256_prechecked( + &tau, &neg_R, &rho, &A, &ts, + ); if result.mul_by_cofactor().is_identity() { Ok(()) diff --git a/curve25519/solana-ed25519/src/edwards.rs b/curve25519/solana-ed25519/src/edwards.rs index e586984..b6fe18b 100644 --- a/curve25519/solana-ed25519/src/edwards.rs +++ b/curve25519/solana-ed25519/src/edwards.rs @@ -1075,7 +1075,8 @@ impl EdwardsPoint { /// Compute \\(a_1 A_1 + a_2 A_2 + b B\\) in variable time, where \\(B\\) is the Ed25519 basepoint. /// - /// This function is optimized for the case where \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\). + /// This function is optimized for the case where \\(a_1\\) and \\(a_2\\) are less than \\(2^{128}\\), + /// and falls back to general scalar multiplication for full-width scalars. /// /// # Example /// @@ -2420,6 +2421,28 @@ mod test { assert_eq!(result.compress(), DOUBLE_SCALAR_MULT_RESULT); } + #[test] + fn triple_scalar_mul_basepoint_accepts_full_width_scalars() { + let mut a1_bytes = [0u8; 32]; + a1_bytes[0] = 7; + a1_bytes[16] = 1; + let a1 = Scalar::from_canonical_bytes(a1_bytes).unwrap(); + + let mut a2_bytes = [0u8; 32]; + a2_bytes[0] = 11; + a2_bytes[24] = 1; + let a2 = Scalar::from_canonical_bytes(a2_bytes).unwrap(); + + let b = B_SCALAR; + let A1 = constants::ED25519_BASEPOINT_POINT * Scalar::from(17u64); + let A2 = constants::ED25519_BASEPOINT_POINT * Scalar::from(19u64); + + let result = EdwardsPoint::vartime_triple_scalar_mul_basepoint(&a1, &A1, &a2, &A2, &b); + let expected = (a1 * A1) + (a2 * A2) + EdwardsPoint::mul_base(&b); + + assert_eq!(result, expected); + } + #[test] #[cfg(feature = "alloc")] fn multiscalar_mul_vs_ed25519py() {