mirror of
https://github.com/saymrwulf/anza-cryptography-source.git
synced 2026-09-04 20:24:04 +00:00
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>
593 lines
21 KiB
Rust
593 lines
21 KiB
Rust
// -*- mode: rust; -*-
|
||
//
|
||
// This file is part of solana-ed25519's ed_sigs module, forked from ed25519-zebra.
|
||
// Original ed25519-zebra code: Copyright (c) Zcash Foundation contributors
|
||
// Modifications for HEEA: Copyright (c) 2025 curve25519-sol contributors
|
||
// See LICENSE-APACHE and LICENSE-MIT for licensing information.
|
||
//
|
||
// Modifications from ed25519-zebra:
|
||
// - Added `verify_zebra`, an accelerated verification path using the HEEA
|
||
// scalar decomposition from curve25519-sol's `HEEADecomposition` trait.
|
||
// See "Accelerating EdDSA Signature Verification with Faster Scalar Size
|
||
// Halving" (TCHES 2025) for the algorithm.
|
||
// - `verify` dispatches to `verify_zebra`, preserving ZIP-215 semantics.
|
||
|
||
use crate::{
|
||
edwards::{CompressedEdwardsY, EdwardsPoint},
|
||
scalar::Scalar,
|
||
traits::{HEEADecomposition, IsIdentity},
|
||
};
|
||
use core::convert::{TryFrom, TryInto};
|
||
use sha2::{Sha512, digest::Update};
|
||
#[cfg(feature = "zeroize")]
|
||
use zeroize::DefaultIsZeroes;
|
||
|
||
use ed25519::{Signature, signature::Verifier};
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
use pkcs8::der::asn1::BitStringRef;
|
||
#[cfg(feature = "pkcs8")]
|
||
use pkcs8::spki::{
|
||
AlgorithmIdentifierRef, DecodePublicKey, EncodePublicKey, Error as SpkiError,
|
||
SubjectPublicKeyInfoRef,
|
||
};
|
||
#[cfg(feature = "pkcs8")]
|
||
use pkcs8::{Document, ObjectIdentifier};
|
||
|
||
use super::{Error, scalar_from_sha512};
|
||
|
||
/// The length of an ed25519 `VerificationKey`, in bytes.
|
||
pub const VERIFICATION_KEY_LENGTH: usize = 32;
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.101.112"); // RFC 8410
|
||
#[cfg(feature = "pkcs8")]
|
||
const ALGORITHM_ID: AlgorithmIdentifierRef<'_> = AlgorithmIdentifierRef {
|
||
oid: OID,
|
||
parameters: None,
|
||
};
|
||
|
||
const LEGACY_EXCLUDED_R_ENCODINGS: [[u8; 32]; 11] = [
|
||
[
|
||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
0x00, 0x00,
|
||
],
|
||
[
|
||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
0x00, 0x00,
|
||
],
|
||
[
|
||
0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
|
||
0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
|
||
0xfc, 0x05,
|
||
],
|
||
[
|
||
0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
|
||
0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
|
||
0x03, 0x7a,
|
||
],
|
||
[
|
||
0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0, 0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98,
|
||
0xf0, 0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39, 0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53,
|
||
0xfc, 0x85,
|
||
],
|
||
[
|
||
0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f, 0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67,
|
||
0x0f, 0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6, 0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac,
|
||
0x03, 0xfa,
|
||
],
|
||
[
|
||
0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0x7f,
|
||
],
|
||
[
|
||
0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0x7f,
|
||
],
|
||
[
|
||
0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0x7f,
|
||
],
|
||
[
|
||
0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff,
|
||
],
|
||
[
|
||
0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||
0xff, 0xff,
|
||
],
|
||
];
|
||
|
||
/// A container for the 32-byte encoded form of an Ed25519 verification key.
|
||
///
|
||
/// This type only checks or carries the byte length. It does not prove that the
|
||
/// bytes decompress to a valid Ed25519 verification key. Convert it to
|
||
/// [`VerificationKey`] to validate the encoded point and cache decoded state
|
||
/// used in signature verification.
|
||
///
|
||
/// A `VerificationKeyBytes` can be used to verify a single signature using the
|
||
/// following idiom:
|
||
/// ```
|
||
/// use core::convert::TryFrom;
|
||
/// # use curve25519::ed_sigs::*;
|
||
/// # let msg = b"Zcash";
|
||
/// # let sk = SigningKey::from_bytes(&[1u8; 32]);
|
||
/// # let sig = sk.sign(msg);
|
||
/// # let vk_bytes = VerificationKeyBytes::from(&sk);
|
||
/// VerificationKey::try_from(vk_bytes)
|
||
/// .and_then(|vk| vk.verify(&sig, msg));
|
||
/// ```
|
||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||
pub struct VerificationKeyBytes(pub(crate) [u8; VERIFICATION_KEY_LENGTH]);
|
||
|
||
impl core::fmt::Debug for VerificationKeyBytes {
|
||
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||
fmt.debug_tuple("VerificationKeyBytes")
|
||
.field(&self.0)
|
||
.finish()
|
||
}
|
||
}
|
||
|
||
impl AsRef<[u8]> for VerificationKeyBytes {
|
||
fn as_ref(&self) -> &[u8] {
|
||
&self.0[..]
|
||
}
|
||
}
|
||
|
||
impl TryFrom<&[u8]> for VerificationKeyBytes {
|
||
type Error = Error;
|
||
fn try_from(slice: &[u8]) -> Result<VerificationKeyBytes, Self::Error> {
|
||
if slice.len() == 32 {
|
||
let mut bytes = [0u8; 32];
|
||
bytes[..].copy_from_slice(slice);
|
||
Ok(bytes.into())
|
||
} else {
|
||
Err(Error::InvalidSliceLength)
|
||
}
|
||
}
|
||
}
|
||
|
||
impl From<[u8; 32]> for VerificationKeyBytes {
|
||
fn from(bytes: [u8; 32]) -> VerificationKeyBytes {
|
||
VerificationKeyBytes(bytes)
|
||
}
|
||
}
|
||
|
||
impl From<VerificationKeyBytes> for [u8; 32] {
|
||
fn from(refined: VerificationKeyBytes) -> [u8; 32] {
|
||
refined.0
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
impl<'a> TryFrom<SubjectPublicKeyInfoRef<'a>> for VerificationKeyBytes {
|
||
type Error = Error;
|
||
|
||
fn try_from(spki: SubjectPublicKeyInfoRef<'a>) -> Result<VerificationKeyBytes, Error> {
|
||
verification_key_bytes_from_spki(spki).map_err(|_| Error::MalformedPublicKey)
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
fn verification_key_bytes_from_spki(
|
||
spki: SubjectPublicKeyInfoRef<'_>,
|
||
) -> Result<VerificationKeyBytes, SpkiError> {
|
||
if spki.algorithm.oid != OID {
|
||
return Err(SpkiError::OidUnknown {
|
||
oid: spki.algorithm.oid,
|
||
});
|
||
}
|
||
|
||
if spki.algorithm != ALGORITHM_ID {
|
||
return Err(SpkiError::KeyMalformed);
|
||
}
|
||
|
||
let bytes = spki
|
||
.subject_public_key
|
||
.as_bytes()
|
||
.ok_or(SpkiError::KeyMalformed)?;
|
||
|
||
VerificationKeyBytes::try_from(bytes).map_err(|_| SpkiError::KeyMalformed)
|
||
}
|
||
|
||
/// A valid Ed25519 verification key.
|
||
///
|
||
/// This is also called a public key by other implementations.
|
||
///
|
||
/// This type holds decompressed state used in signature verification; if the
|
||
/// verification key may not be used immediately, it is probably better to use
|
||
/// [`VerificationKeyBytes`], which stores only the length-checked encoded bytes.
|
||
///
|
||
/// ## Zcash-specific consensus properties
|
||
///
|
||
/// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in
|
||
/// [ZIP 215]. The verification criteria for an (encoded) verification key `A_bytes` are:
|
||
///
|
||
/// * `A_bytes` MUST be an encoding of a point `A` on the twisted Edwards form of
|
||
/// Curve25519, and non-canonical encodings MUST be accepted;
|
||
///
|
||
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519
|
||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||
#[cfg_attr(feature = "serde", serde(try_from = "VerificationKeyBytes"))]
|
||
#[cfg_attr(feature = "serde", serde(into = "VerificationKeyBytes"))]
|
||
#[allow(non_snake_case)]
|
||
pub struct VerificationKey {
|
||
pub(crate) A_bytes: VerificationKeyBytes,
|
||
pub(crate) minus_A: EdwardsPoint,
|
||
}
|
||
|
||
impl From<VerificationKey> for VerificationKeyBytes {
|
||
fn from(vk: VerificationKey) -> VerificationKeyBytes {
|
||
vk.A_bytes
|
||
}
|
||
}
|
||
|
||
impl AsRef<[u8]> for VerificationKey {
|
||
fn as_ref(&self) -> &[u8] {
|
||
&self.A_bytes.0[..]
|
||
}
|
||
}
|
||
|
||
impl Default for VerificationKey {
|
||
fn default() -> VerificationKey {
|
||
let identity: EdwardsPoint = Default::default();
|
||
let identity_bytes = identity.compress().to_bytes();
|
||
|
||
VerificationKey {
|
||
A_bytes: VerificationKeyBytes::from(identity_bytes),
|
||
minus_A: -identity,
|
||
}
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "zeroize")]
|
||
impl DefaultIsZeroes for VerificationKey {}
|
||
|
||
impl From<VerificationKey> for [u8; 32] {
|
||
fn from(vk: VerificationKey) -> [u8; 32] {
|
||
vk.A_bytes.0
|
||
}
|
||
}
|
||
|
||
impl TryFrom<VerificationKeyBytes> for VerificationKey {
|
||
type Error = Error;
|
||
#[allow(non_snake_case)]
|
||
fn try_from(bytes: VerificationKeyBytes) -> Result<Self, Self::Error> {
|
||
// * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the
|
||
// twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted;
|
||
let A = CompressedEdwardsY(bytes.0)
|
||
.decompress()
|
||
.ok_or(Error::MalformedPublicKey)?;
|
||
|
||
Ok(VerificationKey {
|
||
A_bytes: bytes,
|
||
minus_A: -A,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl TryFrom<&[u8]> for VerificationKey {
|
||
type Error = Error;
|
||
fn try_from(slice: &[u8]) -> Result<VerificationKey, Error> {
|
||
VerificationKeyBytes::try_from(slice).and_then(|vkb| vkb.try_into())
|
||
}
|
||
}
|
||
|
||
impl TryFrom<[u8; 32]> for VerificationKey {
|
||
type Error = Error;
|
||
fn try_from(bytes: [u8; 32]) -> Result<Self, Self::Error> {
|
||
VerificationKeyBytes::from(bytes).try_into()
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
impl EncodePublicKey for VerificationKey {
|
||
/// Serialize [`VerificationKey`] to an ASN.1 DER-encoded document.
|
||
fn to_public_key_der(&self) -> pkcs8::spki::Result<Document> {
|
||
SubjectPublicKeyInfoRef {
|
||
algorithm: ALGORITHM_ID,
|
||
subject_public_key: BitStringRef::from_bytes(&self.A_bytes.0[..])?,
|
||
}
|
||
.try_into()
|
||
}
|
||
}
|
||
|
||
#[cfg(feature = "pkcs8")]
|
||
impl DecodePublicKey for VerificationKey {
|
||
/// Deserialize [`VerificationKey`] from ASN.1 DER bytes (32 bytes).
|
||
fn from_public_key_der(bytes: &[u8]) -> Result<Self, pkcs8::spki::Error> {
|
||
let spki = SubjectPublicKeyInfoRef::try_from(bytes)?;
|
||
let pk_bytes = verification_key_bytes_from_spki(spki)?;
|
||
Self::try_from(pk_bytes).map_err(|_| SpkiError::KeyMalformed)
|
||
}
|
||
}
|
||
|
||
impl Verifier<Signature> for VerificationKey {
|
||
/// Verify a [`Signature`] object against a given [`VerificationKey`].
|
||
fn verify(
|
||
&self,
|
||
message: &[u8],
|
||
signature: &Signature,
|
||
) -> Result<(), ed25519::signature::Error> {
|
||
self.verify(signature, message)
|
||
.map_err(|_| ed25519::signature::Error::new())
|
||
}
|
||
}
|
||
|
||
impl VerificationKey {
|
||
fn challenge_scalar(&self, signature: &Signature, msg: &[u8]) -> Scalar {
|
||
scalar_from_sha512(
|
||
Sha512::default()
|
||
.chain(&signature.r_bytes()[..])
|
||
.chain(&self.A_bytes.0[..])
|
||
.chain(msg),
|
||
)
|
||
}
|
||
|
||
/// Verify a purported `signature` on the given `msg`.
|
||
///
|
||
/// This is the default verification mode and uses the HEEA-accelerated
|
||
/// verification path with Zebra / ZIP-215 semantics.
|
||
///
|
||
/// ## Zcash-specific consensus properties
|
||
///
|
||
/// Ed25519 checks are described in [§5.4.5][ps] of the Zcash protocol specification and in
|
||
/// [ZIP215]. The verification criteria for an (encoded) signature `(R_bytes, s_bytes)` with
|
||
/// (encoded) verification key `A_bytes` are:
|
||
///
|
||
/// * `A_bytes` and `R_bytes` MUST be encodings of points `A` and `R` respectively on the
|
||
/// twisted Edwards form of Curve25519, and non-canonical encodings MUST be accepted;
|
||
///
|
||
/// * `s_bytes` MUST represent an integer `s` less than `l`, the order of the prime-order
|
||
/// subgroup of Curve25519;
|
||
///
|
||
/// * the verification equation `[8][s]B = [8]R + [8][k]A` MUST be satisfied;
|
||
///
|
||
/// * the alternate verification equation `[s]B = R + [k]A`, allowed by RFC 8032, MUST NOT be
|
||
/// used.
|
||
///
|
||
/// [ps]: https://zips.z.cash/protocol/protocol.pdf#concreteed25519
|
||
/// [ZIP215]: https://zips.z.cash/zip-0215
|
||
pub fn verify(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
|
||
self.verify_zebra(signature, msg)
|
||
}
|
||
|
||
/// Verify a signature using HEEA with Zebra / ZIP-215 semantics.
|
||
///
|
||
/// This implements the algorithm from "Accelerating EdDSA Signature Verification
|
||
/// with Faster Scalar Size Halving" (TCHES 2025).
|
||
///
|
||
/// The decomposition returns ρ and τ such that either ρ ≡ τh (mod ℓ) or
|
||
/// ρ ≡ -τh (mod ℓ). The standard verification equation sB = R + hA is
|
||
/// multiplied by τ and the sign of A is selected according to `flip_h`.
|
||
///
|
||
/// Both ρ and τ are approximately half the size of h.
|
||
///
|
||
/// We then decompose τs into two 128-bit scalars:
|
||
/// τs = τs_hi * 2^128 + τs_lo
|
||
///
|
||
/// The resulting equation can be checked with a 4-variable MSM with
|
||
/// half-size scalars.
|
||
#[allow(non_snake_case)]
|
||
pub fn verify_zebra(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
|
||
self.verify_zebra_prehashed(signature, self.challenge_scalar(signature, msg))
|
||
}
|
||
|
||
#[allow(non_snake_case)]
|
||
pub(crate) fn verify_zebra_prehashed(
|
||
&self,
|
||
signature: &Signature,
|
||
h: Scalar,
|
||
) -> Result<(), Error> {
|
||
// Generate half-size scalars ρ and τ. If flip_h is false, then
|
||
// ρ ≡ τh (mod ℓ). If flip_h is true, then ρ ≡ -τh (mod ℓ), so the
|
||
// sign of A is flipped below.
|
||
let (rho, tau, flip_h) = h.heea_decompose();
|
||
|
||
// Extract s from the signature
|
||
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(*signature.s_bytes()))
|
||
.ok_or(Error::InvalidSignature)?;
|
||
|
||
// Decode R from the signature
|
||
let neg_R = -CompressedEdwardsY(*signature.r_bytes())
|
||
.decompress()
|
||
.ok_or(Error::InvalidSignature)?;
|
||
|
||
// Standard verification checks: sB = R + hA.
|
||
//
|
||
// We verify:
|
||
// [8] τs B + [8] τ (-R) + [8] ρ A_term == 0
|
||
// where A_term is -A when ρ ≡ τh and A when ρ ≡ -τh.
|
||
|
||
// Compute τs
|
||
let ts = tau * s;
|
||
let A = if flip_h { -self.minus_A } else { self.minus_A };
|
||
// 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(())
|
||
} else {
|
||
Err(Error::InvalidSignature)
|
||
}
|
||
}
|
||
|
||
/// Verify a signature with dalek-style canonical-`R` byte comparison.
|
||
///
|
||
/// This recomputes the expected canonical `R` encoding and compares it to the
|
||
/// signature's `R` bytes.
|
||
///
|
||
/// This helper also preserves this crate's legacy compatibility filters: it
|
||
/// rejects an all-zero encoded public key and the known legacy-excluded `R`
|
||
/// encodings before running the canonical-`R` comparison. Because of those
|
||
/// extra checks, this is not a byte-for-byte clone of every `ed25519-dalek`
|
||
/// release.
|
||
///
|
||
/// Note that dalek-style canonical-`R` comparison is incompatible with the HEEA
|
||
/// transformed equation because the transformed check does not preserve the
|
||
/// original `R` encoding needed for the byte comparison.
|
||
#[allow(non_snake_case)]
|
||
pub fn verify_dalek(&self, signature: &Signature, msg: &[u8]) -> Result<(), Error> {
|
||
self.verify_dalek_prehashed(signature, self.challenge_scalar(signature, msg))
|
||
}
|
||
|
||
#[allow(non_snake_case)]
|
||
fn verify_dalek_prehashed(&self, signature: &Signature, h: Scalar) -> Result<(), Error> {
|
||
if self.A_bytes.0 == [0; 32] || LEGACY_EXCLUDED_R_ENCODINGS.contains(signature.r_bytes()) {
|
||
return Err(Error::InvalidSignature);
|
||
}
|
||
|
||
let s = Option::<Scalar>::from(Scalar::from_canonical_bytes(*signature.s_bytes()))
|
||
.ok_or(Error::InvalidSignature)?;
|
||
|
||
let expected_R =
|
||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&h, &self.minus_A, &s).compress();
|
||
|
||
if expected_R.as_bytes() == signature.r_bytes() {
|
||
Ok(())
|
||
} else {
|
||
Err(Error::InvalidSignature)
|
||
}
|
||
}
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 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)
|
||
}
|
||
}
|
||
}
|