libp2p_core/
peer_record.rs

1use crate::signed_envelope::SignedEnvelope;
2use crate::{proto, signed_envelope, DecodeError, Multiaddr};
3use libp2p_identity::Keypair;
4use libp2p_identity::PeerId;
5use libp2p_identity::SigningError;
6use quick_protobuf::{BytesReader, Writer};
7use web_time::SystemTime;
8
9const PAYLOAD_TYPE: &str = "/libp2p/routing-state-record";
10const DOMAIN_SEP: &str = "libp2p-routing-state";
11
12/// Represents a peer routing record.
13///
14/// Peer records are designed to be distributable and carry a signature by being wrapped in a signed envelope.
15/// For more information see RFC0003 of the libp2p specifications: <https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md>
16#[derive(Debug, PartialEq, Eq, Clone)]
17pub struct PeerRecord {
18    peer_id: PeerId,
19    seq: u64,
20    addresses: Vec<Multiaddr>,
21
22    /// A signed envelope representing this [`PeerRecord`].
23    ///
24    /// If this [`PeerRecord`] was constructed from a [`SignedEnvelope`], this is the original instance.
25    envelope: SignedEnvelope,
26}
27
28impl PeerRecord {
29    /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`].
30    ///
31    /// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid signature and can hence be considered authenticated.
32    pub fn from_signed_envelope(envelope: SignedEnvelope) -> Result<Self, FromEnvelopeError> {
33        use quick_protobuf::MessageRead;
34
35        let (payload, signing_key) =
36            envelope.payload_and_signing_key(String::from(DOMAIN_SEP), PAYLOAD_TYPE.as_bytes())?;
37        let mut reader = BytesReader::from_bytes(payload);
38        let record = proto::PeerRecord::from_reader(&mut reader, payload).map_err(DecodeError)?;
39
40        let peer_id = PeerId::from_bytes(&record.peer_id)?;
41
42        if peer_id != signing_key.to_peer_id() {
43            return Err(FromEnvelopeError::MismatchedSignature);
44        }
45
46        let seq = record.seq;
47        let addresses = record
48            .addresses
49            .into_iter()
50            .map(|a| a.multiaddr.to_vec().try_into())
51            .collect::<Result<Vec<_>, _>>()?;
52
53        Ok(Self {
54            peer_id,
55            seq,
56            addresses,
57            envelope,
58        })
59    }
60
61    /// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key.
62    ///
63    /// This is the same key that is used for authenticating every libp2p connection of your application, i.e. what you use when setting up your [`crate::transport::Transport`].
64    pub fn new(key: &Keypair, addresses: Vec<Multiaddr>) -> Result<Self, SigningError> {
65        use quick_protobuf::MessageWrite;
66
67        let seq = SystemTime::now()
68            .duration_since(SystemTime::UNIX_EPOCH)
69            .expect("now() is never before UNIX_EPOCH")
70            .as_secs();
71        let peer_id = key.public().to_peer_id();
72
73        let payload = {
74            let record = proto::PeerRecord {
75                peer_id: peer_id.to_bytes(),
76                seq,
77                addresses: addresses
78                    .iter()
79                    .map(|m| proto::AddressInfo {
80                        multiaddr: m.to_vec(),
81                    })
82                    .collect(),
83            };
84
85            let mut buf = Vec::with_capacity(record.get_size());
86            let mut writer = Writer::new(&mut buf);
87            record
88                .write_message(&mut writer)
89                .expect("Encoding to succeed");
90
91            buf
92        };
93
94        let envelope = SignedEnvelope::new(
95            key,
96            String::from(DOMAIN_SEP),
97            PAYLOAD_TYPE.as_bytes().to_vec(),
98            payload,
99        )?;
100
101        Ok(Self {
102            peer_id,
103            seq,
104            addresses,
105            envelope,
106        })
107    }
108
109    pub fn to_signed_envelope(&self) -> SignedEnvelope {
110        self.envelope.clone()
111    }
112
113    pub fn into_signed_envelope(self) -> SignedEnvelope {
114        self.envelope
115    }
116
117    pub fn peer_id(&self) -> PeerId {
118        self.peer_id
119    }
120
121    pub fn seq(&self) -> u64 {
122        self.seq
123    }
124
125    pub fn addresses(&self) -> &[Multiaddr] {
126        self.addresses.as_slice()
127    }
128}
129
130#[derive(thiserror::Error, Debug)]
131pub enum FromEnvelopeError {
132    /// Failed to extract the payload from the envelope.
133    #[error("Failed to extract payload from envelope")]
134    BadPayload(#[from] signed_envelope::ReadPayloadError),
135    /// Failed to decode the provided bytes as a [`PeerRecord`].
136    #[error("Failed to decode bytes as PeerRecord")]
137    InvalidPeerRecord(#[from] DecodeError),
138    /// Failed to decode the peer ID.
139    #[error("Failed to decode bytes as PeerId")]
140    InvalidPeerId(#[from] libp2p_identity::ParseError),
141    /// The signer of the envelope is different than the peer id in the record.
142    #[error("The signer of the envelope is different than the peer id in the record")]
143    MismatchedSignature,
144    /// Failed to decode a multi-address.
145    #[error("Failed to decode bytes as MultiAddress")]
146    InvalidMultiaddr(#[from] multiaddr::Error),
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    const HOME: &str = "/ip4/127.0.0.1/tcp/1337";
154
155    #[test]
156    fn roundtrip_envelope() {
157        let key = Keypair::generate_ed25519();
158
159        let record = PeerRecord::new(&key, vec![HOME.parse().unwrap()]).unwrap();
160
161        let envelope = record.to_signed_envelope();
162        let reconstructed = PeerRecord::from_signed_envelope(envelope).unwrap();
163
164        assert_eq!(reconstructed, record)
165    }
166
167    #[test]
168    fn mismatched_signature() {
169        use quick_protobuf::MessageWrite;
170
171        let addr: Multiaddr = HOME.parse().unwrap();
172
173        let envelope = {
174            let identity_a = Keypair::generate_ed25519();
175            let identity_b = Keypair::generate_ed25519();
176
177            let payload = {
178                let record = proto::PeerRecord {
179                    peer_id: identity_a.public().to_peer_id().to_bytes(),
180                    seq: 0,
181                    addresses: vec![proto::AddressInfo {
182                        multiaddr: addr.to_vec(),
183                    }],
184                };
185
186                let mut buf = Vec::with_capacity(record.get_size());
187                let mut writer = Writer::new(&mut buf);
188                record
189                    .write_message(&mut writer)
190                    .expect("Encoding to succeed");
191
192                buf
193            };
194
195            SignedEnvelope::new(
196                &identity_b,
197                String::from(DOMAIN_SEP),
198                PAYLOAD_TYPE.as_bytes().to_vec(),
199                payload,
200            )
201            .unwrap()
202        };
203
204        assert!(matches!(
205            PeerRecord::from_signed_envelope(envelope),
206            Err(FromEnvelopeError::MismatchedSignature)
207        ));
208    }
209}