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::{Code, Error, Multihash, 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;
37
38/// Identifier of a peer of the network.
39///
40/// The data is a CIDv0 compatible multihash of the protobuf encoded public key of the peer
41/// as specified in [specs/peer-ids](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md).
42#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
43pub struct PeerId {
44    multihash: Multihash,
45}
46
47impl fmt::Debug for PeerId {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.debug_tuple("PeerId").field(&self.to_base58()).finish()
50    }
51}
52
53impl fmt::Display for PeerId {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        self.to_base58().fmt(f)
56    }
57}
58
59impl PeerId {
60    /// Builds a `PeerId` from a public key.
61    pub fn from_public_key(key: &PublicKey) -> PeerId {
62        let key_enc = key.to_protobuf_encoding();
63
64        let hash_algorithm = if key_enc.len() <= MAX_INLINE_KEY_LENGTH {
65            Code::Identity
66        } else {
67            Code::Sha2_256
68        };
69
70        let multihash = hash_algorithm.digest(&key_enc);
71
72        PeerId { multihash }
73    }
74
75    /// Parses a `PeerId` from bytes.
76    pub fn from_bytes(data: &[u8]) -> Result<PeerId, Error> {
77        PeerId::from_multihash(Multihash::from_bytes(data)?)
78            .map_err(|mh| Error::UnsupportedCode(mh.code()))
79    }
80
81    /// Tries to turn a `Multihash` into a `PeerId`.
82    ///
83    /// If the multihash does not use a valid hashing algorithm for peer IDs,
84    /// or the hash value does not satisfy the constraints for a hashed
85    /// peer ID, it is returned as an `Err`.
86    pub fn from_multihash(multihash: Multihash) -> Result<PeerId, Multihash> {
87        match Code::try_from(multihash.code()) {
88            Ok(Code::Sha2_256) => Ok(PeerId { multihash }),
89            Ok(Code::Identity) if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH =>
90                Ok(PeerId { multihash }),
91            _ => Err(multihash),
92        }
93    }
94
95    /// Tries to extract a [`PeerId`] from the given [`Multiaddr`].
96    ///
97    /// In case the given [`Multiaddr`] ends with `/p2p/<peer-id>`, this function
98    /// will return the encapsulated [`PeerId`], otherwise it will return `None`.
99    pub fn try_from_multiaddr(address: &Multiaddr) -> Option<PeerId> {
100        address.iter().last().and_then(|p| match p {
101            Protocol::P2p(hash) => PeerId::from_multihash(hash).ok(),
102            _ => None,
103        })
104    }
105
106    /// Generates a random peer ID from a cryptographically secure PRNG.
107    ///
108    /// This is useful for randomly walking on a DHT, or for testing purposes.
109    pub fn random() -> PeerId {
110        let peer_id = rand::thread_rng().gen::<[u8; 32]>();
111        PeerId {
112            multihash: Multihash::wrap(Code::Identity.into(), &peer_id)
113                .expect("The digest size is never too large"),
114        }
115    }
116
117    /// Returns a raw bytes representation of this `PeerId`.
118    pub fn to_bytes(&self) -> Vec<u8> {
119        self.multihash.to_bytes()
120    }
121
122    /// Returns a base-58 encoded string of this `PeerId`.
123    pub fn to_base58(&self) -> String {
124        bs58::encode(self.to_bytes()).into_string()
125    }
126
127    /// Checks whether the public key passed as parameter matches the public key of this `PeerId`.
128    ///
129    /// Returns `None` if this `PeerId`s hash algorithm is not supported when encoding the
130    /// given public key, otherwise `Some` boolean as the result of an equality check.
131    pub fn is_public_key(&self, public_key: &PublicKey) -> Option<bool> {
132        let alg = Code::try_from(self.multihash.code())
133            .expect("Internal multihash is always a valid `Code`");
134        let enc = public_key.to_protobuf_encoding();
135        Some(alg.digest(&enc) == self.multihash)
136    }
137}
138
139impl From<PublicKey> for PeerId {
140    fn from(key: PublicKey) -> PeerId {
141        PeerId::from_public_key(&key)
142    }
143}
144
145impl From<&PublicKey> for PeerId {
146    fn from(key: &PublicKey) -> PeerId {
147        PeerId::from_public_key(key)
148    }
149}
150
151impl TryFrom<Vec<u8>> for PeerId {
152    type Error = Vec<u8>;
153
154    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
155        PeerId::from_bytes(&value).map_err(|_| value)
156    }
157}
158
159impl TryFrom<Multihash> for PeerId {
160    type Error = Multihash;
161
162    fn try_from(value: Multihash) -> Result<Self, Self::Error> {
163        PeerId::from_multihash(value)
164    }
165}
166
167impl AsRef<Multihash> for PeerId {
168    fn as_ref(&self) -> &Multihash {
169        &self.multihash
170    }
171}
172
173impl From<PeerId> for Multihash {
174    fn from(peer_id: PeerId) -> Self {
175        peer_id.multihash
176    }
177}
178
179impl From<PeerId> for Vec<u8> {
180    fn from(peer_id: PeerId) -> Self {
181        peer_id.to_bytes()
182    }
183}
184
185impl Serialize for PeerId {
186    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
187    where
188        S: serde::Serializer,
189    {
190        if serializer.is_human_readable() {
191            serializer.serialize_str(&self.to_base58())
192        } else {
193            serializer.serialize_bytes(&self.to_bytes()[..])
194        }
195    }
196}
197
198impl<'de> Deserialize<'de> for PeerId {
199    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200    where
201        D: serde::Deserializer<'de>,
202    {
203        use serde::de::*;
204
205        struct PeerIdVisitor;
206
207        impl<'de> Visitor<'de> for PeerIdVisitor {
208            type Value = PeerId;
209
210            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
211                write!(f, "valid peer id")
212            }
213
214            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
215            where
216                E: Error,
217            {
218                PeerId::from_bytes(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self))
219            }
220
221            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
222            where
223                E: Error,
224            {
225                PeerId::from_str(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self))
226            }
227        }
228
229        if deserializer.is_human_readable() {
230            deserializer.deserialize_str(PeerIdVisitor)
231        } else {
232            deserializer.deserialize_bytes(PeerIdVisitor)
233        }
234    }
235}
236
237#[derive(Debug, Error)]
238pub enum ParseError {
239    #[error("base-58 decode error: {0}")]
240    B58(#[from] bs58::decode::Error),
241    #[error("decoding multihash failed")]
242    MultiHash,
243}
244
245impl FromStr for PeerId {
246    type Err = ParseError;
247
248    #[inline]
249    fn from_str(s: &str) -> Result<Self, Self::Err> {
250        let bytes = bs58::decode(s).into_vec()?;
251        PeerId::from_bytes(&bytes).map_err(|_| ParseError::MultiHash)
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use crate::{crypto::ed25519::Keypair, PeerId};
258    use multiaddr::{Multiaddr, Protocol};
259    use multihash::Multihash;
260
261    #[test]
262    fn peer_id_is_public_key() {
263        let key = Keypair::generate().public();
264        let peer_id = key.to_peer_id();
265        assert_eq!(peer_id.is_public_key(&key.into()), Some(true));
266    }
267
268    #[test]
269    fn peer_id_into_bytes_then_from_bytes() {
270        let peer_id = Keypair::generate().public().to_peer_id();
271        let second = PeerId::from_bytes(&peer_id.to_bytes()).unwrap();
272        assert_eq!(peer_id, second);
273    }
274
275    #[test]
276    fn peer_id_to_base58_then_back() {
277        let peer_id = Keypair::generate().public().to_peer_id();
278        let second: PeerId = peer_id.to_base58().parse().unwrap();
279        assert_eq!(peer_id, second);
280    }
281
282    #[test]
283    fn random_peer_id_is_valid() {
284        for _ in 0..5000 {
285            let peer_id = PeerId::random();
286            assert_eq!(peer_id, PeerId::from_bytes(&peer_id.to_bytes()).unwrap());
287        }
288    }
289
290    #[test]
291    fn peer_id_from_multiaddr() {
292        let address = "[::1]:1337".parse::<std::net::SocketAddr>().unwrap();
293        let peer = PeerId::random();
294        let address = Multiaddr::empty()
295            .with(Protocol::from(address.ip()))
296            .with(Protocol::Tcp(address.port()))
297            .with(Protocol::P2p(Multihash::from(peer)));
298
299        assert_eq!(peer, PeerId::try_from_multiaddr(&address).unwrap());
300    }
301
302    #[test]
303    fn peer_id_from_multiaddr_no_peer_id() {
304        let address = "[::1]:1337".parse::<std::net::SocketAddr>().unwrap();
305        let address = Multiaddr::empty()
306            .with(Protocol::from(address.ip()))
307            .with(Protocol::Tcp(address.port()));
308
309        assert!(PeerId::try_from_multiaddr(&address).is_none());
310    }
311
312    #[test]
313    fn peer_id_from_bytes() {
314        let peer = PeerId::random();
315        let bytes = peer.to_bytes();
316
317        assert_eq!(PeerId::try_from(bytes).unwrap(), peer);
318    }
319
320    #[test]
321    fn peer_id_as_multihash() {
322        let peer = PeerId::random();
323        let multihash = Multihash::from(peer);
324
325        assert_eq!(&multihash, peer.as_ref());
326        assert_eq!(PeerId::try_from(multihash).unwrap(), peer);
327    }
328
329    #[test]
330    fn serialize_deserialize() {
331        let peer = PeerId::random();
332        let serialized = serde_json::to_string(&peer).unwrap();
333        let deserialized = serde_json::from_str(&serialized).unwrap();
334
335        assert_eq!(peer, deserialized);
336    }
337
338    #[test]
339    fn invalid_multihash() {
340        fn test() -> crate::Result<PeerId> {
341            let bytes = [
342                0x16, 0x20, 0x64, 0x4b, 0xcc, 0x7e, 0x56, 0x43, 0x73, 0x04, 0x09, 0x99, 0xaa, 0xc8,
343                0x9e, 0x76, 0x22, 0xf3, 0xca, 0x71, 0xfb, 0xa1, 0xd9, 0x72, 0xfd, 0x94, 0xa3, 0x1c,
344                0x3b, 0xfb, 0xf2, 0x4e, 0x39, 0x38,
345            ];
346
347            PeerId::from_multihash(Multihash::from_bytes(&bytes).unwrap()).map_err(From::from)
348        }
349        let _error = test().unwrap_err();
350    }
351}