litep2p/
peer_id.rs

1// Copyright 2018 Parity Technologies (UK) Ltd.
2// Copyright 2023 litep2p developers
3//
4// Permission is hereby granted, free of charge, to any person obtaining a
5// copy of this software and associated documentation files (the "Software"),
6// to deal in the Software without restriction, including without limitation
7// the rights to use, copy, modify, merge, publish, distribute, sublicense,
8// and/or sell copies of the Software, and to permit persons to whom the
9// Software is furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20// DEALINGS IN THE SOFTWARE.
21
22#![allow(clippy::wrong_self_convention)]
23
24use crate::crypto::PublicKey;
25
26use multiaddr::{Multiaddr, Protocol};
27use multihash_codetable::{Code, MultihashDigest};
28use rand::Rng;
29use serde::{Deserialize, Serialize};
30use thiserror::Error;
31
32use std::{convert::TryFrom, fmt, str::FromStr};
33
34/// Public keys with byte-lengths smaller than `MAX_INLINE_KEY_LENGTH` will be
35/// automatically used as the peer id using an identity multihash.
36const MAX_INLINE_KEY_LENGTH: usize = 42;
37pub(crate) const MULTIHASH_IDENTITY_CODE: u64 = 0x00;
38type Multihash = multihash::Multihash<64>;
39
40/// Identifier of a peer of the network.
41///
42/// The data is a CIDv0 compatible multihash of the protobuf encoded public key of the peer
43/// as specified in [specs/peer-ids](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md).
44#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
45pub struct PeerId {
46    multihash: Multihash,
47}
48
49impl fmt::Debug for PeerId {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_tuple("PeerId").field(&self.to_base58()).finish()
52    }
53}
54
55impl fmt::Display for PeerId {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        self.to_base58().fmt(f)
58    }
59}
60
61impl PeerId {
62    /// Builds a `PeerId` from a public key.
63    pub fn from_public_key(key: &PublicKey) -> PeerId {
64        Self::from_public_key_protobuf(&key.to_protobuf_encoding())
65    }
66
67    /// Builds a `PeerId` from a public key in protobuf encoding.
68    pub fn from_public_key_protobuf(key_enc: &[u8]) -> PeerId {
69        let multihash = if key_enc.len() <= MAX_INLINE_KEY_LENGTH {
70            Multihash::wrap(MULTIHASH_IDENTITY_CODE, key_enc)
71                .expect("key_enc.len() <= MAX_INLINE_KEY_LENGTH which fits in Multihash<64>")
72        } else {
73            Code::Sha2_256.digest(key_enc)
74        };
75
76        PeerId { multihash }
77    }
78
79    /// Parses a `PeerId` from bytes.
80    pub fn from_bytes(data: &[u8]) -> Result<PeerId, ParseError> {
81        let multihash = Multihash::from_bytes(data).map_err(|error| {
82            tracing::debug!(?error, "failed to decode peer ID bytes as multihash",);
83            ParseError::MultiHash
84        })?;
85
86        PeerId::from_multihash(multihash).map_err(|multihash| {
87            tracing::debug!(
88                code = multihash.code(),
89                digest_len = multihash.digest().len(),
90                "decoded multihash is not a valid peer ID",
91            );
92            ParseError::MultiHash
93        })
94    }
95
96    /// Tries to turn a `Multihash` into a `PeerId`.
97    ///
98    /// If the multihash does not use a valid hashing algorithm for peer IDs,
99    /// or the hash value does not satisfy the constraints for a hashed
100    /// peer ID, it is returned as an `Err`.
101    ///
102    /// Accepts anything that converts into `multihash::Multihash<64>`,
103    /// including `multiaddr::PeerId` which implements that conversion.
104    pub fn from_multihash(multihash: impl Into<Multihash>) -> Result<PeerId, Multihash> {
105        let multihash = multihash.into();
106
107        match multihash.code() {
108            code if code == u64::from(Code::Sha2_256) => Ok(PeerId { multihash }),
109            MULTIHASH_IDENTITY_CODE if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH =>
110                Ok(PeerId { multihash }),
111            _ => Err(multihash),
112        }
113    }
114
115    /// Tries to extract a [`PeerId`] from the given [`Multiaddr`].
116    ///
117    /// In case the given [`Multiaddr`] ends with `/p2p/<peer-id>`, this function
118    /// will return the encapsulated [`PeerId`], otherwise it will return `None`.
119    pub fn try_from_multiaddr(address: &Multiaddr) -> Option<PeerId> {
120        address.iter().last().and_then(|p| match p {
121            Protocol::P2p(peer_id) => PeerId::from_multihash(peer_id).ok(),
122            _ => None,
123        })
124    }
125
126    /// Generates a random peer ID from a cryptographically secure PRNG.
127    ///
128    /// This is useful for randomly walking on a DHT, or for testing purposes.
129    pub fn random() -> PeerId {
130        let peer_id = rand::thread_rng().gen::<[u8; 32]>();
131        PeerId {
132            multihash: Multihash::wrap(MULTIHASH_IDENTITY_CODE, &peer_id)
133                .expect("The digest size is never too large"),
134        }
135    }
136
137    /// Returns a raw bytes representation of this `PeerId`.
138    pub fn to_bytes(&self) -> Vec<u8> {
139        self.multihash.to_bytes()
140    }
141
142    /// Returns a base-58 encoded string of this `PeerId`.
143    pub fn to_base58(&self) -> String {
144        bs58::encode(self.to_bytes()).into_string()
145    }
146
147    /// Checks whether the public key passed as parameter matches the public key of this `PeerId`.
148    ///
149    /// Returns `None` if this `PeerId`s hash algorithm is not supported when encoding the
150    /// given public key, otherwise `Some` boolean as the result of an equality check.
151    pub fn is_public_key(&self, public_key: &PublicKey) -> Option<bool> {
152        let enc = public_key.to_protobuf_encoding();
153
154        let expected = match self.multihash.code() {
155            code if code == u64::from(Code::Sha2_256) => Code::Sha2_256.digest(&enc),
156            MULTIHASH_IDENTITY_CODE => Multihash::wrap(MULTIHASH_IDENTITY_CODE, &enc).expect(
157                "identity key enc fits in Multihash<64> since it passed from_public_key_protobuf",
158            ),
159            _ => return None,
160        };
161
162        Some(expected == self.multihash)
163    }
164
165    /// Converts this peer ID into a `multiaddr` peer ID.
166    ///
167    /// This is a fallible conversion for callers that want to avoid relying on
168    /// the internal invariant that litep2p and multiaddr peer IDs accept the
169    /// same multihash forms.
170    pub fn to_multiaddr_peer_id(&self) -> Result<multiaddr::PeerId, Multihash> {
171        multiaddr::PeerId::try_from(*self.as_ref())
172    }
173}
174
175impl From<PublicKey> for PeerId {
176    fn from(key: PublicKey) -> PeerId {
177        PeerId::from_public_key(&key)
178    }
179}
180
181impl From<&PublicKey> for PeerId {
182    fn from(key: &PublicKey) -> PeerId {
183        PeerId::from_public_key(key)
184    }
185}
186
187impl TryFrom<Vec<u8>> for PeerId {
188    type Error = Vec<u8>;
189
190    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
191        PeerId::from_bytes(&value).map_err(|_| value)
192    }
193}
194
195impl TryFrom<Multihash> for PeerId {
196    type Error = Multihash;
197
198    fn try_from(value: Multihash) -> Result<Self, Self::Error> {
199        PeerId::from_multihash(value)
200    }
201}
202
203impl AsRef<Multihash> for PeerId {
204    fn as_ref(&self) -> &Multihash {
205        &self.multihash
206    }
207}
208
209impl From<PeerId> for Multihash {
210    fn from(peer_id: PeerId) -> Self {
211        peer_id.multihash
212    }
213}
214
215impl From<PeerId> for Vec<u8> {
216    fn from(peer_id: PeerId) -> Self {
217        peer_id.to_bytes()
218    }
219}
220
221impl From<PeerId> for multiaddr::PeerId {
222    /// Converts this peer ID into a `multiaddr` peer ID.
223    ///
224    /// # Panics
225    ///
226    /// Panics only if `PeerId`'s internal invariant has been violated. Safe
227    /// constructors accept the same multihash forms that `multiaddr::PeerId`
228    /// accepts, so this conversion should not fail for any valid `PeerId`.
229    fn from(peer_id: PeerId) -> Self {
230        peer_id
231            .to_multiaddr_peer_id()
232            .expect("litep2p PeerId is always a valid multiaddr PeerId")
233    }
234}
235
236impl Serialize for PeerId {
237    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238    where
239        S: serde::Serializer,
240    {
241        if serializer.is_human_readable() {
242            serializer.serialize_str(&self.to_base58())
243        } else {
244            serializer.serialize_bytes(&self.to_bytes()[..])
245        }
246    }
247}
248
249impl<'de> Deserialize<'de> for PeerId {
250    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251    where
252        D: serde::Deserializer<'de>,
253    {
254        use serde::de::*;
255
256        struct PeerIdVisitor;
257
258        impl Visitor<'_> for PeerIdVisitor {
259            type Value = PeerId;
260
261            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
262                write!(f, "valid peer id")
263            }
264
265            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
266            where
267                E: Error,
268            {
269                PeerId::from_bytes(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self))
270            }
271
272            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
273            where
274                E: Error,
275            {
276                PeerId::from_str(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self))
277            }
278        }
279
280        if deserializer.is_human_readable() {
281            deserializer.deserialize_str(PeerIdVisitor)
282        } else {
283            deserializer.deserialize_bytes(PeerIdVisitor)
284        }
285    }
286}
287
288#[derive(Debug, Error)]
289pub enum ParseError {
290    #[error("base-58 decode error: {0}")]
291    B58(#[from] bs58::decode::Error),
292    #[error("decoding multihash failed")]
293    MultiHash,
294}
295
296impl FromStr for PeerId {
297    type Err = ParseError;
298
299    #[inline]
300    fn from_str(s: &str) -> Result<Self, Self::Err> {
301        let bytes = bs58::decode(s).into_vec()?;
302        PeerId::from_bytes(&bytes)
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use super::{ParseError, MAX_INLINE_KEY_LENGTH, MULTIHASH_IDENTITY_CODE};
309    use crate::{crypto::ed25519::Keypair, PeerId};
310    use multiaddr::{Multiaddr, Protocol};
311    use multihash::Multihash;
312    use multihash_codetable::Code;
313
314    #[test]
315    fn peer_id_is_public_key() {
316        let key = Keypair::generate().public();
317        let peer_id = key.to_peer_id();
318        assert_eq!(peer_id.is_public_key(&key.into()), Some(true));
319    }
320
321    #[test]
322    fn peer_id_into_bytes_then_from_bytes() {
323        let peer_id = Keypair::generate().public().to_peer_id();
324        let second = PeerId::from_bytes(&peer_id.to_bytes()).unwrap();
325        assert_eq!(peer_id, second);
326    }
327
328    #[test]
329    fn peer_id_to_base58_then_back() {
330        let peer_id = Keypair::generate().public().to_peer_id();
331        let second: PeerId = peer_id.to_base58().parse().unwrap();
332        assert_eq!(peer_id, second);
333    }
334
335    #[test]
336    fn random_peer_id_is_valid() {
337        for _ in 0..5000 {
338            let peer_id = PeerId::random();
339            assert_eq!(peer_id, PeerId::from_bytes(&peer_id.to_bytes()).unwrap());
340        }
341    }
342
343    #[test]
344    fn peer_id_from_multiaddr() {
345        let address = "[::1]:1337".parse::<std::net::SocketAddr>().unwrap();
346        let peer = PeerId::random();
347        let address = Multiaddr::empty()
348            .with(Protocol::from(address.ip()))
349            .with(Protocol::Tcp(address.port()))
350            .with(Protocol::P2p(peer.into()));
351
352        assert_eq!(peer, PeerId::try_from_multiaddr(&address).unwrap());
353    }
354
355    #[test]
356    fn peer_id_from_multiaddr_no_peer_id() {
357        let address = "[::1]:1337".parse::<std::net::SocketAddr>().unwrap();
358        let address = Multiaddr::empty()
359            .with(Protocol::from(address.ip()))
360            .with(Protocol::Tcp(address.port()));
361
362        assert!(PeerId::try_from_multiaddr(&address).is_none());
363    }
364
365    #[test]
366    fn peer_id_from_bytes() {
367        let peer = PeerId::random();
368        let bytes = peer.to_bytes();
369
370        assert_eq!(PeerId::try_from(bytes).unwrap(), peer);
371    }
372
373    #[test]
374    fn peer_id_as_multihash() {
375        let peer = PeerId::random();
376        let multihash = Multihash::from(peer);
377
378        assert_eq!(&multihash, peer.as_ref());
379        assert_eq!(PeerId::try_from(multihash).unwrap(), peer);
380    }
381
382    #[test]
383    fn serialize_deserialize() {
384        let peer = PeerId::random();
385        let serialized = serde_json::to_string(&peer).unwrap();
386        let deserialized = serde_json::from_str(&serialized).unwrap();
387
388        assert_eq!(peer, deserialized);
389    }
390
391    #[test]
392    fn invalid_multihash() {
393        fn test() -> crate::Result<PeerId> {
394            let bytes = [
395                0x16, 0x20, 0x64, 0x4b, 0xcc, 0x7e, 0x56, 0x43, 0x73, 0x04, 0x09, 0x99, 0xaa, 0xc8,
396                0x9e, 0x76, 0x22, 0xf3, 0xca, 0x71, 0xfb, 0xa1, 0xd9, 0x72, 0xfd, 0x94, 0xa3, 0x1c,
397                0x3b, 0xfb, 0xf2, 0x4e, 0x39, 0x38,
398            ];
399
400            PeerId::from_multihash(Multihash::from_bytes(&bytes).unwrap()).map_err(From::from)
401        }
402        let _error = test().unwrap_err();
403    }
404
405    // --- from_public_key_protobuf ---
406
407    #[test]
408    fn from_public_key_protobuf_small_key_uses_identity_hash() {
409        let key = Keypair::generate();
410        let enc = crate::crypto::PublicKey::from(key.public()).to_protobuf_encoding();
411        assert!(
412            enc.len() <= MAX_INLINE_KEY_LENGTH,
413            "ed25519 protobuf key must fit inline"
414        );
415
416        let peer_id = PeerId::from_public_key_protobuf(&enc);
417        assert_eq!(peer_id.as_ref().code(), MULTIHASH_IDENTITY_CODE);
418    }
419
420    #[test]
421    fn from_public_key_protobuf_identity_hash_stores_raw_bytes_not_rehash() {
422        // The identity multihash must store the key bytes verbatim, not hash them.
423        let key = Keypair::generate();
424        let enc = crate::crypto::PublicKey::from(key.public()).to_protobuf_encoding();
425
426        let peer_id = PeerId::from_public_key_protobuf(&enc);
427        assert_eq!(peer_id.as_ref().digest(), enc.as_slice());
428    }
429
430    #[test]
431    fn from_public_key_protobuf_large_key_uses_sha256() {
432        // Any encoding longer than MAX_INLINE_KEY_LENGTH (42) must use SHA-256.
433        let enc = vec![0u8; MAX_INLINE_KEY_LENGTH + 1];
434        let peer_id = PeerId::from_public_key_protobuf(&enc);
435        assert_eq!(peer_id.as_ref().code(), u64::from(Code::Sha2_256));
436    }
437
438    #[test]
439    fn from_public_key_protobuf_sha256_digest_is_hash_of_key_not_key_itself() {
440        // SHA-256 path must hash the key, not store it verbatim.
441        let enc = vec![0u8; MAX_INLINE_KEY_LENGTH + 1];
442        let peer_id = PeerId::from_public_key_protobuf(&enc);
443        // digest is 32 bytes (SHA-256 output), not the original key length
444        assert_eq!(peer_id.as_ref().digest().len(), 32);
445        assert_ne!(peer_id.as_ref().digest(), enc.as_slice());
446    }
447
448    // --- is_public_key ---
449
450    #[test]
451    fn is_public_key_returns_false_for_different_key() {
452        let key1 = Keypair::generate().public();
453        let key2 = Keypair::generate().public();
454        let peer_id = key1.to_peer_id();
455        assert_eq!(peer_id.is_public_key(&key2.into()), Some(false));
456    }
457
458    // --- from_multihash ---
459
460    #[test]
461    fn from_multihash_rejects_unsupported_code() {
462        // Code 0x16 (sha3-256) is not a valid peer ID hash.
463        let bytes = [
464            0x16, 0x20, 0x64, 0x4b, 0xcc, 0x7e, 0x56, 0x43, 0x73, 0x04, 0x09, 0x99, 0xaa, 0xc8,
465            0x9e, 0x76, 0x22, 0xf3, 0xca, 0x71, 0xfb, 0xa1, 0xd9, 0x72, 0xfd, 0x94, 0xa3, 0x1c,
466            0x3b, 0xfb, 0xf2, 0x4e, 0x39, 0x38,
467        ];
468        let mh = Multihash::from_bytes(&bytes).unwrap();
469        assert!(PeerId::from_multihash(mh).is_err());
470    }
471
472    // --- from_bytes ---
473
474    #[test]
475    fn from_bytes_rejects_invalid_peer_id_multihash() {
476        let bytes = [
477            0x16, 0x20, 0x64, 0x4b, 0xcc, 0x7e, 0x56, 0x43, 0x73, 0x04, 0x09, 0x99, 0xaa, 0xc8,
478            0x9e, 0x76, 0x22, 0xf3, 0xca, 0x71, 0xfb, 0xa1, 0xd9, 0x72, 0xfd, 0x94, 0xa3, 0x1c,
479            0x3b, 0xfb, 0xf2, 0x4e, 0x39, 0x38,
480        ];
481        assert!(matches!(
482            PeerId::from_bytes(&bytes),
483            Err(ParseError::MultiHash)
484        ));
485    }
486
487    #[test]
488    fn from_bytes_rejects_invalid_multihash_bytes() {
489        assert!(matches!(
490            PeerId::from_bytes(&[0xff]),
491            Err(ParseError::MultiHash)
492        ));
493    }
494
495    #[test]
496    fn from_str_rejects_invalid_base58() {
497        assert!(matches!(
498            "not base58: 0".parse::<PeerId>(),
499            Err(ParseError::B58(_))
500        ));
501    }
502
503    // --- identity and sha256 peer IDs roundtrip ---
504
505    #[test]
506    fn identity_peer_id_roundtrip_through_multiaddr() {
507        // PeerId::random() uses identity hash; verify it survives a multiaddr roundtrip.
508        let peer = PeerId::random();
509        assert_eq!(peer.as_ref().code(), MULTIHASH_IDENTITY_CODE);
510
511        let addr = Multiaddr::empty()
512            .with(Protocol::Ip4("127.0.0.1".parse().unwrap()))
513            .with(Protocol::Tcp(1234))
514            .with(Protocol::P2p(peer.into()));
515
516        assert_eq!(peer, PeerId::try_from_multiaddr(&addr).unwrap());
517    }
518
519    #[test]
520    fn sha256_peer_id_roundtrip_through_multiaddr() {
521        // Use a large synthetic key encoding to force sha256.
522        let enc = vec![42u8; MAX_INLINE_KEY_LENGTH + 1];
523        let peer = PeerId::from_public_key_protobuf(&enc);
524        assert_eq!(peer.as_ref().code(), u64::from(Code::Sha2_256));
525
526        let addr = Multiaddr::empty()
527            .with(Protocol::Ip4("127.0.0.1".parse().unwrap()))
528            .with(Protocol::Tcp(1234))
529            .with(Protocol::P2p(peer.into()));
530
531        assert_eq!(peer, PeerId::try_from_multiaddr(&addr).unwrap());
532    }
533}