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

core/identity: Implement Hash and Ord for PublicKey #2915

Merged
merged 12 commits into from
Sep 22, 2022
6 changes: 6 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# 0.36.1 - [unreleased]
futpib marked this conversation as resolved.
Show resolved Hide resolved

- Implement `Hash` and `Ord` for `PublicKey`. See [PR 2915].

[PR 2915]: https://github.com/libp2p/rust-libp2p/pull/2915

# 0.36.0

- Make RSA keypair support optional. To enable RSA support, `rsa` feature should be enabled.
Expand Down
20 changes: 19 additions & 1 deletion core/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ impl zeroize::Zeroize for keys_proto::PrivateKey {
}

/// The public key of a node's identity keypair.
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum PublicKey {
/// A public Ed25519 key.
Ed25519(ed25519::PublicKey),
Expand Down Expand Up @@ -379,4 +379,22 @@ mod tests {

assert_eq!(expected_peer_id, peer_id);
}

#[test]
fn public_key_implements_hash() {
use std::hash::Hash;

fn assert_implements_hash<T: Hash>() {}

assert_implements_hash::<PublicKey>();
}

#[test]
fn public_key_implements_ord() {
use std::cmp::Ord;

fn assert_implements_ord<T: Ord>() {}

assert_implements_ord::<PublicKey>();
}
}
37 changes: 36 additions & 1 deletion core/src/identity/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

use super::error::DecodingError;
use core::fmt;
use core::hash;
use p256::{
ecdsa::{
signature::{Signer, Verifier},
Expand Down Expand Up @@ -117,7 +118,7 @@ impl fmt::Debug for SecretKey {
}

/// An ECDSA public key.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PublicKey(VerifyingKey);

impl PublicKey {
Expand Down Expand Up @@ -222,9 +223,34 @@ impl fmt::Debug for PublicKey {
}
}

#[allow(clippy::derive_hash_xor_eq)]
impl hash::Hash for PublicKey {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.to_bytes().hash(state);
}
}
futpib marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod tests {
use super::*;
use crate::test::*;
use quickcheck::*;

#[derive(Clone, Debug)]
struct SomePublicKey(PublicKey);

impl Arbitrary for SomePublicKey {
fn arbitrary<G: Gen>(g: &mut G) -> SomePublicKey {
loop {
let mut bytes = Vec::<u8>::arbitrary(g);
bytes.resize(33, 0);
let public_key = PublicKey::from_bytes(&bytes);
if let Ok(public_key) = public_key {
return SomePublicKey(public_key);
}
}
}
}

#[test]
fn sign_verify() {
Expand All @@ -242,4 +268,13 @@ mod tests {
let invalid_msg = "h3ll0 w0rld".as_bytes();
assert!(!pk.verify(invalid_msg, &sig));
}

#[test]
fn ecdsa_public_key_eq_implies_hash() {
fn prop(SomePublicKey(pub1): SomePublicKey, SomePublicKey(pub2): SomePublicKey) -> bool {
pub1 != pub2 || hash(&pub1) == hash(&pub2)
futpib marked this conversation as resolved.
Show resolved Hide resolved
}
QuickCheck::new()
.quickcheck(prop as fn(_, _) -> _);
}
}
49 changes: 49 additions & 0 deletions core/src/identity/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

use super::error::DecodingError;
use core::fmt;
use core::hash;
use core::cmp;
use ed25519_dalek::{self as ed25519, Signer as _, Verifier as _};
use rand::RngCore;
use std::convert::TryFrom;
Expand Down Expand Up @@ -126,6 +128,27 @@ impl fmt::Debug for PublicKey {
}
}

// Remove (derive instead) when this PR is merged:
// https://github.com/dalek-cryptography/ed25519-dalek/pull/176
#[allow(clippy::derive_hash_xor_eq)]
impl hash::Hash for PublicKey {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.0.as_bytes().hash(state);
}
}

impl cmp::PartialOrd for PublicKey {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.0.as_bytes().partial_cmp(other.0.as_bytes())
}
}

impl cmp::Ord for PublicKey {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.0.as_bytes().cmp(other.0.as_bytes())
}
}

impl PublicKey {
/// Verify the Ed25519 signature on a message using the public key.
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool {
Expand Down Expand Up @@ -198,8 +221,25 @@ impl SecretKey {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::*;
use quickcheck::*;

#[derive(Clone, Debug)]
struct SomePublicKey(PublicKey);

impl Arbitrary for SomePublicKey {
fn arbitrary<G: Gen>(g: &mut G) -> SomePublicKey {
loop {
let mut bytes = Vec::<u8>::arbitrary(g);
bytes.resize(32, 0);
let public_key = PublicKey::decode(&mut bytes);
if let Ok(public_key) = public_key {
return SomePublicKey(public_key);
}
}
}
}

fn eq_keypairs(kp1: &Keypair, kp2: &Keypair) -> bool {
kp1.public() == kp2.public() && kp1.0.secret.as_bytes() == kp2.0.secret.as_bytes()
}
Expand Down Expand Up @@ -242,4 +282,13 @@ mod tests {
let invalid_msg = "h3ll0 w0rld".as_bytes();
assert!(!pk.verify(invalid_msg, &sig));
}

#[test]
fn ecdsa_public_key_eq_implies_hash() {
fn prop(SomePublicKey(pub1): SomePublicKey, SomePublicKey(pub2): SomePublicKey) -> bool {
pub1 != pub2 || hash(&pub1) == hash(&pub2)
}
QuickCheck::new()
.quickcheck(prop as fn(_, _) -> _);
}
}
30 changes: 21 additions & 9 deletions core/src/identity/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ impl Keypair {
}

/// An RSA public key.
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PublicKey(Vec<u8>);

impl PublicKey {
Expand Down Expand Up @@ -305,30 +305,33 @@ impl DerDecodable<'_> for Asn1SubjectPublicKeyInfo {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::*;
use quickcheck::*;
use rand07::seq::SliceRandom;
use std::fmt;

const KEY1: &'static [u8] = include_bytes!("test/rsa-2048.pk8");
const KEY2: &'static [u8] = include_bytes!("test/rsa-3072.pk8");
const KEY3: &'static [u8] = include_bytes!("test/rsa-4096.pk8");

#[derive(Clone)]
#[derive(Clone, Debug)]
struct SomeKeypair(Keypair);

impl fmt::Debug for SomeKeypair {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SomeKeypair")
}
}

impl Arbitrary for SomeKeypair {
fn arbitrary<G: Gen>(g: &mut G) -> SomeKeypair {
let mut key = [KEY1, KEY2, KEY3].choose(g).unwrap().to_vec();
SomeKeypair(Keypair::from_pkcs8(&mut key).unwrap())
}
}

#[derive(Clone, Debug)]
struct SomePublicKey(PublicKey);

impl Arbitrary for SomePublicKey {
fn arbitrary<G: Gen>(g: &mut G) -> SomePublicKey {
SomePublicKey(SomeKeypair::arbitrary(g).0.public())
}
}

futpib marked this conversation as resolved.
Show resolved Hide resolved
#[test]
fn rsa_from_pkcs8() {
assert!(Keypair::from_pkcs8(&mut KEY1.to_vec()).is_ok());
Expand Down Expand Up @@ -356,4 +359,13 @@ mod tests {
.tests(10)
.quickcheck(prop as fn(_, _) -> _);
}

#[test]
fn rsa_public_key_eq_implies_hash() {
fn prop(SomePublicKey(pub1): SomePublicKey, SomePublicKey(pub2): SomePublicKey) -> bool {
pub1 != pub2 || hash(&pub1) == hash(&pub2)
}
QuickCheck::new()
.quickcheck(prop as fn(_, _) -> _);
}
}
59 changes: 59 additions & 0 deletions core/src/identity/secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
use super::error::{DecodingError, SigningError};
use asn1_der::typed::{DerDecodable, Sequence};
use core::fmt;
use core::hash;
use core::cmp;
use libsecp256k1::{Message, Signature};
use sha2::{Digest as ShaDigestTrait, Sha256};
use zeroize::Zeroize;
Expand Down Expand Up @@ -163,6 +165,25 @@ impl fmt::Debug for PublicKey {
}
}

#[allow(clippy::derive_hash_xor_eq)]
impl hash::Hash for PublicKey {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.encode().hash(state);
}
}
futpib marked this conversation as resolved.
Show resolved Hide resolved

impl cmp::PartialOrd for PublicKey {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
self.encode().partial_cmp(&other.encode())
}
}

impl cmp::Ord for PublicKey {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.encode().cmp(&other.encode())
}
}

impl PublicKey {
/// Verify the Secp256k1 signature on a message using the public key.
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool {
Expand Down Expand Up @@ -199,6 +220,35 @@ impl PublicKey {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::*;
use quickcheck::*;

#[derive(Clone, Debug)]
struct SomeSecretKey(SecretKey);

impl Arbitrary for SomeSecretKey {
fn arbitrary<G: Gen>(g: &mut G) -> SomeSecretKey {
loop {
let mut bytes = Vec::<u8>::arbitrary(g);
bytes.resize(32, 0);
let secret_key = SecretKey::from_bytes(&mut bytes);
if let Ok(secret_key) = secret_key {
return SomeSecretKey(secret_key);
}
}
}
}

#[derive(Clone, Debug)]
struct SomePublicKey(PublicKey);

impl Arbitrary for SomePublicKey {
fn arbitrary<G: Gen>(g: &mut G) -> SomePublicKey {
let secret_key = SomeSecretKey::arbitrary(g).0;
let keypair: Keypair = secret_key.into();
SomePublicKey(keypair.public().clone())
}
}

#[test]
fn secp256k1_secret_from_bytes() {
Expand All @@ -209,4 +259,13 @@ mod tests {
assert_eq!(sk1.0.serialize(), sk2.0.serialize());
assert_eq!(sk_bytes, [0; 32]);
}

#[test]
fn ecdsa_public_key_eq_implies_hash() {
fn prop(SomePublicKey(pub1): SomePublicKey, SomePublicKey(pub2): SomePublicKey) -> bool {
pub1 != pub2 || hash(&pub1) == hash(&pub2)
}
QuickCheck::new()
.quickcheck(prop as fn(_, _) -> _);
}
}
3 changes: 3 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub use translation::address_translation;
pub use transport::Transport;
pub use upgrade::{InboundUpgrade, OutboundUpgrade, ProtocolName, UpgradeError, UpgradeInfo};

#[cfg(test)]
mod test;

use std::{future::Future, pin::Pin};

/// Implemented on objects that can run a `Future` in the background.
Expand Down
8 changes: 8 additions & 0 deletions core/src/test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::hash::{Hash, Hasher};
use std::collections::hash_map::DefaultHasher;

pub fn hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
}