diff --git a/core/src/peer_id.rs b/core/src/peer_id.rs index f7448049..dd60c43d 100644 --- a/core/src/peer_id.rs +++ b/core/src/peer_id.rs @@ -24,6 +24,10 @@ use quick_error::quick_error; use multihash; use std::{convert::TryFrom, fmt, str::FromStr}; +/// Public keys with byte-lengths smaller than `MAX_INLINE_KEY_LENGTH` will be +/// automatically used as the peer id using an identity multihash. +const MAX_INLINE_KEY_LENGTH: usize = 42; + /// Identifier of a peer of the network. /// /// The data is a multihash of the public key of the peer. @@ -52,8 +56,23 @@ impl PeerId { #[inline] pub fn from_public_key(key: PublicKey) -> PeerId { let key_enc = key.into_protobuf_encoding(); - let multihash = multihash::encode(multihash::Hash::SHA2256, &key_enc) - .expect("sha2-256 is always supported"); + + // Note: the correct behaviour, according to the libp2p specifications, is the + // commented-out code, which consists it transmitting small keys un-hashed. However, this + // version and all previous versions of rust-libp2p always hash the key. Starting from + // version 0.13, rust-libp2p accepts both hashed and non-hashed keys as input + // (see `from_bytes`). Starting from version 0.14, rust-libp2p will switch to not hashing + // the key (a.k.a. the correct behaviour). + // In other words, rust-libp2p 0.13 is compatible with all versions of rust-libp2p. + // Rust-libp2p 0.12 and below is **NOT** compatible with rust-libp2p 0.14 and above. + /*let hash_algorithm = if key_enc.len() <= MAX_INLINE_KEY_LENGTH { + multihash::Hash::Identity + } else { + multihash::Hash::SHA2256 + };*/ + let hash_algorithm = multihash::Hash::SHA2256; + let multihash = multihash::encode(hash_algorithm, &key_enc) + .expect("identity and sha2-256 are always supported by known public key types"); PeerId { multihash } } @@ -63,12 +82,14 @@ impl PeerId { pub fn from_bytes(data: Vec) -> Result> { match multihash::Multihash::from_bytes(data) { Ok(multihash) => { - if multihash.algorithm() == multihash::Hash::SHA2256 { + if multihash.algorithm() == multihash::Hash::SHA2256 + || multihash.algorithm() == multihash::Hash::Identity + { Ok(PeerId { multihash }) } else { Err(multihash.into_bytes()) } - }, + } Err(err) => Err(err.data), } } @@ -131,7 +152,8 @@ impl PeerId { let enc = public_key.clone().into_protobuf_encoding(); match multihash::encode(alg, &enc) { Ok(h) => Some(h == self.multihash), - Err(multihash::EncodeError::UnsupportedType) => None + Err(multihash::EncodeError::UnsupportedType) => None, + Err(multihash::EncodeError::UnsupportedInputLength) => None, } } } diff --git a/misc/multihash/src/errors.rs b/misc/multihash/src/errors.rs index 523c4081..df3ba853 100644 --- a/misc/multihash/src/errors.rs +++ b/misc/multihash/src/errors.rs @@ -5,6 +5,8 @@ use std::{error, fmt}; pub enum EncodeError { /// The requested hash algorithm isn't supported by this library. UnsupportedType, + /// The input length is too large for the hash algorithm. + UnsupportedInputLength, } impl fmt::Display for EncodeError { @@ -12,6 +14,10 @@ impl fmt::Display for EncodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { EncodeError::UnsupportedType => write!(f, "This type is not supported yet"), + EncodeError::UnsupportedInputLength => write!( + f, + "The length of the input for the given hash is not yet supported" + ), } } } diff --git a/misc/multihash/src/hashes.rs b/misc/multihash/src/hashes.rs index 8d6d29af..7833548a 100644 --- a/misc/multihash/src/hashes.rs +++ b/misc/multihash/src/hashes.rs @@ -3,6 +3,8 @@ /// Not all hash types are supported by this library. #[derive(PartialEq, Eq, Clone, Debug, Copy, Hash)] pub enum Hash { + /// Identity (Raw binary ) + Identity, /// SHA-1 (20-byte hash size) SHA1, /// SHA-256 (32-byte hash size) @@ -39,6 +41,7 @@ impl Hash { /// Get the corresponding hash code. pub fn code(&self) -> u16 { match self { + Hash::Identity => 0x00, Hash::SHA1 => 0x11, Hash::SHA2256 => 0x12, Hash::SHA2512 => 0x13, @@ -60,6 +63,7 @@ impl Hash { /// Get the hash length in bytes. pub fn size(&self) -> u8 { match self { + Hash::Identity => 42, Hash::SHA1 => 20, Hash::SHA2256 => 32, Hash::SHA2512 => 64, @@ -81,6 +85,7 @@ impl Hash { /// Returns the algorithm corresponding to a code, or `None` if no algorithm is matching. pub fn from_code(code: u16) -> Option { Some(match code { + 0x00 => Hash::Identity, 0x11 => Hash::SHA1, 0x12 => Hash::SHA2256, 0x13 => Hash::SHA2512, diff --git a/misc/multihash/src/lib.rs b/misc/multihash/src/lib.rs index 64d0406b..25a1d824 100644 --- a/misc/multihash/src/lib.rs +++ b/misc/multihash/src/lib.rs @@ -57,24 +57,46 @@ macro_rules! match_encoder { /// ``` /// pub fn encode(hash: Hash, input: &[u8]) -> Result { - let (offset, mut output) = encode_hash(hash); - match_encoder!(hash for (input, &mut output[offset ..]) { - SHA1 => sha1::Sha1, - SHA2256 => sha2::Sha256, - SHA2512 => sha2::Sha512, - SHA3224 => sha3::Sha3_224, - SHA3256 => sha3::Sha3_256, - SHA3384 => sha3::Sha3_384, - SHA3512 => sha3::Sha3_512, - Keccak224 => sha3::Keccak224, - Keccak256 => sha3::Keccak256, - Keccak384 => sha3::Keccak384, - Keccak512 => sha3::Keccak512, - Blake2b512 => blake2::Blake2b, - Blake2s256 => blake2::Blake2s, - }); + // Custom length encoding for the identity multihash + if let Hash::Identity = hash { + if u64::from(std::u32::MAX) < as_u64(input.len()) { + return Err(EncodeError::UnsupportedInputLength); + } + let mut buf = encode::u16_buffer(); + let code = encode::u16(hash.code(), &mut buf); + let mut len_buf = encode::u32_buffer(); + let size = encode::u32(input.len() as u32, &mut len_buf); - Ok(Multihash { bytes: output.freeze() }) + let total_len = code.len() + size.len() + input.len(); + + let mut output = BytesMut::with_capacity(total_len); + output.put_slice(code); + output.put_slice(size); + output.put_slice(input); + Ok(Multihash { + bytes: output.freeze(), + }) + } else { + let (offset, mut output) = encode_hash(hash); + match_encoder!(hash for (input, &mut output[offset ..]) { + SHA1 => sha1::Sha1, + SHA2256 => sha2::Sha256, + SHA2512 => sha2::Sha512, + SHA3224 => sha3::Sha3_224, + SHA3256 => sha3::Sha3_256, + SHA3384 => sha3::Sha3_384, + SHA3512 => sha3::Sha3_512, + Keccak224 => sha3::Keccak224, + Keccak256 => sha3::Keccak256, + Keccak384 => sha3::Keccak384, + Keccak512 => sha3::Keccak512, + Blake2b512 => blake2::Blake2b, + Blake2s256 => blake2::Blake2s, + }); + Ok(Multihash { + bytes: output.freeze(), + }) + } } // Encode the given [`Hash`] value and ensure the returned [`BytesMut`] @@ -180,15 +202,25 @@ impl<'a> MultihashRef<'a> { let (code, bytes) = decode::u16(&input).map_err(|_| DecodeError::BadInputLength)?; let alg = Hash::from_code(code).ok_or(DecodeError::UnknownCode)?; + + // handle the identity case + if alg == Hash::Identity { + let (hash_len, bytes) = decode::u32(&bytes).map_err(|_| DecodeError::BadInputLength)?; + if as_u64(bytes.len()) != u64::from(hash_len) { + return Err(DecodeError::BadInputLength); + } + return Ok(MultihashRef { bytes: input }); + } + let hash_len = usize::from(alg.size()); // Length of input after hash code should be exactly hash_len + 1 if bytes.len() != hash_len + 1 { - return Err(DecodeError::BadInputLength) + return Err(DecodeError::BadInputLength); } if usize::from(bytes[0]) != hash_len { - return Err(DecodeError::BadInputLength) + return Err(DecodeError::BadInputLength); } Ok(MultihashRef { bytes: input }) @@ -231,6 +263,11 @@ impl<'a> PartialEq for MultihashRef<'a> { } } +#[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] +fn as_u64(a: usize) -> u64 { + a as u64 +} + /// Convert bytes to a hex representation pub fn to_hex(bytes: &[u8]) -> String { let mut hex = String::with_capacity(bytes.len() * 2);