referrerpolicy=no-referrer-when-downgrade

sc_network_types/
peer_id.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::{
20	multiaddr::{Multiaddr, Protocol},
21	multihash::{Code, Error, Multihash},
22};
23use rand::Rng;
24use serde_with::{DeserializeFromStr, SerializeDisplay};
25
26use std::{fmt, hash::Hash, str::FromStr};
27
28/// Public keys with byte-lengths smaller than `MAX_INLINE_KEY_LENGTH` will be
29/// automatically used as the peer id using an identity multihash.
30const MAX_INLINE_KEY_LENGTH: usize = 42;
31
32/// Identifier of a peer of the network.
33///
34/// The data is a CIDv0 compatible multihash of the protobuf encoded public key of the peer
35/// as specified in [specs/peer-ids](https://github.com/libp2p/specs/blob/master/peer-ids/peer-ids.md).
36#[derive(
37	Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd, SerializeDisplay, DeserializeFromStr,
38)]
39pub struct PeerId {
40	multihash: Multihash,
41}
42
43impl fmt::Debug for PeerId {
44	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45		f.debug_tuple("PeerId").field(&self.to_base58()).finish()
46	}
47}
48
49impl fmt::Display for PeerId {
50	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51		self.to_base58().fmt(f)
52	}
53}
54
55impl PeerId {
56	/// Generate random peer ID.
57	pub fn random() -> PeerId {
58		let peer = rand::thread_rng().gen::<[u8; 32]>();
59		PeerId {
60			multihash: Multihash::wrap(0x0, &peer).expect("The digest size is never too large"),
61		}
62	}
63
64	/// Try to extract `PeerId` from `Multiaddr`.
65	pub fn try_from_multiaddr(address: &Multiaddr) -> Option<PeerId> {
66		match address.iter().find(|protocol| std::matches!(protocol, Protocol::P2p(_))) {
67			Some(Protocol::P2p(multihash)) => Some(Self { multihash }),
68			_ => None,
69		}
70	}
71
72	/// Tries to turn a `Multihash` into a `PeerId`.
73	///
74	/// If the multihash does not use a valid hashing algorithm for peer IDs,
75	/// or the hash value does not satisfy the constraints for a hashed
76	/// peer ID, it is returned as an `Err`.
77	pub fn from_multihash(multihash: Multihash) -> Result<PeerId, Multihash> {
78		match Code::try_from(multihash.code()) {
79			Ok(Code::Sha2_256) => Ok(PeerId { multihash }),
80			Ok(Code::Identity) if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH =>
81				Ok(PeerId { multihash }),
82			_ => Err(multihash),
83		}
84	}
85
86	/// Parses a `PeerId` from bytes.
87	pub fn from_bytes(data: &[u8]) -> Result<PeerId, Error> {
88		PeerId::from_multihash(Multihash::from_bytes(data)?)
89			.map_err(|mh| Error::UnsupportedCode(mh.code()))
90	}
91
92	/// Returns a raw bytes representation of this `PeerId`.
93	pub fn to_bytes(&self) -> Vec<u8> {
94		self.multihash.to_bytes()
95	}
96
97	/// Returns a base-58 encoded string of this `PeerId`.
98	pub fn to_base58(&self) -> String {
99		bs58::encode(self.to_bytes()).into_string()
100	}
101
102	/// Convert `PeerId` into ed25519 public key bytes.
103	pub fn into_ed25519(&self) -> Option<[u8; 32]> {
104		let hash = &self.multihash;
105		// https://www.ietf.org/archive/id/draft-multiformats-multihash-07.html#name-the-multihash-identifier-re
106		if hash.code() != 0 {
107			// Hash is not identity
108			return None
109		}
110
111		let public = libp2p_identity::PublicKey::try_decode_protobuf(hash.digest()).ok()?;
112		public.try_into_ed25519().ok().map(|public| public.to_bytes())
113	}
114
115	/// Get `PeerId` from ed25519 public key bytes.
116	pub fn from_ed25519(bytes: &[u8; 32]) -> Option<PeerId> {
117		let public = libp2p_identity::ed25519::PublicKey::try_from_bytes(bytes).ok()?;
118		let public: libp2p_identity::PublicKey = public.into();
119		let peer_id: libp2p_identity::PeerId = public.into();
120
121		Some(peer_id.into())
122	}
123}
124
125impl AsRef<Multihash> for PeerId {
126	fn as_ref(&self) -> &Multihash {
127		&self.multihash
128	}
129}
130
131impl From<PeerId> for Multihash {
132	fn from(peer_id: PeerId) -> Self {
133		peer_id.multihash
134	}
135}
136
137impl From<libp2p_identity::PeerId> for PeerId {
138	fn from(peer_id: libp2p_identity::PeerId) -> Self {
139		PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
140	}
141}
142
143impl From<PeerId> for libp2p_identity::PeerId {
144	fn from(peer_id: PeerId) -> Self {
145		libp2p_identity::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
146	}
147}
148
149impl From<&libp2p_identity::PeerId> for PeerId {
150	fn from(peer_id: &libp2p_identity::PeerId) -> Self {
151		PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
152	}
153}
154
155impl From<&PeerId> for libp2p_identity::PeerId {
156	fn from(peer_id: &PeerId) -> Self {
157		libp2p_identity::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
158	}
159}
160
161impl From<litep2p::PeerId> for PeerId {
162	fn from(peer_id: litep2p::PeerId) -> Self {
163		PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
164	}
165}
166
167impl From<PeerId> for litep2p::PeerId {
168	fn from(peer_id: PeerId) -> Self {
169		litep2p::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
170	}
171}
172
173impl From<&litep2p::PeerId> for PeerId {
174	fn from(peer_id: &litep2p::PeerId) -> Self {
175		PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
176	}
177}
178
179impl From<&PeerId> for litep2p::PeerId {
180	fn from(peer_id: &PeerId) -> Self {
181		litep2p::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
182	}
183}
184
185/// Error when parsing a [`PeerId`] from string or bytes.
186#[derive(Debug, thiserror::Error)]
187pub enum ParseError {
188	#[error("base-58 decode error: {0}")]
189	B58(#[from] bs58::decode::Error),
190	#[error("unsupported multihash code '{0}'")]
191	UnsupportedCode(u64),
192	#[error("invalid multihash")]
193	InvalidMultihash(#[from] crate::multihash::Error),
194}
195
196impl FromStr for PeerId {
197	type Err = ParseError;
198
199	#[inline]
200	fn from_str(s: &str) -> Result<Self, Self::Err> {
201		let bytes = bs58::decode(s).into_vec()?;
202		let peer_id = PeerId::from_bytes(&bytes)?;
203
204		Ok(peer_id)
205	}
206}
207
208#[cfg(test)]
209mod tests {
210	use super::*;
211
212	#[test]
213	fn extract_peer_id_from_multiaddr() {
214		{
215			let peer = PeerId::random();
216			let address = "/ip4/198.51.100.19/tcp/30333"
217				.parse::<Multiaddr>()
218				.unwrap()
219				.with(Protocol::P2p(peer.into()));
220
221			assert_eq!(PeerId::try_from_multiaddr(&address), Some(peer));
222		}
223
224		{
225			let peer = PeerId::random();
226			assert_eq!(
227				PeerId::try_from_multiaddr(&Multiaddr::empty().with(Protocol::P2p(peer.into()))),
228				Some(peer)
229			);
230		}
231
232		{
233			assert!(PeerId::try_from_multiaddr(
234				&"/ip4/198.51.100.19/tcp/30333".parse::<Multiaddr>().unwrap()
235			)
236			.is_none());
237		}
238	}
239
240	#[test]
241	fn from_ed25519() {
242		let keypair = litep2p::crypto::ed25519::Keypair::generate();
243		let original_peer_id = litep2p::PeerId::from_public_key(
244			&litep2p::crypto::PublicKey::Ed25519(keypair.public()),
245		);
246
247		let peer_id: PeerId = original_peer_id.into();
248		assert_eq!(original_peer_id.to_bytes(), peer_id.to_bytes());
249
250		let key = peer_id.into_ed25519().unwrap();
251		assert_eq!(PeerId::from_ed25519(&key).unwrap(), original_peer_id.into());
252	}
253}