mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-06 20:41:07 +00:00
Aeneas-compat: verified-verification entry points + serial-pin cfg
Pure refactors for the Charon/Aeneas extraction pipeline; production behavior unchanged (both default and pinned configs cargo-check clean, pre-existing warnings only). - ed_sigs::sha512_hash3: single-call SHA-512 oracle, semantically Sha512(r || a || m); a monomorphic signature with no foreign types lets the extractor treat the hash as one opaque oracle (sha2-0.11 stack). - VerificationKey::verify_sha512 (+ recompute_r_sha512, a_bytes_nonzero, check_scalar_canonical, is_legacy_excluded_r): semantically identical to verify_dalek with each step spelled extractor-friendly - derived array PartialEq/contains as explicit index loops, and Scalar::from_canonical_bytes (subtle internals defeat the extractor) as an explicit s < l byte compare + from_bytes_mod_order (the identity on canonical bytes). Signature accessors each called exactly once. - SIMD gates: cfg(target_arch = "x86_64") becomes cfg(all(target_arch = "x86_64", not(curve25519_serial_only))). Default builds are identical (the new cfg is never set); extraction builds pass RUSTFLAGS=--cfg curve25519_serial_only so the AVX2 dispatch arm compiles out and backend selection is the real constant Serial - the same serial-pin mechanism upstream curve25519-dalek provides natively. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1c8497d682
commit
d275613c37
5 changed files with 161 additions and 15 deletions
|
|
@ -90,4 +90,5 @@ check-cfg = [
|
||||||
'cfg(curve25519_backend, values("simd"))',
|
'cfg(curve25519_backend, values("simd"))',
|
||||||
'cfg(curve25519_diagnostics, values("build"))',
|
'cfg(curve25519_diagnostics, values("build"))',
|
||||||
'cfg(curve25519_bits, values("64"))',
|
'cfg(curve25519_bits, values("64"))',
|
||||||
|
'cfg(curve25519_serial_only)',
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -39,19 +39,19 @@ use crate::Scalar;
|
||||||
|
|
||||||
pub mod serial;
|
pub mod serial;
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
pub mod vector;
|
pub mod vector;
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
enum BackendKind {
|
enum BackendKind {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
Avx2,
|
Avx2,
|
||||||
Serial,
|
Serial,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_selected_backend() -> BackendKind {
|
fn get_selected_backend() -> BackendKind {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
{
|
{
|
||||||
cpufeatures::new!(cpuid_avx2, "avx2");
|
cpufeatures::new!(cpuid_avx2, "avx2");
|
||||||
let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init();
|
let token_avx2: cpuid_avx2::InitToken = cpuid_avx2::init();
|
||||||
|
|
@ -74,7 +74,7 @@ where
|
||||||
use crate::traits::VartimeMultiscalarMul;
|
use crate::traits::VartimeMultiscalarMul;
|
||||||
|
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => {
|
BackendKind::Avx2 => {
|
||||||
vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::<I, J>(
|
vector::scalar_mul::pippenger::spec_avx2::Pippenger::optional_multiscalar_mul::<I, J>(
|
||||||
scalars, points,
|
scalars, points,
|
||||||
|
|
@ -90,7 +90,7 @@ where
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
pub(crate) enum VartimePrecomputedStraus {
|
pub(crate) enum VartimePrecomputedStraus {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
|
Avx2(vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus),
|
||||||
Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus),
|
Scalar(serial::scalar_mul::precomputed_straus::VartimePrecomputedStraus),
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ impl VartimePrecomputedStraus {
|
||||||
use crate::traits::VartimePrecomputedMultiscalarMul;
|
use crate::traits::VartimePrecomputedMultiscalarMul;
|
||||||
|
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => VartimePrecomputedStraus::Avx2(
|
BackendKind::Avx2 => VartimePrecomputedStraus::Avx2(
|
||||||
vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(
|
vector::scalar_mul::precomputed_straus::spec_avx2::VartimePrecomputedStraus::new(
|
||||||
static_points,
|
static_points,
|
||||||
|
|
@ -124,7 +124,7 @@ impl VartimePrecomputedStraus {
|
||||||
use crate::traits::VartimePrecomputedMultiscalarMul;
|
use crate::traits::VartimePrecomputedMultiscalarMul;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
VartimePrecomputedStraus::Avx2(inner) => inner.len(),
|
VartimePrecomputedStraus::Avx2(inner) => inner.len(),
|
||||||
VartimePrecomputedStraus::Scalar(inner) => inner.len(),
|
VartimePrecomputedStraus::Scalar(inner) => inner.len(),
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +135,7 @@ impl VartimePrecomputedStraus {
|
||||||
use crate::traits::VartimePrecomputedMultiscalarMul;
|
use crate::traits::VartimePrecomputedMultiscalarMul;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
VartimePrecomputedStraus::Avx2(inner) => inner.is_empty(),
|
VartimePrecomputedStraus::Avx2(inner) => inner.is_empty(),
|
||||||
VartimePrecomputedStraus::Scalar(inner) => inner.is_empty(),
|
VartimePrecomputedStraus::Scalar(inner) => inner.is_empty(),
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +157,7 @@ impl VartimePrecomputedStraus {
|
||||||
use crate::traits::VartimePrecomputedMultiscalarMul;
|
use crate::traits::VartimePrecomputedMultiscalarMul;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
VartimePrecomputedStraus::Avx2(inner) => inner.optional_mixed_multiscalar_mul(
|
VartimePrecomputedStraus::Avx2(inner) => inner.optional_mixed_multiscalar_mul(
|
||||||
static_scalars,
|
static_scalars,
|
||||||
dynamic_scalars,
|
dynamic_scalars,
|
||||||
|
|
@ -184,7 +184,7 @@ where
|
||||||
use crate::traits::MultiscalarMul;
|
use crate::traits::MultiscalarMul;
|
||||||
|
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => {
|
BackendKind::Avx2 => {
|
||||||
vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::<I, J>(scalars, points)
|
vector::scalar_mul::straus::spec_avx2::Straus::multiscalar_mul::<I, J>(scalars, points)
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ where
|
||||||
use crate::traits::VartimeMultiscalarMul;
|
use crate::traits::VartimeMultiscalarMul;
|
||||||
|
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => {
|
BackendKind::Avx2 => {
|
||||||
vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::<I, J>(
|
vector::scalar_mul::straus::spec_avx2::Straus::optional_multiscalar_mul::<I, J>(
|
||||||
scalars, points,
|
scalars, points,
|
||||||
|
|
@ -220,7 +220,7 @@ where
|
||||||
/// Perform constant-time, variable-base scalar multiplication.
|
/// Perform constant-time, variable-base scalar multiplication.
|
||||||
pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
|
pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint {
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
|
BackendKind::Avx2 => vector::scalar_mul::variable_base::spec_avx2::mul(point, scalar),
|
||||||
BackendKind::Serial => serial::scalar_mul::variable_base::mul(point, scalar),
|
BackendKind::Serial => serial::scalar_mul::variable_base::mul(point, scalar),
|
||||||
}
|
}
|
||||||
|
|
@ -230,7 +230,7 @@ pub fn variable_base_mul(point: &EdwardsPoint, scalar: &Scalar) -> EdwardsPoint
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
|
pub fn vartime_double_base_mul(a: &Scalar, A: &EdwardsPoint, b: &Scalar) -> EdwardsPoint {
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => vector::scalar_mul::vartime_double_base::spec_avx2::mul(a, A, b),
|
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),
|
BackendKind::Serial => serial::scalar_mul::vartime_double_base::mul(a, A, b),
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +267,7 @@ pub(crate) fn vartime_triple_base_mul_128_128_256_prechecked(
|
||||||
b: &Scalar,
|
b: &Scalar,
|
||||||
) -> EdwardsPoint {
|
) -> EdwardsPoint {
|
||||||
match get_selected_backend() {
|
match get_selected_backend() {
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
BackendKind::Avx2 => {
|
BackendKind::Avx2 => {
|
||||||
vector::scalar_mul::vartime_triple_base::spec_avx2::mul_128_128_256_prechecked(
|
vector::scalar_mul::vartime_triple_base::spec_avx2::mul_128_128_256_prechecked(
|
||||||
a1, A1, a2, A2, b,
|
a1, A1, a2, A2, b,
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,19 @@ pub(crate) fn scalar_from_sha512(hash: Sha512) -> Scalar {
|
||||||
|
|
||||||
scalar
|
scalar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// AENEAS-COMPAT: single-call SHA-512 oracle for the verified verification
|
||||||
|
/// path. Semantically `Sha512(r || a || m)` — identical to hashing via the
|
||||||
|
/// incremental `Digest` API. A single monomorphic function whose signature
|
||||||
|
/// carries no foreign types lets the extractor treat the whole hash
|
||||||
|
/// computation as one opaque oracle.
|
||||||
|
pub(crate) fn sha512_hash3(r: &[u8], a: &[u8], m: &[u8]) -> [u8; 64] {
|
||||||
|
let mut h = Sha512::default();
|
||||||
|
Digest::update(&mut h, r);
|
||||||
|
Digest::update(&mut h, a);
|
||||||
|
Digest::update(&mut h, m);
|
||||||
|
let output = h.finalize();
|
||||||
|
let mut bytes = [0u8; 64];
|
||||||
|
bytes.copy_from_slice(output.as_slice());
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -462,3 +462,132 @@ impl VerificationKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// AENEAS-COMPAT verified-verification entry points.
|
||||||
|
//
|
||||||
|
// `verify_sha512` is semantically `verify_dalek` with each step spelled in
|
||||||
|
// extractor-friendly form: the derived array `PartialEq`/`contains` become
|
||||||
|
// explicit index loops, and `Scalar::from_canonical_bytes` (whose `subtle`
|
||||||
|
// internals defeat the extractor) becomes an explicit `s < l` byte compare
|
||||||
|
// followed by `Scalar::from_bytes_mod_order` (the identity on canonical
|
||||||
|
// bytes). The verification path is variable-time throughout, as upstream's.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// `bytes` interpreted little-endian is a canonical scalar (< l)? If so the
|
||||||
|
/// scalar itself; value-level semantics identical to
|
||||||
|
/// `Scalar::from_canonical_bytes(bytes).into()`.
|
||||||
|
fn check_scalar_canonical(bytes: [u8; 32]) -> Result<Scalar, Error> {
|
||||||
|
/// l = 2^252 + 27742317777372353535851937790883648493, little-endian.
|
||||||
|
const L_BYTES: [u8; 32] = [
|
||||||
|
237, 211, 245, 92, 26, 99, 18, 88, 214, 156, 247, 162, 222, 249, 222,
|
||||||
|
20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16,
|
||||||
|
];
|
||||||
|
// bytes < l, most-significant byte first; the first differing byte decides.
|
||||||
|
let mut lt = false;
|
||||||
|
let mut decided = false;
|
||||||
|
let mut i = 32;
|
||||||
|
while i > 0 {
|
||||||
|
let j = i - 1;
|
||||||
|
if !decided {
|
||||||
|
if bytes[j] < L_BYTES[j] {
|
||||||
|
lt = true;
|
||||||
|
decided = true;
|
||||||
|
} else if bytes[j] > L_BYTES[j] {
|
||||||
|
decided = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
if lt {
|
||||||
|
Ok(Scalar::from_bytes_mod_order(bytes))
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Explicit-loop `LEGACY_EXCLUDED_R_ENCODINGS.contains(r)`.
|
||||||
|
fn is_legacy_excluded_r(r: &[u8; 32]) -> bool {
|
||||||
|
let mut found = false;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 11 {
|
||||||
|
let mut eq = true;
|
||||||
|
let mut j = 0;
|
||||||
|
while j < 32 {
|
||||||
|
if LEGACY_EXCLUDED_R_ENCODINGS[i][j] != r[j] {
|
||||||
|
eq = false;
|
||||||
|
}
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
if eq {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
found
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VerificationKey {
|
||||||
|
/// Explicit-loop `self.A_bytes.0 != [0; 32]`.
|
||||||
|
fn a_bytes_nonzero(&self) -> bool {
|
||||||
|
let mut nonzero = false;
|
||||||
|
let mut i = 0;
|
||||||
|
while i < 32 {
|
||||||
|
if self.A_bytes.0[i] != 0 {
|
||||||
|
nonzero = true;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
nonzero
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recompute the expected canonical `R` encoding:
|
||||||
|
/// `compress([k]*(-A) + [s]*B)` with `k = SHA-512(R || A || msg) mod l`.
|
||||||
|
fn recompute_r_sha512(
|
||||||
|
&self,
|
||||||
|
r_bytes: &[u8; 32],
|
||||||
|
s: &Scalar,
|
||||||
|
msg: &[u8],
|
||||||
|
) -> CompressedEdwardsY {
|
||||||
|
let k = Scalar::from_bytes_mod_order_wide(&super::sha512_hash3(
|
||||||
|
&r_bytes[..],
|
||||||
|
&self.A_bytes.0[..],
|
||||||
|
msg,
|
||||||
|
));
|
||||||
|
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &self.minus_A, s).compress()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Semantically identical to [`Self::verify_dalek`]; see the module
|
||||||
|
/// comment above for the extractor-friendly spellings. The signature's
|
||||||
|
/// `R`/`s` accessors are each called exactly once.
|
||||||
|
pub fn verify_sha512(&self, sig: &Signature, msg: &[u8]) -> Result<(), Error> {
|
||||||
|
// (parameter named `sig`: the extractor's generated code would
|
||||||
|
// otherwise shadow the `signature::` crate namespace)
|
||||||
|
let r_bytes: [u8; 32] = *sig.r_bytes();
|
||||||
|
let s_bytes: [u8; 32] = *sig.s_bytes();
|
||||||
|
|
||||||
|
if !self.a_bytes_nonzero() {
|
||||||
|
return Err(Error::InvalidSignature);
|
||||||
|
}
|
||||||
|
if is_legacy_excluded_r(&r_bytes) {
|
||||||
|
return Err(Error::InvalidSignature);
|
||||||
|
}
|
||||||
|
let s = check_scalar_canonical(s_bytes)?;
|
||||||
|
|
||||||
|
let expected_r = self.recompute_r_sha512(&r_bytes, &s, msg);
|
||||||
|
let e = expected_r.as_bytes();
|
||||||
|
let mut equal = true;
|
||||||
|
let mut k = 0;
|
||||||
|
while k < 32 {
|
||||||
|
if e[k] != r_bytes[k] {
|
||||||
|
equal = false;
|
||||||
|
}
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
if equal {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::InvalidSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ impl Scalar {
|
||||||
|
|
||||||
/// Construct a `Scalar` from bytes that are known to be canonical.
|
/// Construct a `Scalar` from bytes that are known to be canonical.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(all(target_arch = "x86_64", not(curve25519_serial_only)))]
|
||||||
pub(crate) const fn from_canonical_bytes_unchecked(bytes: [u8; 32]) -> Scalar {
|
pub(crate) const fn from_canonical_bytes_unchecked(bytes: [u8; 32]) -> Scalar {
|
||||||
Scalar { bytes }
|
Scalar { bytes }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue