informalsystems_malachitebft_peer/
lib.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#![no_std]
21extern crate alloc;
22
23use alloc::{
24    string::{String, ToString},
25    vec::Vec,
26};
27use core::{fmt, str::FromStr};
28
29use thiserror::Error;
30
31#[cfg(feature = "rand")]
32use rand::Rng;
33
34mod ser;
35
36/// Local type-alias for multihash.
37///
38/// Must be big enough to accommodate for `MAX_INLINE_KEY_LENGTH`.
39/// 64 satisfies that and can hold 512 bit hashes which is what the ecosystem typically uses.
40/// Given that this appears in our type-signature, using a "common" number here makes us more compatible.
41type Multihash = multihash::Multihash<64>;
42
43#[cfg(feature = "serde")]
44use serde::{Deserialize, Serialize};
45
46/// Public keys with byte-lengths smaller than `MAX_INLINE_KEY_LENGTH` will be
47/// automatically used as the peer id using an identity multihash.
48const MAX_INLINE_KEY_LENGTH: usize = 42;
49
50const MULTIHASH_IDENTITY_CODE: u64 = 0;
51const MULTIHASH_SHA256_CODE: u64 = 0x12;
52
53/// Identifier of a peer of the network.
54///
55/// The data is a CIDv0 compatible multihash of the protobuf encoded public key of the peer
56/// as specified in [specs/peer-ids](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md).
57#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
58pub struct PeerId {
59    multihash: Multihash,
60}
61
62impl fmt::Debug for PeerId {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        f.debug_tuple("PeerId").field(&self.to_base58()).finish()
65    }
66}
67
68impl fmt::Display for PeerId {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        self.to_base58().fmt(f)
71    }
72}
73
74impl PeerId {
75    /// Parses a `PeerId` from bytes.
76    pub fn from_bytes(data: &[u8]) -> Result<PeerId, ParseError> {
77        PeerId::from_multihash(
78            Multihash::from_bytes(data).map_err(|e| ParseError::InvalidMultihash(e.to_string()))?,
79        )
80        .map_err(|mh| ParseError::UnsupportedCode(mh.code()))
81    }
82
83    /// Tries to turn a `Multihash` into a `PeerId`.
84    ///
85    /// If the multihash does not use a valid hashing algorithm for peer IDs,
86    /// or the hash value does not satisfy the constraints for a hashed
87    /// peer ID, it is returned as an `Err`.
88    pub fn from_multihash(multihash: Multihash) -> Result<PeerId, Multihash> {
89        match multihash.code() {
90            MULTIHASH_SHA256_CODE => Ok(PeerId { multihash }),
91            MULTIHASH_IDENTITY_CODE if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH => {
92                Ok(PeerId { multihash })
93            }
94            _ => Err(multihash),
95        }
96    }
97
98    /// Generates a random peer ID from a cryptographically secure PRNG.
99    ///
100    /// This is useful for randomly walking on a DHT, or for testing purposes.
101    #[cfg(feature = "rand")]
102    pub fn random() -> PeerId {
103        let peer_id = rand::thread_rng().gen::<[u8; 32]>();
104        PeerId {
105            multihash: Multihash::wrap(0x0, &peer_id).expect("The digest size is never too large"),
106        }
107    }
108
109    /// Returns a raw bytes representation of this `PeerId`.
110    pub fn to_bytes(self) -> Vec<u8> {
111        self.multihash.to_bytes()
112    }
113
114    /// Returns a base-58 encoded string of this `PeerId`.
115    pub fn to_base58(self) -> String {
116        bs58::encode(self.to_bytes()).into_string()
117    }
118}
119
120impl TryFrom<Vec<u8>> for PeerId {
121    type Error = Vec<u8>;
122
123    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
124        PeerId::from_bytes(&value).map_err(|_| value)
125    }
126}
127
128impl TryFrom<Multihash> for PeerId {
129    type Error = Multihash;
130
131    fn try_from(value: Multihash) -> Result<Self, Self::Error> {
132        PeerId::from_multihash(value)
133    }
134}
135
136impl AsRef<Multihash> for PeerId {
137    fn as_ref(&self) -> &Multihash {
138        &self.multihash
139    }
140}
141
142impl From<PeerId> for Multihash {
143    fn from(peer_id: PeerId) -> Self {
144        peer_id.multihash
145    }
146}
147
148impl From<PeerId> for Vec<u8> {
149    fn from(peer_id: PeerId) -> Self {
150        peer_id.to_bytes()
151    }
152}
153
154#[cfg(feature = "serde")]
155impl Serialize for PeerId {
156    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157    where
158        S: serde::Serializer,
159    {
160        if serializer.is_human_readable() {
161            serializer.serialize_str(&self.to_base58())
162        } else {
163            serializer.serialize_bytes(&self.to_bytes()[..])
164        }
165    }
166}
167
168#[cfg(feature = "serde")]
169impl<'de> Deserialize<'de> for PeerId {
170    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171    where
172        D: serde::Deserializer<'de>,
173    {
174        use serde::de::*;
175
176        struct PeerIdVisitor;
177
178        impl Visitor<'_> for PeerIdVisitor {
179            type Value = PeerId;
180
181            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
182                write!(f, "valid peer id")
183            }
184
185            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
186            where
187                E: Error,
188            {
189                PeerId::from_bytes(v).map_err(|_| Error::invalid_value(Unexpected::Bytes(v), &self))
190            }
191
192            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
193            where
194                E: Error,
195            {
196                PeerId::from_str(v).map_err(|_| Error::invalid_value(Unexpected::Str(v), &self))
197            }
198        }
199
200        if deserializer.is_human_readable() {
201            deserializer.deserialize_str(PeerIdVisitor)
202        } else {
203            deserializer.deserialize_bytes(PeerIdVisitor)
204        }
205    }
206}
207
208/// Error when parsing a [`PeerId`] from string or bytes.
209#[derive(Debug, Error)]
210pub enum ParseError {
211    #[error("base-58 decode error: {0}")]
212    B58(String),
213    #[error("unsupported multihash code '{0}'")]
214    UnsupportedCode(u64),
215    #[error("invalid multihash: {0}")]
216    InvalidMultihash(String),
217}
218
219impl FromStr for PeerId {
220    type Err = ParseError;
221
222    #[inline]
223    fn from_str(s: &str) -> Result<Self, Self::Err> {
224        let bytes = bs58::decode(s)
225            .into_vec()
226            .map_err(|e| ParseError::B58(e.to_string()))?;
227        let peer_id = PeerId::from_bytes(&bytes)?;
228
229        Ok(peer_id)
230    }
231}