Implement Hash on non-secret Signature and VerifyingKey types (#1309)

Per #1229: Signature and public-key types lack `Hash`, which prevents
use in `HashMap` / `HashSet` keys and similar collections.

- ecdsa: Signature, DER Signature, SignatureWithOid
- ed25519: Signature, pkcs8::PublicKeyBytes
- ed448: Signature, pkcs8::PublicKeyBytes
- ml-dsa: Signature, VerifyingKey
- slh-dsa: Signature, VerifyingKey

Where a trivial derive works (ed25519, ed448), uses derive. Where
generic bounds or upstream gaps require it, uses a manual impl over
the canonical serialized bytes (`to_bytes` / `encode` / `as_bytes`),
which is the natural Hash domain for these types anyway.

Closes #1229
This commit is contained in:
Onyeka Obi
2026-05-01 15:17:15 -07:00
committed by GitHub
parent f3c409ee83
commit 826baa3682
9 changed files with 61 additions and 4 deletions
+11
View File
@@ -151,6 +151,17 @@ where
}
}
impl<C> core::hash::Hash for Signature<C>
where
C: EcdsaCurve,
MaxSize<C>: ArraySize,
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArraySize,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
impl<C> AsRef<[u8]> for Signature<C>
where
C: EcdsaCurve,
+22
View File
@@ -398,6 +398,16 @@ where
}
}
impl<C> core::hash::Hash for Signature<C>
where
C: EcdsaCurve,
SignatureSize<C>: ArraySize,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
impl<C> fmt::LowerHex for Signature<C>
where
C: EcdsaCurve,
@@ -667,6 +677,18 @@ where
{
}
#[cfg(feature = "digest")]
impl<C> core::hash::Hash for SignatureWithOid<C>
where
C: EcdsaCurve,
SignatureSize<C>: ArraySize,
{
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.signature.hash(state);
self.oid.hash(state);
}
}
#[cfg(feature = "digest")]
impl<C> From<SignatureWithOid<C>> for Signature<C>
where
+1 -1
View File
@@ -316,7 +316,7 @@ pub type SignatureBytes = [u8; Signature::BYTE_SIZE];
///
/// Signature verification libraries are expected to reject invalid field
/// elements at the time a signature is verified.
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
#[cfg_attr(
feature = "zerocopy",
derive(FromBytes, IntoBytes, Immutable, KnownLayout, Unaligned)
+1 -1
View File
@@ -229,7 +229,7 @@ impl str::FromStr for KeypairBytes {
///
/// Note that this type operates on raw bytes and performs no validation that
/// public keys represent valid compressed Ed25519 y-coordinates.
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[cfg_attr(
feature = "zerocopy",
derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable,)
+1 -1
View File
@@ -115,7 +115,7 @@ pub type SignatureBytes = [u8; Signature::BYTE_SIZE];
///
/// Signature verification libraries are expected to reject invalid field
/// elements at the time a signature is verified.
#[derive(Copy, Clone, Eq, PartialEq)]
#[derive(Copy, Clone, Eq, Hash, PartialEq)]
#[repr(C)]
pub struct Signature {
R: ComponentBytes,
+1 -1
View File
@@ -205,7 +205,7 @@ impl fmt::Debug for KeypairBytes {
///
/// Note that this type operates on raw bytes and performs no validation that
/// public keys represent valid compressed Ed448 y-coordinates.
#[derive(Clone, Copy, Eq, PartialEq)]
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct PublicKeyBytes(pub [u8; Self::BYTE_SIZE]);
impl PublicKeyBytes {
+12
View File
@@ -151,6 +151,12 @@ impl<P: MlDsaParams> signature::SignatureEncoding for Signature<P> {
type Repr = EncodedSignature<P>;
}
impl<P: MlDsaParams> core::hash::Hash for Signature<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.encode().hash(state);
}
}
struct MuBuilder(H);
impl MuBuilder {
@@ -834,6 +840,12 @@ impl<P: MlDsaParams> VerifyingKey<P> {
}
}
impl<P: MlDsaParams> core::hash::Hash for VerifyingKey<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.encode().hash(state);
}
}
impl<P: MlDsaParams> signature::Verifier<Signature<P>> for VerifyingKey<P> {
fn verify(&self, msg: &[u8], signature: &Signature<P>) -> Result<(), Error> {
self.multipart_verify(&[msg], signature)
+6
View File
@@ -71,6 +71,12 @@ impl<P: ParameterSet> Signature<P> {
}
}
impl<P: ParameterSet> core::hash::Hash for Signature<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
impl<P: ParameterSet> TryFrom<&[u8]> for Signature<P> {
type Error = Error;
+6
View File
@@ -136,6 +136,12 @@ impl<P: ParameterSet + VerifyingKeyLen> VerifyingKey<P> {
}
}
impl<P: ParameterSet> core::hash::Hash for VerifyingKey<P> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
impl<P: ParameterSet> Clone for VerifyingKey<P> {
fn clone(&self) -> Self {
VerifyingKey {