anza-cryptography-source/curve25519/solana-ed25519/src/ed_sigs/verification_key.rs
mrwulf d275613c37 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>
2026-07-04 23:00:06 +02:00

593 lines
21 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// -*- 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)
}
}
}