diff --git a/src/verifying.rs b/src/verifying.rs index b700bac..89e1b65 100644 --- a/src/verifying.rs +++ b/src/verifying.rs @@ -11,6 +11,7 @@ use core::convert::TryFrom; use core::fmt::Debug; +use core::hash::{Hash, Hasher}; use curve25519_dalek::digest::generic_array::typenum::U64; use curve25519_dalek::digest::Digest; @@ -38,8 +39,19 @@ use crate::signature::*; use crate::signing::*; /// An ed25519 public key. +/// +/// # Note +/// +/// The `Eq` and `Hash` impls here use the compressed Edwards y encoding, _not_ the algebraic +/// representation. This means if this `VerifyingKey` is non-canonically encoded, it will be +/// considered unequal to the other equivalent encoding, despite the two representing the same +/// point. More encoding details can be found +/// [here](https://hdevalence.ca/blog/2020-10-04-its-25519am). +/// +/// If you don't care and/or don't want to deal with this, just make sure to use the +/// [`VerifyingKey::verify_strict`] function. // Invariant: VerifyingKey.1 is always the decompression of VerifyingKey.0 -#[derive(Copy, Clone, Default, Eq, PartialEq)] +#[derive(Copy, Clone, Default, Eq)] pub struct VerifyingKey(pub(crate) CompressedEdwardsY, pub(crate) EdwardsPoint); impl Debug for VerifyingKey { @@ -54,6 +66,18 @@ impl AsRef<[u8]> for VerifyingKey { } } +impl Hash for VerifyingKey { + fn hash(&self, state: &mut H) { + self.as_bytes().hash(state); + } +} + +impl PartialEq for VerifyingKey { + fn eq(&self, other: &VerifyingKey) -> bool { + self.as_bytes() == other.as_bytes() + } +} + impl From<&ExpandedSecretKey> for VerifyingKey { /// Derive this public key from its corresponding `ExpandedSecretKey`. fn from(expanded_secret_key: &ExpandedSecretKey) -> VerifyingKey { @@ -114,7 +138,7 @@ impl VerifyingKey { /// # Returns /// /// A `Result` whose okay value is an EdDSA `VerifyingKey` or whose error value - /// is an `SignatureError` describing the error that occurred. + /// is a `SignatureError` describing the error that occurred. #[inline] pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_LENGTH]) -> Result { let compressed = CompressedEdwardsY(*bytes); @@ -176,14 +200,12 @@ impl VerifyingKey { /// * `context` is an optional context string, up to 255 bytes inclusive, /// which may be used to provide additional domain separation. If not /// set, this will default to an empty string. - /// * `signature` is a purported Ed25519ph [`Signature`] on the `prehashed_message`. + /// * `signature` is a purported Ed25519ph signature on the `prehashed_message`. /// /// # Returns /// /// Returns `true` if the `signature` was a valid signature created by this /// `Keypair` on the `prehashed_message`. - /// - /// [rfc8032]: https://tools.ietf.org/html/rfc8032#section-5.1 #[allow(non_snake_case)] pub fn verify_prehashed( &self, @@ -229,7 +251,7 @@ impl VerifyingKey { /// 1. Scalar Malleability /// /// The authors of the RFC explicitly stated that verification of an ed25519 - /// signature must fail if the scalar `s` is not properly reduced mod \ell: + /// signature must fail if the scalar `s` is not properly reduced mod $\ell$: /// /// > To verify a signature on a message M using public key A, with F /// > being 0 for Ed25519ctx, 1 for Ed25519ph, and if Ed25519ctx or @@ -322,7 +344,7 @@ impl VerifyingKey { /// * `context` is an optional context string, up to 255 bytes inclusive, /// which may be used to provide additional domain separation. If not /// set, this will default to an empty string. - /// * `signature` is a purported Ed25519ph [`Signature`] on the `prehashed_message`. + /// * `signature` is a purported Ed25519ph signature on the `prehashed_message`. /// /// # Returns /// diff --git a/tests/ed25519.rs b/tests/ed25519.rs index 03597d6..25c3520 100644 --- a/tests/ed25519.rs +++ b/tests/ed25519.rs @@ -283,6 +283,7 @@ mod integrations { use super::*; use rand::rngs::OsRng; use sha2::Sha512; + use std::collections::HashMap; #[test] fn sign_verify() { @@ -427,6 +428,33 @@ mod integrations { assert!(result.is_ok()); } + + #[test] + fn public_key_hash_trait_check() { + let mut csprng = OsRng {}; + let secret: SigningKey = SigningKey::generate(&mut csprng); + let public_from_secret: VerifyingKey = (&secret).into(); + + let mut m = HashMap::new(); + m.insert(public_from_secret, "Example_Public_Key"); + + m.insert(public_from_secret, "Updated Value"); + + let (k, v) = m.get_key_value(&public_from_secret).unwrap(); + assert_eq!(k, &public_from_secret); + assert_eq!(v.clone(), "Updated Value"); + assert_eq!(m.len(), 1usize); + + let second_secret: SigningKey = SigningKey::generate(&mut csprng); + let public_from_second_secret: VerifyingKey = (&second_secret).into(); + assert_ne!(public_from_secret, public_from_second_secret); + m.insert(public_from_second_secret, "Second public key"); + + let (k, v) = m.get_key_value(&public_from_second_secret).unwrap(); + assert_eq!(k, &public_from_second_secret); + assert_eq!(v.clone(), "Second public key"); + assert_eq!(m.len(), 2usize); + } } #[cfg(all(test, feature = "serde"))]