Skip to content

Conversation

@r-zig
Copy link
Contributor

@r-zig r-zig commented Nov 14, 2025

Description

Description

Fixes #6204

Changes PeerRecord to use the standard libp2p-peer-record constants instead of routing-state-record values, ensuring interoperability with Go and JavaScript implementations.

Changes

  • 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:

This matches the implementations in:

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

Add cross-implementation interoperability tests that verify Rust can
decode and verify signed peer records created by Go and JavaScript
libp2p implementations.

Test fixtures:
- JavaScript fixture generated using @libp2p/peer-record
- Go fixture generated using go-libp2p/core/peer
- Both use standard domain 'libp2p-peer-record' and payload type [3, 1]

Current behavior:
- Tests FAIL with UnexpectedPayloadType error
- Rust expects '/libp2p/routing-state-record' [47, 108, ...]
- JS/Go provide standard payload type [3, 1]

This demonstrates the incompatibility bug that will be fixed in the
next commit.
Changes PeerRecord to use the standard libp2p-peer-record constants
instead of routing-state-record values, ensuring interoperability with
Go and JavaScript implementations.

Changes:
- 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 the multicodec table:
https://github.com/multiformats/multicodec/blob/master/table.csv

This matches the implementations in:
- Go: https://github.com/libp2p/go-libp2p/blob/master/core/peer/record.go#L21-L29
- JS: https://github.com/libp2p/js-libp2p/blob/main/packages/peer-record/src/peer-record/consts.ts#L1-L7

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 fixes interoperability with Go/JS libp2p.

Fixes cross-implementation compatibility issue where Rust could not
verify signed peer records from Go/JS implementations.
Documents the breaking fix that aligns PeerRecord constants with Go/JS
implementations, changing domain to 'libp2p-peer-record' and payload
type to [0x03, 0x01] for cross-implementation compatibility.

Pull-Request: libp2p#6205.
Bumps libp2p-core from 0.43.1 to 0.44.0 due to breaking change in
PeerRecord domain and payload type constants.

This is a minor version bump (not patch) because the change breaks
compatibility with existing Rust-created peer records.

The breaking change:
- PAYLOAD_TYPE: '/libp2p/routing-state-record' -> [0x03, 0x01]
- DOMAIN_SEP: 'libp2p-routing-state' -> 'libp2p-peer-record'

Existing signed peer records created with the old constants will not
be verifiable after this change, but the new constants align with
Go/JS implementations and fix cross-implementation interoperability.

See also:
- PR libp2p#6205: The fix implementation
- Issue libp2p#6204: Original bug report"
Copy link
Member

@jxs jxs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Ron, and thanks for this!
This does not seem like a bug, as Thomas when implementing followed the RFC:

Domain strings may be any valid UTF-8 string, but should be fairly short and descriptive of their use case, for example "libp2p-routing-record".

to avoid breaking changes I'd suggest we create a new method like from_signed_envelope_with_domain wdyt? I also don't think we need the interop files
CC @elenaf9

@r-zig
Copy link
Contributor Author

r-zig commented Nov 15, 2025

Hi Ron, and thanks for this! This does not seem like a bug, as Thomas when implementing followed the RFC:

Domain strings may be any valid UTF-8 string, but should be fairly short and descriptive of their use case, for example "libp2p-routing-record".

to avoid breaking changes I'd suggest we create a new method like from_signed_envelope_with_domain wdyt? I also don't think we need the interop files CC @elenaf9

The issue here is not only about the domain. Even if we allow the caller to provide a domain string, it’s still not enough — because the failure occurs earlier, due to an invalid payload type (BadPayload(UnexpectedPayloadType)).
Both domain and payload type (multicodec) must match across implementations in order for signed PeerRecord envelopes to be exchanged successfully between libp2p implementations.

Right now, the Rust implementation signs a peer record using defaults that do not match the values used in Go, JS, and Python. Those implementations validate against a specific domain and payload type. For example:
JS:
declaration here:
https://github.com/libp2p/js-libp2p/blob/main/packages/peer-record/src/peer-record/consts.ts
https://github.com/libp2p/js-libp2p/blob/851395edf7a5ffdef39a90ca5417a5db0da30520/packages/peer-record/src/peer-record/consts.ts
-->
https://github.com/libp2p/js-libp2p/blob/851395edf7a5ffdef39a90ca5417a5db0da30520/packages/peer-record/src/envelope/index.ts#L42-L46
use here:
https://github.com/libp2p/js-libp2p/blob/851395edf7a5ffdef39a90ca5417a5db0da30520/packages/peer-record/src/peer-record/index.ts#L45-L46
--> https://github.com/libp2p/js-libp2p/blob/851395edf7a5ffdef39a90ca5417a5db0da30520/packages/peer-record/src/peer-record/index.ts#L51-L52
-->

Go:
https://github.com/libp2p/go-libp2p/blob/aefe38980c395efb1957195e7e57ca5b2e1437a5/core/peer/record.go#L24-L29
https://github.com/libp2p/go-libp2p/blob/aefe38980c395efb1957195e7e57ca5b2e1437a5/core/peer/record.go#L77
https://github.com/libp2p/go-libp2p/blob/aefe38980c395efb1957195e7e57ca5b2e1437a5/core/record/envelope.go#L57-L65

Py:
https://github.com/libp2p/py-libp2p/blob/c0dedaae41a8a6da73a7a4d34484a452dd4b08f4/libp2p/peer/envelope.py#L19-L20
https://github.com/libp2p/py-libp2p/blob/c0dedaae41a8a6da73a7a4d34484a452dd4b08f4/libp2p/peer/envelope.py#L104-L105

Because of this, leaving the current Rust function as-is will produce signatures that will not interoperate with other libp2p stacks. It will sign a PeerRecord — but one that other languages will reject.

So at minimum, we should either:

Remove the existing no-arg function and introduce a new function that requires both domain and payload type explicitly,
or
Keep the current fix, ensuring we match the domain and multicodec used by the other implementations.
But in both cases it will be a breaking change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PeerRecord incompatible with Go/JS implementations due to wrong domain/payload type

2 participants