From 1e860fe771772402ca76788c334ad756ce2ac2e8 Mon Sep 17 00:00:00 2001 From: mrwulf Date: Sat, 4 Jul 2026 17:41:50 +0200 Subject: [PATCH] Aeneas-compat: check_scalar via explicit ell-compare + from_bytes_mod_order Replaces from_canonical_bytes (subtle CtOption / black_box machinery the extractor cannot interpret) with an explicit little-endian comparison of the scalar bytes against ell, then from_bytes_mod_order (the identity on canonical input). Value-level semantics identical; the verification path is variable-time throughout, so the constant-time construction is not required. Co-Authored-By: Claude Fable 5 --- ed25519-dalek/src/signature.rs | 35 +++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/ed25519-dalek/src/signature.rs b/ed25519-dalek/src/signature.rs index a86af7c..0634857 100644 --- a/ed25519-dalek/src/signature.rs +++ b/ed25519-dalek/src/signature.rs @@ -92,12 +92,41 @@ fn check_scalar(bytes: [u8; 32]) -> Result { } /// Ensures that the scalar `s` of a signature is within the bounds [0, ℓ) +/// +/// AENEAS-COMPAT (formal verification): explicit little-endian comparison +/// against ℓ followed by `from_bytes_mod_order` (the identity on canonical +/// bytes) — value-level semantics identical to +/// `Scalar::from_canonical_bytes(bytes).into()`; the subtle machinery's +/// `black_box` internals defeat the extractor, and the verification path is +/// variable-time throughout. #[cfg(not(feature = "legacy_compatibility"))] #[inline(always)] fn check_scalar(bytes: [u8; 32]) -> Result { - match Scalar::from_canonical_bytes(bytes).into() { - None => Err(InternalError::ScalarFormat.into()), - Some(x) => Ok(x), + /// ℓ = 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 < ℓ, 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(InternalError::ScalarFormat.into()) } }