use crate::{
multiaddr::{Multiaddr, Protocol},
multihash::{Code, Error, Multihash},
};
use rand::Rng;
use std::{fmt, hash::Hash, str::FromStr};
const MAX_INLINE_KEY_LENGTH: usize = 42;
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct PeerId {
multihash: Multihash,
}
impl fmt::Debug for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("PeerId").field(&self.to_base58()).finish()
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.to_base58().fmt(f)
}
}
impl PeerId {
pub fn random() -> PeerId {
let peer = rand::thread_rng().gen::<[u8; 32]>();
PeerId {
multihash: Multihash::wrap(0x0, &peer).expect("The digest size is never too large"),
}
}
pub fn try_from_multiaddr(address: &Multiaddr) -> Option<PeerId> {
match address.iter().find(|protocol| std::matches!(protocol, Protocol::P2p(_))) {
Some(Protocol::P2p(multihash)) => Some(Self { multihash }),
_ => None,
}
}
pub fn from_multihash(multihash: Multihash) -> Result<PeerId, Multihash> {
match Code::try_from(multihash.code()) {
Ok(Code::Sha2_256) => Ok(PeerId { multihash }),
Ok(Code::Identity) if multihash.digest().len() <= MAX_INLINE_KEY_LENGTH =>
Ok(PeerId { multihash }),
_ => Err(multihash),
}
}
pub fn from_bytes(data: &[u8]) -> Result<PeerId, Error> {
PeerId::from_multihash(Multihash::from_bytes(data)?)
.map_err(|mh| Error::UnsupportedCode(mh.code()))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.multihash.to_bytes()
}
pub fn to_base58(&self) -> String {
bs58::encode(self.to_bytes()).into_string()
}
pub fn into_ed25519(&self) -> Option<[u8; 32]> {
let hash = &self.multihash;
if hash.code() != 0 {
return None
}
let public = libp2p_identity::PublicKey::try_decode_protobuf(hash.digest()).ok()?;
public.try_into_ed25519().ok().map(|public| public.to_bytes())
}
pub fn from_ed25519(bytes: &[u8; 32]) -> Option<PeerId> {
let public = libp2p_identity::ed25519::PublicKey::try_from_bytes(bytes).ok()?;
let public: libp2p_identity::PublicKey = public.into();
let peer_id: libp2p_identity::PeerId = public.into();
Some(peer_id.into())
}
}
impl AsRef<Multihash> for PeerId {
fn as_ref(&self) -> &Multihash {
&self.multihash
}
}
impl From<PeerId> for Multihash {
fn from(peer_id: PeerId) -> Self {
peer_id.multihash
}
}
impl From<libp2p_identity::PeerId> for PeerId {
fn from(peer_id: libp2p_identity::PeerId) -> Self {
PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
}
}
impl From<PeerId> for libp2p_identity::PeerId {
fn from(peer_id: PeerId) -> Self {
libp2p_identity::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
}
}
impl From<&libp2p_identity::PeerId> for PeerId {
fn from(peer_id: &libp2p_identity::PeerId) -> Self {
PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
}
}
impl From<&PeerId> for libp2p_identity::PeerId {
fn from(peer_id: &PeerId) -> Self {
libp2p_identity::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
}
}
impl From<litep2p::PeerId> for PeerId {
fn from(peer_id: litep2p::PeerId) -> Self {
PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
}
}
impl From<PeerId> for litep2p::PeerId {
fn from(peer_id: PeerId) -> Self {
litep2p::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
}
}
impl From<&litep2p::PeerId> for PeerId {
fn from(peer_id: &litep2p::PeerId) -> Self {
PeerId { multihash: Multihash::from_bytes(&peer_id.to_bytes()).expect("to succeed") }
}
}
impl From<&PeerId> for litep2p::PeerId {
fn from(peer_id: &PeerId) -> Self {
litep2p::PeerId::from_bytes(&peer_id.to_bytes()).expect("to succeed")
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParseError {
#[error("base-58 decode error: {0}")]
B58(#[from] bs58::decode::Error),
#[error("unsupported multihash code '{0}'")]
UnsupportedCode(u64),
#[error("invalid multihash")]
InvalidMultihash(#[from] crate::multihash::Error),
}
impl FromStr for PeerId {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = bs58::decode(s).into_vec()?;
let peer_id = PeerId::from_bytes(&bytes)?;
Ok(peer_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn extract_peer_id_from_multiaddr() {
{
let peer = PeerId::random();
let address = "/ip4/198.51.100.19/tcp/30333"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(peer.into()));
assert_eq!(PeerId::try_from_multiaddr(&address), Some(peer));
}
{
let peer = PeerId::random();
assert_eq!(
PeerId::try_from_multiaddr(&Multiaddr::empty().with(Protocol::P2p(peer.into()))),
Some(peer)
);
}
{
assert!(PeerId::try_from_multiaddr(
&"/ip4/198.51.100.19/tcp/30333".parse::<Multiaddr>().unwrap()
)
.is_none());
}
}
#[test]
fn from_ed25519() {
let keypair = litep2p::crypto::ed25519::Keypair::generate();
let original_peer_id = litep2p::PeerId::from_public_key(
&litep2p::crypto::PublicKey::Ed25519(keypair.public()),
);
let peer_id: PeerId = original_peer_id.into();
assert_eq!(original_peer_id.to_bytes(), peer_id.to_bytes());
let key = peer_id.into_ed25519().unwrap();
assert_eq!(PeerId::from_ed25519(&key).unwrap(), original_peer_id.into());
}
}