libp2p_identity/
peer_id.rs

1// Copyright 2018 Parity Technologies (UK) Ltd.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21use std::{fmt, str::FromStr};
22
23#[cfg(feature = "rand")]
24use rand::Rng;
25use sha2::Digest as _;
26use thiserror::Error;
27
28/// Local type-alias for multihash.
29///
30/// Must be big enough to accommodate for `MAX_INLINE_KEY_LENGTH`.
31/// 64 satisfies that and can hold 512 bit hashes which is what the ecosystem typically uses.
32/// Given that this appears in our type-signature,
33/// using a "common" number here makes us more compatible.
34type Multihash = multihash::Multihash<64>;
35
36#[cfg(feature = "serde")]
37use serde::{Deserialize, Serialize};
38
39/// Public keys with byte-lengths smaller than `MAX_INLINE_KEY_LENGTH` will be
40/// automatically used as the peer id using an identity multihash.
41const MAX_INLINE_KEY_LENGTH: usize = 42;
42
43const MULTIHASH_IDENTITY_CODE: u64 = 0;
44const MULTIHASH_SHA256_CODE: u64 = 0x12;
45
46/// Identifier of a peer of the network.
47///
48/// The data is a CIDv0 compatible multihash of the protobuf encoded public key of the peer
49/// as specified in [specs/peer-ids](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md).
50#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
51pub struct PeerId {
52    multihash: Multihash,
53}
54
55impl fmt::Debug for PeerId {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_tuple("PeerId").field(&self.to_base58()).finish()
58    }
59}
60
61impl fmt::Display for PeerId {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        self.to_base58().fmt(f)
64    }
65}
66
67impl PeerId {
68    /// Builds a `PeerId` from a public key.
69    pub fn from_public_key(key: &crate::keypair::PublicKey) -> PeerId {
70        let key_enc = key.encode_protobuf();
71
72        let multihash = if key_enc.len() <= MAX_INLINE_KEY_LENGTH {
73            Multihash::wrap(MULTIHASH_IDENTITY_CODE, &key_enc)
74                .expect("64 byte multihash provides sufficient space")
75        } else {
76            Multihash::wrap(MULTIHASH_SHA256_CODE, &sha2::Sha256::digest(key_enc))
77                .expect("64 byte multihash provides sufficient space")
78        };
79
80        PeerId { multihash }
81    }
82
83    /// Parses a `PeerId` from bytes.
84    pub fn from_bytes(data: &[u8]) -> Result<PeerId, ParseError> {
85        PeerId::from_multihash(Multihash::from_bytes(data)?)
86            .map_err(|mh| ParseError::UnsupportedCode(mh.code()))
87    }
88
89    /// Tries to turn a `Multihash` into a `PeerId`.
90    ///
91    /// If the multihash does not use a valid hashing algorithm for peer IDs,
92    /// or the hash value does not satisfy the constraints for a hashed
93    /// peer ID, it is returned as an `Err`.
94    pub fn from_multihash(multihash: Multihash) -> Result<PeerId, Multihash> {
95        match multihash.code() {
96            MULTIHASH_SHA256_CODE => Ok(PeerId { multihash }),
97            MULTIHASH_IDENTITY_CODE if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH => {
98                Ok(PeerId { multihash })
99            }
100            _ => Err(multihash),
101        }
102    }
103
104    /// Generates a random peer ID from a cryptographically secure PRNG.
105    ///
106    /// This is useful for randomly walking on a DHT, or for testing purposes.
107    #[cfg(feature = "rand")]
108    pub fn random() -> PeerId {
109        let peer_id = rand::thread_rng().gen::<[u8; 32]>();
110        PeerId {
111            multihash: Multihash::wrap(0x0, &peer_id).expect("The digest size is never too large"),
112        }
113    }
114
115    /// Returns a raw bytes representation of this `PeerId`.
116    pub fn to_bytes(self) -> Vec<u8> {
117        self.multihash.to_bytes()
118    }
119
120    /// Returns a base-58 encoded string of this `PeerId`.
121    pub fn to_base58(self) -> String {
122        bs58::encode(self.to_bytes()).into_string()
123    }
124}
125
126impl From<crate::PublicKey> for PeerId {
127    fn from(key: crate::PublicKey) -> PeerId {
128        PeerId::from_public_key(&key)
129    }
130}
131
132impl From<&crate::PublicKey> for PeerId {
133    fn from(key: &crate::PublicKey) -> PeerId {
134        PeerId::from_public_key(key)
135    }
136}
137
138impl TryFrom<Vec<u8>> for PeerId {
139    type Error = Vec<u8>;
140
141    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
142        PeerId::from_bytes(&value).map_err(|_| value)
143    }
144}
145
146impl TryFrom<Multihash> for PeerId {
147    type Error = Multihash;
148
149    fn try_from(value: Multihash) -> Result<Self, Self::Error> {
150        PeerId::from_multihash(value)
151    }
152}
153
154impl AsRef<Multihash> for PeerId {
155    fn as_ref(&self) -> &Multihash {
156        &self.multihash
157    }
158}
159
160impl From<PeerId> for Multihash {
161    fn from(peer_id: PeerId) -> Self {
162        peer_id.multihash
163    }
164}
165
166impl From<PeerId> for Vec<u8> {
167    fn from(peer_id: PeerId) -> Self {
168        peer_id.to_bytes()
169    }
170}
171
172#[cfg(feature = "serde")]
173impl Serialize for PeerId {
174    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175    where
176        S: serde::Serializer,
177    {
178        if serializer.is_human_readable() {
179            serializer.serialize_str(&self.to_base58())
180        } else {
181            serializer.serialize_bytes(&self.to_bytes()[..])
182        }
183    }
184}
185
186#[cfg(feature = "serde")]
187impl<'de> Deserialize<'de> for PeerId {
188    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        use serde::de::*;
193
194        struct PeerIdVisitor;
195
196        impl Visitor<'_> for PeerIdVisitor {
197            type Value = PeerId;
198
199            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
200                write!(f, "valid peer id")
201            }
202
203            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
204            where
205                E: Error,
206            {
207                PeerId::from_bytes(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self))
208            }
209
210            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
211            where
212                E: Error,
213            {
214                PeerId::from_str(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self))
215            }
216        }
217
218        if deserializer.is_human_readable() {
219            deserializer.deserialize_str(PeerIdVisitor)
220        } else {
221            deserializer.deserialize_bytes(PeerIdVisitor)
222        }
223    }
224}
225
226/// Error when parsing a [`PeerId`] from string or bytes.
227#[derive(Debug, Error)]
228pub enum ParseError {
229    #[error("base-58 decode error: {0}")]
230    B58(#[from] bs58::decode::Error),
231    #[error("unsupported multihash code '{0}'")]
232    UnsupportedCode(u64),
233    #[error("invalid multihash")]
234    InvalidMultihash(#[from] multihash::Error),
235}
236
237impl FromStr for PeerId {
238    type Err = ParseError;
239
240    #[inline]
241    fn from_str(s: &str) -> Result<Self, Self::Err> {
242        let bytes = bs58::decode(s).into_vec()?;
243        let peer_id = PeerId::from_bytes(&bytes)?;
244
245        Ok(peer_id)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    #[cfg(all(feature = "ed25519", feature = "rand"))]
255    fn peer_id_into_bytes_then_from_bytes() {
256        let peer_id = crate::Keypair::generate_ed25519().public().to_peer_id();
257        let second = PeerId::from_bytes(&peer_id.to_bytes()).unwrap();
258        assert_eq!(peer_id, second);
259    }
260
261    #[test]
262    #[cfg(all(feature = "ed25519", feature = "rand"))]
263    fn peer_id_to_base58_then_back() {
264        let peer_id = crate::Keypair::generate_ed25519().public().to_peer_id();
265        let second: PeerId = peer_id.to_base58().parse().unwrap();
266        assert_eq!(peer_id, second);
267    }
268
269    #[test]
270    #[cfg(feature = "rand")]
271    fn random_peer_id_is_valid() {
272        for _ in 0..5000 {
273            let peer_id = PeerId::random();
274            assert_eq!(peer_id, PeerId::from_bytes(&peer_id.to_bytes()).unwrap());
275        }
276    }
277}