mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-04 20:24:04 +00:00
fix 128bits scalar precondition (#44)
* Add prechecked optimized triple-base mul Introduce a prechecked 128/128/256 optimized path for vartime triple-base multiplication: vartime_triple_base_mul_128_128_256 now checks whether a1 and a2 fit in 128 bits and falls back to general multiplication if not. Add vartime_triple_base_mul_128_128_256_prechecked and corresponding serial/vector backend implementations (renamed to *_prechecked). Add scalar_fits_in_128_bits helper and update callers (verification_key) to use the prechecked path. Update docs/comments and add a test to ensure full-width scalars are handled by the fallback path. * bring back the docs * CI
This commit is contained in:
parent
cdc0688a81
commit
53383206b8
5 changed files with 79 additions and 23 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
Loading…
Reference in a new issue