cometbft_p2p/
peer_id.rs

1//! Secret Connection peer IDs.
2
3use crate::{Error, VerifyPeerError};
4use base16ct::mixed as hex;
5use prost::DecodeError;
6use std::{
7    fmt::{self, Debug, Display},
8    str::FromStr,
9};
10use subtle::{Choice, ConstantTimeEq};
11
12/// Secret Connection peer IDs (i.e. key fingerprints)
13// TODO(tarcieri): use `cometbft::node::Id`
14#[allow(clippy::derived_hash_with_manual_eq)]
15#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
16pub struct PeerId(pub [u8; Self::LENGTH]);
17
18impl PeerId {
19    /// Length of a Node ID in bytes
20    pub const LENGTH: usize = 20;
21
22    /// Create a new Node ID from raw bytes
23    pub fn new(bytes: [u8; Self::LENGTH]) -> Self {
24        Self(bytes)
25    }
26
27    /// Borrow the node ID as a byte slice
28    pub fn as_bytes(&self) -> &[u8; Self::LENGTH] {
29        &self.0
30    }
31
32    /// Get an owned ID containing the peer ID.
33    pub fn to_bytes(self) -> [u8; Self::LENGTH] {
34        self.0
35    }
36
37    /// Verify this [`PeerId`] matches another one, returning [`Error::VerifyPeer`] in the event
38    /// there is a mismatch.
39    pub fn verify(self, expected_peer_id: PeerId) -> Result<(), VerifyPeerError> {
40        if bool::from(self.ct_eq(&expected_peer_id)) {
41            Ok(())
42        } else {
43            Err(VerifyPeerError {
44                expected_peer_id,
45                actual_peer_id: self,
46            })
47        }
48    }
49}
50
51impl AsRef<[u8]> for PeerId {
52    fn as_ref(&self) -> &[u8] {
53        self.as_bytes()
54    }
55}
56
57impl ConstantTimeEq for PeerId {
58    fn ct_eq(&self, other: &Self) -> Choice {
59        self.0.ct_eq(&other.0)
60    }
61}
62
63impl Display for PeerId {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        for byte in &self.0 {
66            write!(f, "{byte:02x}")?;
67        }
68        Ok(())
69    }
70}
71
72impl Debug for PeerId {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "node::Id({self})")
75    }
76}
77
78impl From<[u8; PeerId::LENGTH]> for PeerId {
79    fn from(bytes: [u8; PeerId::LENGTH]) -> PeerId {
80        PeerId(bytes)
81    }
82}
83
84impl From<PeerId> for [u8; PeerId::LENGTH] {
85    fn from(peer_id: PeerId) -> Self {
86        peer_id.0
87    }
88}
89
90impl From<&PeerId> for [u8; PeerId::LENGTH] {
91    fn from(peer_id: &PeerId) -> Self {
92        peer_id.0
93    }
94}
95
96/// Decode Node ID from hex
97impl FromStr for PeerId {
98    type Err = Error;
99
100    fn from_str(s: &str) -> Result<Self, Error> {
101        // Accept either upper or lower case hex
102        let bytes = hex::decode_vec(s).map_err(|_| DecodeError::new("hex decoding error"))?;
103        bytes
104            .try_into()
105            .map(Self)
106            .map_err(|_| DecodeError::new("invalid peer ID length").into())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::PeerId;
113    use crate::VerifyPeerError;
114
115    const PEER_ID_1: PeerId = PeerId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
116    const PEER_ID_2: PeerId = PeerId([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
117
118    #[test]
119    fn verify_peer() {
120        assert!(PEER_ID_1.verify(PEER_ID_1).is_ok());
121
122        let err = PEER_ID_1.verify(PEER_ID_2).unwrap_err();
123        assert_eq!(
124            err,
125            VerifyPeerError {
126                expected_peer_id: PEER_ID_2,
127                actual_peer_id: PEER_ID_1
128            }
129        )
130    }
131}