@@ -4,13 +4,30 @@ use web_time::SystemTime;
44
55use crate :: { proto, signed_envelope, signed_envelope:: SignedEnvelope , DecodeError , Multiaddr } ;
66
7- const PAYLOAD_TYPE : & str = "/libp2p/routing-state-record" ;
8- const DOMAIN_SEP : & str = "libp2p-routing-state" ;
7+ // Legacy constants for backward compatibility with existing Rust libp2p deployments.
8+ const LEGACY_PAYLOAD_TYPE : & str = "/libp2p/routing-state-record" ;
9+ const LEGACY_DOMAIN_SEP : & str = "libp2p-routing-state" ;
10+
11+ // Standard constants for cross-implementation compatibility with Go/JS libp2p.
12+ // Defined in https://github.com/multiformats/multicodec/blob/master/table.csv
13+ // and https://github.com/libp2p/specs/blob/master/RFC/0002-signed-envelopes.md.
14+ const STANDARD_PAYLOAD_TYPE : & [ u8 ] = & [ 0x03 , 0x01 ] ;
15+ const STANDARD_DOMAIN_SEP : & str = "libp2p-peer-record" ;
916
1017/// Represents a peer routing record.
1118///
1219/// Peer records are designed to be distributable and carry a signature by being wrapped in a signed
1320/// envelope. For more information see RFC0003 of the libp2p specifications: <https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md>
21+ ///
22+ /// ## Cross-Implementation Compatibility
23+ ///
24+ /// This implementation provides two formats:
25+ /// - **Legacy format** (default methods): Compatible with existing Rust libp2p deployments.
26+ /// - **Standard format** (`*_interop` methods): Compatible with Go and JavaScript implementations.
27+ ///
28+ /// Use the `*_interop` variants (e.g., [`PeerRecord::new_interop`],
29+ /// [`PeerRecord::from_signed_envelope_interop`]) when you need to exchange peer records with
30+ /// non-Rust libp2p implementations.
1431#[ derive( Debug , PartialEq , Eq , Clone ) ]
1532pub struct PeerRecord {
1633 peer_id : PeerId ,
@@ -25,15 +42,43 @@ pub struct PeerRecord {
2542}
2643
2744impl PeerRecord {
28- /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`].
45+ /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`] using legacy format.
46+ ///
47+ /// Uses the legacy routing-state-record format for backward compatibility with existing
48+ /// Rust libp2p deployments.
2949 ///
3050 /// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid
3151 /// signature and can hence be considered authenticated.
52+ ///
53+ /// For cross-implementation compatibility with Go/JS libp2p, use
54+ /// [`Self::from_signed_envelope_interop`].
3255 pub fn from_signed_envelope ( envelope : SignedEnvelope ) -> Result < Self , FromEnvelopeError > {
56+ Self :: from_signed_envelope_impl ( envelope, LEGACY_DOMAIN_SEP , LEGACY_PAYLOAD_TYPE . as_bytes ( ) )
57+ }
58+
59+ /// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`] using standard interop
60+ /// format.
61+ ///
62+ /// Uses the standard libp2p-peer-record format for cross-implementation compatibility
63+ /// with Go and JavaScript libp2p implementations.
64+ ///
65+ /// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid
66+ /// signature and can hence be considered authenticated.
67+ pub fn from_signed_envelope_interop (
68+ envelope : SignedEnvelope ,
69+ ) -> Result < Self , FromEnvelopeError > {
70+ Self :: from_signed_envelope_impl ( envelope, STANDARD_DOMAIN_SEP , STANDARD_PAYLOAD_TYPE )
71+ }
72+
73+ fn from_signed_envelope_impl (
74+ envelope : SignedEnvelope ,
75+ domain : & str ,
76+ payload_type : & [ u8 ] ,
77+ ) -> Result < Self , FromEnvelopeError > {
3378 use quick_protobuf:: MessageRead ;
3479
3580 let ( payload, signing_key) =
36- envelope. payload_and_signing_key ( String :: from ( DOMAIN_SEP ) , PAYLOAD_TYPE . as_bytes ( ) ) ?;
81+ envelope. payload_and_signing_key ( String :: from ( domain ) , payload_type ) ?;
3782 let mut reader = BytesReader :: from_bytes ( payload) ;
3883 let record = proto:: PeerRecord :: from_reader ( & mut reader, payload) . map_err ( DecodeError ) ?;
3984
@@ -58,11 +103,43 @@ impl PeerRecord {
58103 } )
59104 }
60105
61- /// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key.
106+ /// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key
107+ /// using legacy format.
108+ ///
109+ /// Uses the legacy routing-state-record format for backward compatibility with existing
110+ /// Rust libp2p deployments.
62111 ///
63112 /// This is the same key that is used for authenticating every libp2p connection of your
64113 /// application, i.e. what you use when setting up your [`crate::transport::Transport`].
114+ ///
115+ /// For cross-implementation compatibility with Go/JS libp2p, use [`Self::new_interop`].
65116 pub fn new ( key : & Keypair , addresses : Vec < Multiaddr > ) -> Result < Self , SigningError > {
117+ Self :: new_impl (
118+ key,
119+ addresses,
120+ LEGACY_DOMAIN_SEP ,
121+ LEGACY_PAYLOAD_TYPE . as_bytes ( ) ,
122+ )
123+ }
124+
125+ /// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key
126+ /// using standard interop format.
127+ ///
128+ /// Uses the standard libp2p-peer-record format for cross-implementation compatibility
129+ /// with Go and JavaScript libp2p implementations.
130+ ///
131+ /// This is the same key that is used for authenticating every libp2p connection of your
132+ /// application, i.e. what you use when setting up your [`crate::transport::Transport`].
133+ pub fn new_interop ( key : & Keypair , addresses : Vec < Multiaddr > ) -> Result < Self , SigningError > {
134+ Self :: new_impl ( key, addresses, STANDARD_DOMAIN_SEP , STANDARD_PAYLOAD_TYPE )
135+ }
136+
137+ fn new_impl (
138+ key : & Keypair ,
139+ addresses : Vec < Multiaddr > ,
140+ domain : & str ,
141+ payload_type : & [ u8 ] ,
142+ ) -> Result < Self , SigningError > {
66143 use quick_protobuf:: MessageWrite ;
67144
68145 let seq = SystemTime :: now ( )
@@ -92,12 +169,8 @@ impl PeerRecord {
92169 buf
93170 } ;
94171
95- let envelope = SignedEnvelope :: new (
96- key,
97- String :: from ( DOMAIN_SEP ) ,
98- PAYLOAD_TYPE . as_bytes ( ) . to_vec ( ) ,
99- payload,
100- ) ?;
172+ let envelope =
173+ SignedEnvelope :: new ( key, String :: from ( domain) , payload_type. to_vec ( ) , payload) ?;
101174
102175 Ok ( Self {
103176 peer_id,
@@ -154,7 +227,7 @@ mod tests {
154227 const HOME : & str = "/ip4/127.0.0.1/tcp/1337" ;
155228
156229 #[ test]
157- fn roundtrip_envelope ( ) {
230+ fn roundtrip_envelope_legacy ( ) {
158231 let key = Keypair :: generate_ed25519 ( ) ;
159232
160233 let record = PeerRecord :: new ( & key, vec ! [ HOME . parse( ) . unwrap( ) ] ) . unwrap ( ) ;
@@ -166,7 +239,19 @@ mod tests {
166239 }
167240
168241 #[ test]
169- fn mismatched_signature ( ) {
242+ fn roundtrip_envelope_interop ( ) {
243+ let key = Keypair :: generate_ed25519 ( ) ;
244+
245+ let record = PeerRecord :: new_interop ( & key, vec ! [ HOME . parse( ) . unwrap( ) ] ) . unwrap ( ) ;
246+
247+ let envelope = record. to_signed_envelope ( ) ;
248+ let reconstructed = PeerRecord :: from_signed_envelope_interop ( envelope) . unwrap ( ) ;
249+
250+ assert_eq ! ( reconstructed, record)
251+ }
252+
253+ #[ test]
254+ fn mismatched_signature_legacy ( ) {
170255 use quick_protobuf:: MessageWrite ;
171256
172257 let addr: Multiaddr = HOME . parse ( ) . unwrap ( ) ;
@@ -195,8 +280,8 @@ mod tests {
195280
196281 SignedEnvelope :: new (
197282 & identity_b,
198- String :: from ( DOMAIN_SEP ) ,
199- PAYLOAD_TYPE . as_bytes ( ) . to_vec ( ) ,
283+ String :: from ( LEGACY_DOMAIN_SEP ) ,
284+ LEGACY_PAYLOAD_TYPE . as_bytes ( ) . to_vec ( ) ,
200285 payload,
201286 )
202287 . unwrap ( )
@@ -207,4 +292,47 @@ mod tests {
207292 Err ( FromEnvelopeError :: MismatchedSignature )
208293 ) ) ;
209294 }
295+
296+ #[ test]
297+ fn mismatched_signature_interop ( ) {
298+ use quick_protobuf:: MessageWrite ;
299+
300+ let addr: Multiaddr = HOME . parse ( ) . unwrap ( ) ;
301+
302+ let envelope = {
303+ let identity_a = Keypair :: generate_ed25519 ( ) ;
304+ let identity_b = Keypair :: generate_ed25519 ( ) ;
305+
306+ let payload = {
307+ let record = proto:: PeerRecord {
308+ peer_id : identity_a. public ( ) . to_peer_id ( ) . to_bytes ( ) ,
309+ seq : 0 ,
310+ addresses : vec ! [ proto:: AddressInfo {
311+ multiaddr: addr. to_vec( ) ,
312+ } ] ,
313+ } ;
314+
315+ let mut buf = Vec :: with_capacity ( record. get_size ( ) ) ;
316+ let mut writer = Writer :: new ( & mut buf) ;
317+ record
318+ . write_message ( & mut writer)
319+ . expect ( "Encoding to succeed" ) ;
320+
321+ buf
322+ } ;
323+
324+ SignedEnvelope :: new (
325+ & identity_b,
326+ String :: from ( STANDARD_DOMAIN_SEP ) ,
327+ STANDARD_PAYLOAD_TYPE . to_vec ( ) ,
328+ payload,
329+ )
330+ . unwrap ( )
331+ } ;
332+
333+ assert ! ( matches!(
334+ PeerRecord :: from_signed_envelope_interop( envelope) ,
335+ Err ( FromEnvelopeError :: MismatchedSignature )
336+ ) ) ;
337+ }
210338}
0 commit comments