anza-cryptography-source/curve25519/solana-ed25519/src/backend.rs
zz-sol 53383206b8
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
2026-06-16 08:59:35 -04:00

285 lines
9.2 KiB
Rust

// -*- mode: rust; -*-
//
// This file is part of curve25519-dalek.
// Copyright (c) 2016-2021 isis lovecruft
// Copyright (c) 2016-2019 Henry de Valence
// See LICENSE for licensing information.
//
// Authors:
// - isis agora lovecruft <isis@patternsinthevoid.net>
// - Henry de Valence <hdevalence@hdevalence.ca>
//! **INTERNALS:** Pluggable implementations for different architectures.
//!
//! The backend code is split into two parts: a serial backend,
//! and a vector backend.
//!
//! The [`serial`] backend contains 32- and 64-bit implementations of
//! field arithmetic and scalar arithmetic, as well as implementations
//! of point operations using the mixed-model strategy (passing
//! between different curve models depending on the operation).
//!
//! The [`vector`] backend contains implementations of vectorized
//! field arithmetic, used to implement point operations using a novel
//! implementation strategy derived from parallel formulas of Hisil,
//! Wong, Carter, and Dawson.
//!
//! Because the two strategies give rise to different curve models,
//! it's not possible to reuse exactly the same scalar multiplication
//! code (or to write it generically), so both serial and vector
//! backends contain matching implementations of scalar multiplication
//! algorithms. These are intended to be selected by a `#[cfg]`-based
//! type alias.
//!
//! The [`vector`] backend is selected by the `simd_backend` cargo
//! feature; it uses the [`serial`] backend for non-vectorized operations.
use crate::EdwardsPoint;
use crate::Scalar;
pub mod serial;
#[cfg(target_arch = "x86_64")]
pub mod vector;
#[derive(Copy, Clone)]
enum BackendKind {
#[cfg(target_arch = "x86_64")]
Avx2,
Serial,
}
#[inline]
fn get_selected_backend() -> BackendKind {
#[cfg(target_arch = "x86_64")]
{
cpufeatures::new!(cpuid_avx2, "avx2");
let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init();
if token_avx2.get() {
return BackendKind::Avx2;
}
}
BackendKind::Serial
}
#[allow(missing_docs)]
#[cfg(feature = "alloc")]
pub fn pippenger_optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<EdwardsPoint>
where
I: IntoIterator,
I::Item: core::borrow::Borrow<Scalar>,
J: IntoIterator<Item = Option<EdwardsPoint>>,
{
use crate::traits::VartimeMultiscalarMul;
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => {
vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::<I, J>(
scalars, points,
)
}
BackendKind::Serial => {
serial::scalar_mul::pippenger::Pippenger::optional_multiscalar_mul::<I, J>(
scalars, points,
)
}
}
}
#[cfg(feature = "alloc")]
pub(crate) enum VartimePrecomputedStraus {
#[cfg(target_arch = "x86_64")]
Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus),
}
#[cfg(feature = "alloc")]
impl VartimePrecomputedStraus {
pub fn new<I>(static_points: I) -> Self
where
I: IntoIterator,
I::Item: core::borrow::Borrow<EdwardsPoint>,
{
use crate::traits::VartimePrecomputedMultiscalarMul;
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => VartimePrecomputedStraus::Avx2(
vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(
static_points,
),
),
BackendKind::Serial => VartimePrecomputedStraus::Scalar(
serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus::new(
static_points,
),
),
}
}
/// Return the number of static points in the precomputation.
pub fn len(&self) -> usize {
use crate::traits::VartimePrecomputedMultiscalarMul;
match self {
#[cfg(target_arch = "x86_64")]
VartimePrecomputedStraus::Avx2(inner) => inner.len(),
VartimePrecomputedStraus::Scalar(inner) => inner.len(),
}
}
/// Determine if the precomputation is empty.
pub fn is_empty(&self) -> bool {
use crate::traits::VartimePrecomputedMultiscalarMul;
match self {
#[cfg(target_arch = "x86_64")]
VartimePrecomputedStraus::Avx2(inner) => inner.is_empty(),
VartimePrecomputedStraus::Scalar(inner) => inner.is_empty(),
}
}
pub fn optional_mixed_multiscalar_mul<I, J, K>(
&self,
static_scalars: I,
dynamic_scalars: J,
dynamic_points: K,
) -> Option<EdwardsPoint>
where
I: IntoIterator,
I::Item: core::borrow::Borrow<Scalar>,
J: IntoIterator,
J::Item: core::borrow::Borrow<Scalar>,
K: IntoIterator<Item = Option<EdwardsPoint>>,
{
use crate::traits::VartimePrecomputedMultiscalarMul;
match self {
#[cfg(target_arch = "x86_64")]
VartimePrecomputedStraus::Avx2(inner) => inner.optional_mixed_multiscalar_mul(
static_scalars,
dynamic_scalars,
dynamic_points,
),
VartimePrecomputedStraus::Scalar(inner) => inner.optional_mixed_multiscalar_mul(
static_scalars,
dynamic_scalars,
dynamic_points,
),
}
}
}
#[allow(missing_docs)]
#[cfg(feature = "alloc")]
pub fn straus_multiscalar_mul<I, J>(scalars: I, points: J) -> EdwardsPoint
where
I: IntoIterator,
I::Item: core::borrow::Borrow<Scalar>,
J: IntoIterator,
J::Item: core::borrow::Borrow<EdwardsPoint>,
{
use crate::traits::MultiscalarMul;
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => {
vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::<I, J>(scalars, points)
}
BackendKind::Serial => {
serial::scalar_mul::straus::Straus::multiscalar_mul::<I, J>(scalars, points)
}
}
}
#[allow(missing_docs)]
#[cfg(feature = "alloc")]
pub fn straus_optional_multiscalar_mul<I, J>(scalars: I, points: J) -> Option<EdwardsPoint>
where
I: IntoIterator,
I::Item: core::borrow::Borrow<Scalar>,
J: IntoIterator<Item = Option<EdwardsPoint>>,
{
use crate::traits::VartimeMultiscalarMul;
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => {
vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::<I, J>(
scalars, points,
)
}
BackendKind::Serial => {
serial::scalar_mul::straus::Straus::optional_multiscalar_mul::<I, J>(scalars, points)
}
}
}
/// Perform constant-time, variable-base scalar multiplication.
pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
BackendKind::Serial => serial::scalar_mul::variable_base::mul(point, scalar),
}
}
/// Compute \\(aA + bB\\) in variable time, where \\(B\\) is the Ed25519 basepoint.
#[allow(non_snake_case)]
pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
match get_selected_backend() {
#[cfg(target_arch = "x86_64")]
BackendKind::Avx2 => vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b),
BackendKind::Serial => serial::scalar_mul::vartime_double_base::mul(a, A, b),
}
}
/// Compute \\(a_1 A_1 + a_2 A_2 + b B\\) in variable time, where \\(B\\) is the Ed25519 basepoint.
///
/// 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,
A1: &EdwardsPoint,
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_prechecked(
a1, A1, a2, A2, b,
)
}
BackendKind::Serial => {
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)
}