Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
2a09cdb
implement signedPeerRecord
drHuangMHT Dec 31, 2024
b5d7aec
apply suggestions
drHuangMHT Jan 1, 2025
85d7496
Merge branch 'master' into identify-peer-record
drHuangMHT Jan 3, 2025
4d3d692
add test
drHuangMHT Jan 3, 2025
59f80f2
reduce diff
drHuangMHT Jan 4, 2025
ef89b5b
Merge branch 'master' into identify-peer-record
drHuangMHT Jan 4, 2025
12b9a29
rename symbols
drHuangMHT Jan 10, 2025
d3d8ae6
prefer addresses in signedPeerRecord
drHuangMHT Jan 10, 2025
59151b6
rename symbols
drHuangMHT Jan 11, 2025
9b6a139
lint and fmt
drHuangMHT Jan 11, 2025
cf30682
Merge branch 'master' into identify-peer-record
drHuangMHT Feb 5, 2025
e0e4d9c
rename identifiers
drHuangMHT Feb 13, 2025
d26a692
Merge branch 'master' into identify-peer-record
drHuangMHT Feb 13, 2025
92a49a5
reduce enum variant size with Box
drHuangMHT Feb 16, 2025
b35cbd3
simplify envelope deserialization
drHuangMHT Feb 16, 2025
f29c58d
document try_deserialize_signed_envelope
drHuangMHT Feb 16, 2025
ec8f31b
CI lint
drHuangMHT Feb 16, 2025
c73d37a
dedup constructor, reference count KeyType
drHuangMHT Feb 20, 2025
7cbb3f2
doc and fmt lint
drHuangMHT Feb 21, 2025
c013293
opaque type on new_with_key
drHuangMHT Feb 21, 2025
54aed27
changelog and manifest
drHuangMHT Feb 21, 2025
cd70cb9
workspace manifest
drHuangMHT Feb 21, 2025
8df2279
Merge branch 'master' into identify-peer-record
drHuangMHT Feb 21, 2025
9ec20a4
Merge branch 'master' into identify-peer-record
drHuangMHT Feb 25, 2025
c5db873
fix definition link, simplify record parsing
drHuangMHT Feb 28, 2025
b882ad5
revert core version bump
drHuangMHT Feb 28, 2025
0d27658
Merge branch 'master' into identify-peer-record
drHuangMHT Feb 28, 2025
ec67ce6
Merge branch 'master' into identify-peer-record
mergify[bot] Mar 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.43.1

- Add `libp2p::core::peer_record::PeerRecord::try_deserialize_signed_peer_record`
utility method for deserializing `SignedEnvelope` without creating an instance of
`PeerRecord`.
See [PR 5785](https://github.com/libp2p/rust-libp2p/pull/5785)

## 0.43.0

- Added `libp2p::core::util::unreachable` that is a drop-in replacement of `void::unreachable`.
Expand Down
58 changes: 35 additions & 23 deletions core/src/peer_record.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use libp2p_identity::{Keypair, PeerId, SigningError};
use libp2p_identity::{Keypair, PeerId, PublicKey, SigningError};
use quick_protobuf::{BytesReader, Writer};
use web_time::SystemTime;

use crate::{proto, signed_envelope, signed_envelope::SignedEnvelope, DecodeError, Multiaddr};

const PAYLOAD_TYPE: &str = "/libp2p/routing-state-record";
const DOMAIN_SEP: &str = "libp2p-routing-state";
pub const PAYLOAD_TYPE: &str = "/libp2p/routing-state-record";
pub const DOMAIN_SEP: &str = "libp2p-routing-state";

/// Represents a peer routing record.
///
Expand All @@ -30,26 +30,7 @@ impl PeerRecord {
/// If this function succeeds, the [`SignedEnvelope`] contained a peer record with a valid
/// signature and can hence be considered authenticated.
pub fn from_signed_envelope(envelope: SignedEnvelope) -> Result<Self, FromEnvelopeError> {
use quick_protobuf::MessageRead;

let (payload, signing_key) =
envelope.payload_and_signing_key(String::from(DOMAIN_SEP), PAYLOAD_TYPE.as_bytes())?;
let mut reader = BytesReader::from_bytes(payload);
let record = proto::PeerRecord::from_reader(&mut reader, payload).map_err(DecodeError)?;

let peer_id = PeerId::from_bytes(&record.peer_id)?;

if peer_id != signing_key.to_peer_id() {
return Err(FromEnvelopeError::MismatchedSignature);
}

let seq = record.seq;
let addresses = record
.addresses
.into_iter()
.map(|a| a.multiaddr.to_vec().try_into())
.collect::<Result<Vec<_>, _>>()?;

let (_, peer_id, seq, addresses) = Self::try_deserialize_signed_envelope(&envelope)?;
Comment thread
drHuangMHT marked this conversation as resolved.
Outdated
Ok(Self {
peer_id,
seq,
Expand Down Expand Up @@ -126,6 +107,37 @@ impl PeerRecord {
pub fn addresses(&self) -> &[Multiaddr] {
self.addresses.as_slice()
}

/// Utility method for deserializing an [`SignedEnvelope`] using
/// [`PeerRecord`]-specific [domain separation](https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md#signed-envelope-domain)
/// and [payload type](https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md#signed-envelope-payload-type).
/// Useful for extracting the address only.
/// Returns `Ok((envelope_public_key, envelope_signer_id, sequence_number, signed_addresses))`
/// when the envelope is valid.
/// Will fail when the source of the addresses doesn't match signer of the envelope.
pub fn try_deserialize_signed_envelope(
envelope: &SignedEnvelope,
) -> Result<(&PublicKey, PeerId, u64, Vec<Multiaddr>), FromEnvelopeError> {
use quick_protobuf::MessageRead;

let (payload, signing_key) =
envelope.payload_and_signing_key(String::from(DOMAIN_SEP), PAYLOAD_TYPE.as_bytes())?;
let mut reader = BytesReader::from_bytes(payload);
let record = proto::PeerRecord::from_reader(&mut reader, payload).map_err(DecodeError)?;

let peer_id = PeerId::from_bytes(&record.peer_id)?;

if peer_id != signing_key.to_peer_id() {
return Err(FromEnvelopeError::MismatchedSignature);
}

let addresses = record
.addresses
.into_iter()
.map(|a| a.multiaddr.to_vec().try_into())
.collect::<Result<Vec<_>, _>>()?;
Ok((signing_key, peer_id, record.seq, addresses))
}
}

#[derive(thiserror::Error, Debug)]
Expand Down
5 changes: 5 additions & 0 deletions protocols/identify/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 0.46.1

- Implement optional `signedPeerRecord` support for identify messages.
See [PR 5785](https://github.com/libp2p/rust-libp2p/pull/5785)

## 0.46.0

- Add `hide_listen_addrs` option to prevent leaking (local) listen addresses.
Expand Down
73 changes: 64 additions & 9 deletions protocols/identify/src/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ use std::{
};

use libp2p_core::{
multiaddr, multiaddr::Protocol, transport::PortUse, ConnectedPoint, Endpoint, Multiaddr,
multiaddr::{self, Protocol},
transport::PortUse,
ConnectedPoint, Endpoint, Multiaddr,
};
use libp2p_identity::{PeerId, PublicKey};
use libp2p_identity::{Keypair, PeerId, PublicKey};
use libp2p_swarm::{
behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm},
ConnectionDenied, ConnectionId, DialError, ExternalAddresses, ListenAddresses,
Expand Down Expand Up @@ -117,8 +119,10 @@ pub struct Config {
/// Application-specific version of the protocol family used by the peer,
/// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`.
protocol_version: String,
/// The public key of the local node. To report on the wire.
local_public_key: PublicKey,
/// The key of the local node. Only the public key will be report on the wire.
/// The behaviour will not produce [`PeerRecord`](libp2p_core::PeerRecord) when
/// supplied with a public key.
local_key: KeyType,
/// Name and version of the local peer implementation, similar to the
/// `User-Agent` header in the HTTP protocol.
///
Expand Down Expand Up @@ -156,12 +160,30 @@ pub struct Config {

impl Config {
/// Creates a new configuration for the identify [`Behaviour`] that
/// advertises the given protocol version and public key.
/// advertises the given protocol version and public key.
/// Use [`new_with_signed_peer_record`](Config::new_with_signed_peer_record) for
/// `signedPeerRecord` support.
pub fn new(protocol_version: String, local_public_key: PublicKey) -> Self {
Self {
protocol_version,
agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")),
local_public_key,
local_key: local_public_key.into(),
interval: Duration::from_secs(5 * 60),
push_listen_addr_updates: false,
cache_size: 100,
hide_listen_addrs: false,
}
Comment thread
dariusc93 marked this conversation as resolved.
Outdated
}

/// Creates a new configuration for the identify [`Behaviour`] that
/// advertises the given protocol version and public key.
/// The private key will be used to sign [`PeerRecord`](libp2p_core::PeerRecord)
/// for verifiable address advertisement.
pub fn new_with_signed_peer_record(protocol_version: String, local_keypair: &Keypair) -> Self {
Self {
protocol_version,
agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")),
local_key: local_keypair.into(),
interval: Duration::from_secs(5 * 60),
push_listen_addr_updates: false,
cache_size: 100,
Expand Down Expand Up @@ -209,7 +231,7 @@ impl Config {

/// Get the local public key of the Config.
pub fn local_public_key(&self) -> &PublicKey {
&self.local_public_key
self.local_key.public_key()
}

/// Get the agent version of the Config.
Expand Down Expand Up @@ -380,7 +402,7 @@ impl NetworkBehaviour for Behaviour {
Ok(Handler::new(
self.config.interval,
peer,
self.config.local_public_key.clone(),
self.config.local_key.clone(),
self.config.protocol_version.clone(),
self.config.agent_version.clone(),
remote_addr.clone(),
Expand Down Expand Up @@ -413,7 +435,7 @@ impl NetworkBehaviour for Behaviour {
Ok(Handler::new(
self.config.interval,
peer,
self.config.local_public_key.clone(),
self.config.local_key.clone(),
self.config.protocol_version.clone(),
self.config.agent_version.clone(),
// TODO: This is weird? That is the public address we dialed,
Expand Down Expand Up @@ -670,6 +692,39 @@ impl PeerCache {
}
}

#[derive(Debug, Clone)]
pub(crate) enum KeyType {
// With public key only the behaviour will not
// be able to produce a `SignedEnvelope`.
// Reduce enum size with heap allocated `Box<T>`
PublicKey(Box<PublicKey>),
Keypair {
keypair: Box<Keypair>,
public_key: Box<PublicKey>,
},
}
Comment thread
drHuangMHT marked this conversation as resolved.
impl From<PublicKey> for KeyType {
fn from(value: PublicKey) -> Self {
Self::PublicKey(value.clone().into())
Comment thread
drHuangMHT marked this conversation as resolved.
Outdated
}
}
impl From<&Keypair> for KeyType {
fn from(value: &Keypair) -> Self {
Self::Keypair {
public_key: value.public().into(),
keypair: value.clone().into(),
}
}
}
impl KeyType {
pub(crate) fn public_key(&self) -> &PublicKey {
match &self {
KeyType::PublicKey(pubkey) => pubkey,
KeyType::Keypair { public_key, .. } => public_key,
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
7 changes: 7 additions & 0 deletions protocols/identify/src/generated/structs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,11 @@ message Identify {
optional bytes observedAddr = 4;

repeated string protocols = 3;

// signedPeerRecord contains a serialized SignedEnvelope containing a PeerRecord,
// signed by the sending node. It contains the same addresses as the listenAddrs field, but
// in a form that lets us share authenticated addrs with other peers.
// see github.com/libp2p/go-libp2p/core/record/pb/envelope.proto and
// github.com/libp2p/go-libp2p/core/peer/pb/peer_record.proto for message definitions.
Comment thread
drHuangMHT marked this conversation as resolved.
Outdated
optional bytes signedPeerRecord = 8;
}
4 changes: 4 additions & 0 deletions protocols/identify/src/generated/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct Identify {
pub listenAddrs: Vec<Vec<u8>>,
pub observedAddr: Option<Vec<u8>>,
pub protocols: Vec<String>,
pub signedPeerRecord: Option<Vec<u8>>,
}

impl<'a> MessageRead<'a> for Identify {
Expand All @@ -35,6 +36,7 @@ impl<'a> MessageRead<'a> for Identify {
Ok(18) => msg.listenAddrs.push(r.read_bytes(bytes)?.to_owned()),
Ok(34) => msg.observedAddr = Some(r.read_bytes(bytes)?.to_owned()),
Ok(26) => msg.protocols.push(r.read_string(bytes)?.to_owned()),
Ok(66) => msg.signedPeerRecord = Some(r.read_bytes(bytes)?.to_owned()),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
Expand All @@ -52,6 +54,7 @@ impl MessageWrite for Identify {
+ self.listenAddrs.iter().map(|s| 1 + sizeof_len((s).len())).sum::<usize>()
+ self.observedAddr.as_ref().map_or(0, |m| 1 + sizeof_len((m).len()))
+ self.protocols.iter().map(|s| 1 + sizeof_len((s).len())).sum::<usize>()
+ self.signedPeerRecord.as_ref().map_or(0, |m| 1 + sizeof_len((m).len()))
}

fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
Expand All @@ -61,6 +64,7 @@ impl MessageWrite for Identify {
for s in &self.listenAddrs { w.write_with_tag(18, |w| w.write_bytes(&**s))?; }
if let Some(ref s) = self.observedAddr { w.write_with_tag(34, |w| w.write_bytes(&**s))?; }
for s in &self.protocols { w.write_with_tag(26, |w| w.write_string(&**s))?; }
if let Some(ref s) = self.signedPeerRecord { w.write_with_tag(66, |w| w.write_bytes(&**s))?; }
Ok(())
}
}
Expand Down
28 changes: 19 additions & 9 deletions protocols/identify/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use libp2p_core::{
upgrade::{ReadyUpgrade, SelectUpgrade},
Multiaddr,
};
use libp2p_identity::{PeerId, PublicKey};
use libp2p_identity::PeerId;
use libp2p_swarm::{
handler::{
ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound,
Expand All @@ -45,8 +45,8 @@ use smallvec::SmallVec;
use tracing::Level;

use crate::{
protocol,
protocol::{Info, PushInfo, UpgradeError},
behaviour::KeyType,
protocol::{self, Info, PushInfo, UpgradeError},
PROTOCOL_NAME, PUSH_PROTOCOL_NAME,
};

Expand Down Expand Up @@ -80,8 +80,8 @@ pub struct Handler {
/// The interval of `trigger_next_identify`, i.e. the recurrent delay.
interval: Duration,

/// The public key of the local peer.
public_key: PublicKey,
/// The key of the local peer.
local_key: KeyType,

/// Application-specific version of the protocol family used by the peer,
/// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`.
Expand Down Expand Up @@ -125,10 +125,10 @@ pub enum Event {

impl Handler {
/// Creates a new `Handler`.
pub fn new(
pub(crate) fn new(
interval: Duration,
remote_peer_id: PeerId,
public_key: PublicKey,
local_key: KeyType,
protocol_version: String,
agent_version: String,
observed_addr: Multiaddr,
Expand All @@ -144,7 +144,7 @@ impl Handler {
trigger_next_identify: Delay::new(Duration::ZERO),
exchanged_one_periodic_identify: false,
interval,
public_key,
local_key,
protocol_version,
agent_version,
observed_addr,
Expand Down Expand Up @@ -226,13 +226,23 @@ impl Handler {
}

fn build_info(&mut self) -> Info {
let signed_envelope = match &self.local_key {
KeyType::PublicKey(_) => None,
KeyType::Keypair { keypair, .. } => libp2p_core::PeerRecord::new(
keypair,
Vec::from_iter(self.external_addresses.iter().cloned()),
)
.ok()
.map(|r| r.into_signed_envelope()),
};
Info {
public_key: self.public_key.clone(),
public_key: self.local_key.public_key().clone(),
protocol_version: self.protocol_version.clone(),
agent_version: self.agent_version.clone(),
listen_addrs: Vec::from_iter(self.external_addresses.iter().cloned()),
protocols: Vec::from_iter(self.local_supported_protocols.iter().cloned()),
observed_addr: self.observed_addr.clone(),
signed_peer_record: signed_envelope,
}
}

Expand Down
Loading