libp2p_core/
peer_record.rs

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