Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Hash trait for VerifyingKey #265

Merged
merged 2 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 29 additions & 7 deletions src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this sentence will not be true until #267 is addressed.

// 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 {
Expand All @@ -54,6 +66,18 @@ impl AsRef<[u8]> for VerifyingKey {
}
}

impl Hash for VerifyingKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}

impl PartialEq<VerifyingKey> 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 {
Expand Down Expand Up @@ -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<VerifyingKey, SignatureError> {
let compressed = CompressedEdwardsY(*bytes);
Expand Down Expand Up @@ -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<D>(
&self,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
///
Expand Down
28 changes: 28 additions & 0 deletions tests/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ mod integrations {
use super::*;
use rand::rngs::OsRng;
use sha2::Sha512;
use std::collections::HashMap;

#[test]
fn sign_verify() {
Expand Down Expand Up @@ -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"))]
Expand Down