Skip to content

Commit e10ce4c

Browse files
authored
fix: use standard peer-record domain and payload type
Fixes #6204 PAYLOAD_TYPE: "/libp2p/routing-state-record" → [0x03, 0x01] DOMAIN_SEP: "libp2p-routing-state" → "libp2p-peer-record" The payload type [0x03, 0x01] is the multicodec identifier for libp2p-peer-record, as defined in: [Multicodec table](https://github.com/multiformats/multicodec/blob/master/table.csv) This matches the implementations in: [Go libp2p](https://github.com/libp2p/go-libp2p/blob/master/core/peer/record.go#L21-L24) [JS libp2p](https://github.com/libp2p/js-libp2p/blob/main/packages/peer-record/src/peer-record/consts.ts#L7) PR Recreation Notice This PR is a recreation of [#6205], which was automatically closed after I mistakenly deleted my fork. It restores the exact same code and commits as before. No additional changes were made. Follow-up plan (based on discussion in the original PR) During the previous review, the suggested direction was: Introduce this change behind a new Cargo feature, which will be false by default. Deprecate the current implementation, allowing users to switch gradually. Give the ecosystem time to migrate, before potentially making the new behavior the default in the future. Why I’m reopening now This PR is being reopened and then I added the new optional `peer-record-interop` feature flag. Testing Added cross-implementation interoperability tests: core/tests/peer_record_interop.rs - Tests that verify Rust can decode and verify signed peer records from Go and JS core/tests/fixtures/ - Test fixtures with signed peer records generated by Go and JS implementations Before the fix, these tests failed with: BadPayload(UnexpectedPayloadType { expected: [47, 108, ...], got: [3, 1] }) After the fix, all tests pass. Breaking Change ⚠️ BREAKING CHANGE: Existing signed peer records created with Rust libp2p will not be verifiable with this change, as the domain and payload type have changed. However, this is necessary to fix interoperability with Go/JS libp2p implementations. Notes & open questions None - this is a straightforward alignment with the standard used by other libp2p implementations. Change checklist [x ] I have performed a self-review of my own code I have made corresponding changes to the documentation I have added tests that prove my fix is effective or that my feature works [x ] A changelog entry has been made in the appropriate crates Pull-Request: #6230.
1 parent 646fd16 commit e10ce4c

File tree

5 files changed

+159
-18
lines changed

5 files changed

+159
-18
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ libp2p = { version = "0.56.1", path = "libp2p" }
7878
libp2p-allow-block-list = { version = "0.6.0", path = "misc/allow-block-list" }
7979
libp2p-autonat = { version = "0.15.0", path = "protocols/autonat" }
8080
libp2p-connection-limits = { version = "0.6.0", path = "misc/connection-limits" }
81-
libp2p-core = { version = "0.43.1", path = "core" }
81+
libp2p-core = { version = "0.43.2", path = "core" }
8282
libp2p-dcutr = { version = "0.14.0", path = "protocols/dcutr" }
8383
libp2p-dns = { version = "0.44.0", path = "transports/dns" }
8484
libp2p-floodsub = { version = "0.47.0", path = "protocols/floodsub" }

core/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 0.43.2
2+
3+
- Add `*_interop` methods to `PeerRecord` for cross-implementation compatibility with Go and JavaScript libp2p.
4+
- `PeerRecord::new_interop()` - Create peer records using standard format
5+
- `PeerRecord::from_signed_envelope_interop()` - Verify peer records using standard format
6+
7+
The standard format uses libp2p-peer-record domain and multicodec identifier (0x0301) for interoperability.
8+
Existing methods (`new()`, `from_signed_envelope()`) maintain backward compatibility with legacy Rust libp2p format.
9+
10+
Use the `*_interop` variants when exchanging peer records with non-Rust libp2p implementations.
11+
12+
See [PR 6230](https://github.com/libp2p/rust-libp2p/pull/6230).
13+
114
## 0.43.1
215
- Remove `once_cell` dependency.
316
See [PR 5913](https://github.com/libp2p/rust-libp2p/pull/5913)

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "libp2p-core"
33
edition.workspace = true
44
rust-version = { workspace = true }
55
description = "Core traits and structs of libp2p"
6-
version = "0.43.1"
6+
version = "0.43.2"
77
authors = ["Parity Technologies <[email protected]>"]
88
license = "MIT"
99
repository = "https://github.com/libp2p/rust-libp2p"

core/src/peer_record.rs

Lines changed: 143 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,30 @@ use web_time::SystemTime;
44

55
use 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)]
1532
pub struct PeerRecord {
1633
peer_id: PeerId,
@@ -25,15 +42,43 @@ pub struct PeerRecord {
2542
}
2643

2744
impl 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

Comments
 (0)