// -*- 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 // - Henry de Valence //! **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(scalars: I, points: J) -> Option where I: IntoIterator, I::Item: core::borrow::Borrow, J: IntoIterator>, { 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::( scalars, points, ) } BackendKind::Serial => { serial::scalar_mul::pippenger::Pippenger::optional_multiscalar_mul::( 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(static_points: I) -> Self where I: IntoIterator, I::Item: core::borrow::Borrow, { 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( &self, static_scalars: I, dynamic_scalars: J, dynamic_points: K, ) -> Option where I: IntoIterator, I::Item: core::borrow::Borrow, J: IntoIterator, J::Item: core::borrow::Borrow, K: IntoIterator>, { 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(scalars: I, points: J) -> EdwardsPoint where I: IntoIterator, I::Item: core::borrow::Borrow, J: IntoIterator, J::Item: core::borrow::Borrow, { use crate::traits::MultiscalarMul; match get_selected_backend() { #[cfg(target_arch = "x86_64")] BackendKind::Avx2 => { vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::(scalars, points) } BackendKind::Serial => { serial::scalar_mul::straus::Straus::multiscalar_mul::(scalars, points) } } } #[allow(missing_docs)] #[cfg(feature = "alloc")] pub fn straus_optional_multiscalar_mul(scalars: I, points: J) -> Option where I: IntoIterator, I::Item: core::borrow::Borrow, J: IntoIterator>, { 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::( scalars, points, ) } BackendKind::Serial => { serial::scalar_mul::straus::Straus::optional_multiscalar_mul::(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) }