From 2a09cdbabb46ce9fcaf549b28f421795927ddf95 Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Tue, 31 Dec 2024 18:07:37 +0800 Subject: [PATCH 1/8] implement signedPeerRecord --- protocols/identify/src/behaviour.rs | 66 ++++++++++++++++--- .../identify/src/generated/structs.proto | 7 ++ protocols/identify/src/generated/structs.rs | 6 +- protocols/identify/src/handler.rs | 24 +++++-- protocols/identify/src/protocol.rs | 21 ++++-- 5 files changed, 101 insertions(+), 23 deletions(-) diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index 0cd27d90717..2dfed1001cd 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -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, @@ -118,7 +120,7 @@ pub struct Config { /// 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, + local_key: CryptoKey, /// Name and version of the local peer implementation, similar to the /// `User-Agent` header in the HTTP protocol. /// @@ -156,12 +158,25 @@ pub struct Config { impl Config { /// Creates a new configuration for the identify [`Behaviour`] that - /// advertises the given protocol version and public key. - pub fn new(protocol_version: String, local_public_key: PublicKey) -> Self { + /// advertises the given protocol version and public key. + /// Use `new_with_keypair` for `SignedPeerRecord` support. + pub fn new(protocol_version: String, public_key: PublicKey) -> Self { Self { protocol_version, agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")), - local_public_key, + local_key: public_key.into(), + interval: Duration::from_secs(5 * 60), + push_listen_addr_updates: false, + cache_size: 100, + hide_listen_addrs: false, + } + } + + pub fn new_with_keypair(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, @@ -209,7 +224,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. @@ -380,7 +395,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(), @@ -413,7 +428,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, @@ -670,6 +685,39 @@ impl PeerCache { } } +#[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub enum CryptoKey { + // With public key only the behaviour will not + // be able to produce a `SignedEnvelope`. + Public(PublicKey), + Keypair { + keypair: Keypair, + public_key: PublicKey, + }, +} +impl From for CryptoKey { + fn from(value: PublicKey) -> Self { + Self::Public(value) + } +} +impl From for CryptoKey { + fn from(value: Keypair) -> Self { + Self::Keypair { + public_key: value.public(), + keypair: value, + } + } +} +impl CryptoKey { + pub(crate) fn public_key(&self) -> &PublicKey { + match &self { + CryptoKey::Public(pubkey) => pubkey, + CryptoKey::Keypair { public_key, .. } => public_key, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/protocols/identify/src/generated/structs.proto b/protocols/identify/src/generated/structs.proto index fbe6836bfdb..c9df399294c 100644 --- a/protocols/identify/src/generated/structs.proto +++ b/protocols/identify/src/generated/structs.proto @@ -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. + optional bytes signedPeerRecord = 8; } diff --git a/protocols/identify/src/generated/structs.rs b/protocols/identify/src/generated/structs.rs index 3be9b6f94ad..1d790d011db 100644 --- a/protocols/identify/src/generated/structs.rs +++ b/protocols/identify/src/generated/structs.rs @@ -22,6 +22,7 @@ pub struct Identify { pub listenAddrs: Vec>, pub observedAddr: Option>, pub protocols: Vec, + pub signedPeerRecord: Option>, } impl<'a> MessageRead<'a> for Identify { @@ -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), } @@ -43,7 +45,7 @@ impl<'a> MessageRead<'a> for Identify { } } -impl MessageWrite for Identify { +impl<'a> MessageWrite for Identify { fn get_size(&self) -> usize { 0 + self.protocolVersion.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) @@ -52,6 +54,7 @@ impl MessageWrite for Identify { + self.listenAddrs.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + self.observedAddr.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) + self.protocols.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + + self.signedPeerRecord.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) } fn write_message(&self, w: &mut Writer) -> Result<()> { @@ -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(()) } } diff --git a/protocols/identify/src/handler.rs b/protocols/identify/src/handler.rs index 6e5af290cd2..693f7953104 100644 --- a/protocols/identify/src/handler.rs +++ b/protocols/identify/src/handler.rs @@ -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, @@ -45,8 +45,8 @@ use smallvec::SmallVec; use tracing::Level; use crate::{ - protocol, - protocol::{Info, PushInfo, UpgradeError}, + behaviour::CryptoKey, + protocol::{self, Info, PushInfo, UpgradeError}, PROTOCOL_NAME, PUSH_PROTOCOL_NAME, }; @@ -81,7 +81,7 @@ pub struct Handler { interval: Duration, /// The public key of the local peer. - public_key: PublicKey, + local_key: CryptoKey, /// Application-specific version of the protocol family used by the peer, /// e.g. `ipfs/1.0.0` or `polkadot/1.0.0`. @@ -128,7 +128,7 @@ impl Handler { pub fn new( interval: Duration, remote_peer_id: PeerId, - public_key: PublicKey, + public_key: CryptoKey, protocol_version: String, agent_version: String, observed_addr: Multiaddr, @@ -144,7 +144,7 @@ impl Handler { trigger_next_identify: Delay::new(Duration::ZERO), exchanged_one_periodic_identify: false, interval, - public_key, + local_key: public_key, protocol_version, agent_version, observed_addr, @@ -232,13 +232,23 @@ impl Handler { } fn build_info(&mut self) -> Info { + let signed_envelope = match &self.local_key { + CryptoKey::Public(_) => None, + CryptoKey::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, } } diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index 257ec1f88d2..9be53d61174 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -22,7 +22,7 @@ use std::io; use asynchronous_codec::{FramedRead, FramedWrite}; use futures::prelude::*; -use libp2p_core::{multiaddr, Multiaddr}; +use libp2p_core::{multiaddr, Multiaddr, SignedEnvelope}; use libp2p_identity as identity; use libp2p_identity::PublicKey; use libp2p_swarm::StreamProtocol; @@ -53,6 +53,7 @@ pub struct Info { pub protocols: Vec, /// Address observed by or for the remote. pub observed_addr: Multiaddr, + pub signed_peer_record: Option, } impl Info { @@ -108,6 +109,10 @@ where listenAddrs: listen_addrs, observedAddr: Some(info.observed_addr.to_vec()), protocols: info.protocols.iter().map(|p| p.to_string()).collect(), + signedPeerRecord: info + .signed_peer_record + .clone() + .map(|r| r.into_protobuf_encoding()), }; let mut framed_io = FramedWrite::new( @@ -166,7 +171,7 @@ where fn parse_listen_addrs(listen_addrs: Vec>) -> Vec { listen_addrs .into_iter() - .filter_map(|bytes| match Multiaddr::try_from(bytes) { + .filter_map(|bytes| match Multiaddr::try_from(bytes.to_vec()) { Ok(a) => Some(a), Err(e) => { tracing::debug!("Unable to parse multiaddr: {e:?}"); @@ -179,7 +184,7 @@ fn parse_listen_addrs(listen_addrs: Vec>) -> Vec { fn parse_protocols(protocols: Vec) -> Vec { protocols .into_iter() - .filter_map(|p| match StreamProtocol::try_from_owned(p) { + .filter_map(|p| match StreamProtocol::try_from_owned(p.to_string()) { Ok(p) => Some(p), Err(e) => { tracing::debug!("Received invalid protocol from peer: {e}"); @@ -200,7 +205,7 @@ fn parse_public_key(public_key: Option>) -> Option { } fn parse_observed_addr(observed_addr: Option>) -> Option { - observed_addr.and_then(|bytes| match Multiaddr::try_from(bytes) { + observed_addr.and_then(|bytes| match Multiaddr::try_from(bytes.to_vec()) { Ok(a) => Some(a), Err(e) => { tracing::debug!("Unable to parse observed multiaddr: {e:?}"); @@ -228,6 +233,9 @@ impl TryFrom for Info { listen_addrs: parse_listen_addrs(msg.listenAddrs), protocols: parse_protocols(msg.protocols), observed_addr: parse_observed_addr(msg.observedAddr).unwrap_or(Multiaddr::empty()), + signed_peer_record: msg + .signedPeerRecord + .and_then(|b| SignedEnvelope::from_protobuf_encoding(b.as_ref()).ok()), }; Ok(info) @@ -240,8 +248,8 @@ impl TryFrom for PushInfo { fn try_from(msg: proto::Identify) -> Result { let info = PushInfo { public_key: parse_public_key(msg.publicKey), - protocol_version: msg.protocolVersion, - agent_version: msg.agentVersion, + protocol_version: msg.protocolVersion.map(|v| v.to_string()), + agent_version: msg.agentVersion.map(|v| v.to_string()), listen_addrs: parse_listen_addrs(msg.listenAddrs), protocols: parse_protocols(msg.protocols), observed_addr: parse_observed_addr(msg.observedAddr), @@ -293,6 +301,7 @@ mod tests { .public() .encode_protobuf(), ), + signedPeerRecord: None, }; let info = PushInfo::try_from(payload).expect("not to fail"); From b5d7aec5e82594c499e14e8ded3a3b995870f45b Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Wed, 1 Jan 2025 17:51:08 +0800 Subject: [PATCH 2/8] apply suggestions --- protocols/identify/src/behaviour.rs | 34 +++++++++++++++++------------ protocols/identify/src/handler.rs | 14 ++++++------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index 2dfed1001cd..5acee4f4945 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -119,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_key: CryptoKey, + /// 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. /// @@ -159,7 +161,7 @@ pub struct Config { impl Config { /// Creates a new configuration for the identify [`Behaviour`] that /// advertises the given protocol version and public key. - /// Use `new_with_keypair` for `SignedPeerRecord` support. + /// Use [`new_with_keypair`](Config::new_with_keypair) for `signedPeerRecord` support. pub fn new(protocol_version: String, public_key: PublicKey) -> Self { Self { protocol_version, @@ -172,7 +174,11 @@ impl Config { } } - pub fn new_with_keypair(protocol_version: String, local_keypair: Keypair) -> Self { + /// 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_keypair(protocol_version: String, local_keypair: &Keypair) -> Self { Self { protocol_version, agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")), @@ -687,33 +693,33 @@ impl PeerCache { #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] -pub enum CryptoKey { +pub(crate) enum KeyType { // With public key only the behaviour will not // be able to produce a `SignedEnvelope`. - Public(PublicKey), + PublicKey(PublicKey), Keypair { keypair: Keypair, public_key: PublicKey, }, } -impl From for CryptoKey { +impl From for KeyType { fn from(value: PublicKey) -> Self { - Self::Public(value) + Self::PublicKey(value.clone()) } } -impl From for CryptoKey { - fn from(value: Keypair) -> Self { +impl From<&Keypair> for KeyType { + fn from(value: &Keypair) -> Self { Self::Keypair { public_key: value.public(), - keypair: value, + keypair: value.clone(), } } } -impl CryptoKey { +impl KeyType { pub(crate) fn public_key(&self) -> &PublicKey { match &self { - CryptoKey::Public(pubkey) => pubkey, - CryptoKey::Keypair { public_key, .. } => public_key, + KeyType::PublicKey(pubkey) => pubkey, + KeyType::Keypair { public_key, .. } => public_key, } } } diff --git a/protocols/identify/src/handler.rs b/protocols/identify/src/handler.rs index 693f7953104..f3f1a9c25d5 100644 --- a/protocols/identify/src/handler.rs +++ b/protocols/identify/src/handler.rs @@ -45,7 +45,7 @@ use smallvec::SmallVec; use tracing::Level; use crate::{ - behaviour::CryptoKey, + behaviour::KeyType, protocol::{self, Info, PushInfo, UpgradeError}, PROTOCOL_NAME, PUSH_PROTOCOL_NAME, }; @@ -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. - local_key: CryptoKey, + /// 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`. @@ -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: CryptoKey, + public_key: KeyType, protocol_version: String, agent_version: String, observed_addr: Multiaddr, @@ -233,8 +233,8 @@ impl Handler { fn build_info(&mut self) -> Info { let signed_envelope = match &self.local_key { - CryptoKey::Public(_) => None, - CryptoKey::Keypair { keypair, .. } => libp2p_core::PeerRecord::new( + KeyType::PublicKey(_) => None, + KeyType::Keypair { keypair, .. } => libp2p_core::PeerRecord::new( keypair, Vec::from_iter(self.external_addresses.iter().cloned()), ) From 4d3d692e10257ffb87981d2fdcf9f6ee884d5928 Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Fri, 3 Jan 2025 12:32:21 +0800 Subject: [PATCH 3/8] add test --- protocols/identify/src/protocol.rs | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index 9be53d61174..a4547301128 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -275,7 +275,11 @@ pub enum UpgradeError { #[cfg(test)] mod tests { + use std::str::FromStr; + + use libp2p_core::PeerRecord; use libp2p_identity as identity; + use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; use super::*; @@ -308,4 +312,80 @@ mod tests { assert_eq!(info.listen_addrs, vec![valid_multiaddr]) } + + #[test] + fn protobuf_roundtrip() { + // from go implementation of identify, + // see https://github.com/libp2p/go-libp2p/blob/2209ae05976df6a1cc2631c961f57549d109008c/p2p/protocol/identify/pb/identify.pb.go#L133 + // signedPeerRecord field is a dummy one that can't be properly parsed into SignedEnvelope, + // but the wire format doesn't care. + let go_protobuf: [u8; 375] = [ + 0x0a, 0x27, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x62, 0x2f, 0x69, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0b, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x2e, 0x70, 0x62, 0x22, 0x86, + 0x02, 0x0a, 0x08, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x12, 0x28, 0x0a, + 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, + 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x6c, 0x69, 0x73, + 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x0b, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, + 0x22, 0x0a, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x41, 0x64, 0x64, + 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6f, 0x62, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, + 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x50, 0x65, 0x65, 0x72, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x42, 0x36, 0x5a, + 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, + 0x62, 0x70, 0x32, 0x70, 0x2f, 0x67, 0x6f, 0x2d, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, + 0x2f, 0x70, 0x32, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x79, 0x2f, 0x70, 0x62, + ]; + let mut buf = [0u8; 375]; + let mut message = + proto::Identify::from_reader(&mut BytesReader::from_bytes(&go_protobuf), &go_protobuf) + .expect("read to succeed"); + + // The actual bytes they put in is "github.com/libp2p/go-libp2p/p2p/protocol/identify/pb". + // Starting with Z4 means it is zig-zag-encoded 4-byte varint of string, appended by + // protobuf. + assert_eq!( + String::from_utf8( + message + .signedPeerRecord + .clone() + .expect("field to be present") + ) + .expect("parse to succeed"), + "Z4github.com/libp2p/go-libp2p/p2p/protocol/identify/pb".to_string() + ); + message + .write_message(&mut Writer::new(&mut buf[..])) + .expect("same length after roundtrip"); + assert_eq!(go_protobuf, buf); + + let identity = identity::Keypair::generate_ed25519(); + let record = PeerRecord::new( + &identity, + vec![Multiaddr::from_str("/ip4/0.0.0.0").expect("parse to succeed")], + ) + .expect("infallible siging using ed25519"); + message + .signedPeerRecord + .replace(record.into_signed_envelope().into_protobuf_encoding()); + let mut buf = Vec::new(); + message + .write_message(&mut Writer::new(&mut buf)) + .expect("write to succeed"); + let parsed_message = proto::Identify::from_reader(&mut BytesReader::from_bytes(&buf), &buf) + .expect("read to succeed"); + assert_eq!(message, parsed_message) + } } From 59f80f2864618d8dab97c57faf6300e629a11d39 Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Sat, 4 Jan 2025 13:23:14 +0800 Subject: [PATCH 4/8] reduce diff --- protocols/identify/src/generated/structs.rs | 2 +- protocols/identify/src/handler.rs | 4 ++-- protocols/identify/src/protocol.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/protocols/identify/src/generated/structs.rs b/protocols/identify/src/generated/structs.rs index 1d790d011db..fcda2159a60 100644 --- a/protocols/identify/src/generated/structs.rs +++ b/protocols/identify/src/generated/structs.rs @@ -45,7 +45,7 @@ impl<'a> MessageRead<'a> for Identify { } } -impl<'a> MessageWrite for Identify { +impl MessageWrite for Identify { fn get_size(&self) -> usize { 0 + self.protocolVersion.as_ref().map_or(0, |m| 1 + sizeof_len((m).len())) diff --git a/protocols/identify/src/handler.rs b/protocols/identify/src/handler.rs index f3f1a9c25d5..c4ef7e91201 100644 --- a/protocols/identify/src/handler.rs +++ b/protocols/identify/src/handler.rs @@ -128,7 +128,7 @@ impl Handler { pub(crate) fn new( interval: Duration, remote_peer_id: PeerId, - public_key: KeyType, + local_key: KeyType, protocol_version: String, agent_version: String, observed_addr: Multiaddr, @@ -144,7 +144,7 @@ impl Handler { trigger_next_identify: Delay::new(Duration::ZERO), exchanged_one_periodic_identify: false, interval, - local_key: public_key, + local_key, protocol_version, agent_version, observed_addr, diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index a4547301128..ad0b5c48620 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -171,7 +171,7 @@ where fn parse_listen_addrs(listen_addrs: Vec>) -> Vec { listen_addrs .into_iter() - .filter_map(|bytes| match Multiaddr::try_from(bytes.to_vec()) { + .filter_map(|bytes| match Multiaddr::try_from(bytes) { Ok(a) => Some(a), Err(e) => { tracing::debug!("Unable to parse multiaddr: {e:?}"); @@ -184,7 +184,7 @@ fn parse_listen_addrs(listen_addrs: Vec>) -> Vec { fn parse_protocols(protocols: Vec) -> Vec { protocols .into_iter() - .filter_map(|p| match StreamProtocol::try_from_owned(p.to_string()) { + .filter_map(|p| match StreamProtocol::try_from_owned(p) { Ok(p) => Some(p), Err(e) => { tracing::debug!("Received invalid protocol from peer: {e}"); @@ -205,7 +205,7 @@ fn parse_public_key(public_key: Option>) -> Option { } fn parse_observed_addr(observed_addr: Option>) -> Option { - observed_addr.and_then(|bytes| match Multiaddr::try_from(bytes.to_vec()) { + observed_addr.and_then(|bytes| match Multiaddr::try_from(bytes) { Ok(a) => Some(a), Err(e) => { tracing::debug!("Unable to parse observed multiaddr: {e:?}"); @@ -248,8 +248,8 @@ impl TryFrom for PushInfo { fn try_from(msg: proto::Identify) -> Result { let info = PushInfo { public_key: parse_public_key(msg.publicKey), - protocol_version: msg.protocolVersion.map(|v| v.to_string()), - agent_version: msg.agentVersion.map(|v| v.to_string()), + protocol_version: msg.protocolVersion, + agent_version: msg.agentVersion, listen_addrs: parse_listen_addrs(msg.listenAddrs), protocols: parse_protocols(msg.protocols), observed_addr: parse_observed_addr(msg.observedAddr), From 12b9a29e19588898f66525fe0e4f684c6e703432 Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Fri, 10 Jan 2025 12:26:31 +0800 Subject: [PATCH 5/8] rename symbols --- protocols/identify/src/behaviour.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index 5acee4f4945..781df516240 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -178,11 +178,11 @@ impl Config { /// 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_keypair(protocol_version: String, local_keypair: &Keypair) -> Self { + pub fn new_with_signed_peer_record(protocol_version: String, keypair: &Keypair) -> Self { Self { protocol_version, agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")), - local_key: local_keypair.into(), + local_key: keypair.into(), interval: Duration::from_secs(5 * 60), push_listen_addr_updates: false, cache_size: 100, From d3d8ae6b9800f34238aa1eaaaebd0c4e11bfbe9c Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Fri, 10 Jan 2025 14:22:08 +0800 Subject: [PATCH 6/8] prefer addresses in signedPeerRecord --- core/src/peer_record.rs | 51 ++++++++++++++++-------------- protocols/identify/src/protocol.rs | 23 +++++++++++--- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/core/src/peer_record.rs b/core/src/peer_record.rs index 9c6b7f73f05..ac7c1dd19c9 100644 --- a/core/src/peer_record.rs +++ b/core/src/peer_record.rs @@ -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. /// @@ -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 { - 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::, _>>()?; - + let (_, peer_id, seq, addresses) = Self::try_deserialize_signed_envelope(&envelope)?; Ok(Self { peer_id, seq, @@ -126,6 +107,30 @@ impl PeerRecord { pub fn addresses(&self) -> &[Multiaddr] { self.addresses.as_slice() } + + pub fn try_deserialize_signed_envelope( + envelope: &SignedEnvelope, + ) -> Result<(&PublicKey, PeerId, u64, Vec), 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::, _>>()?; + Ok((signing_key, peer_id, record.seq, addresses)) + } } #[derive(thiserror::Error, Debug)] diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index ad0b5c48620..69e14412225 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -22,7 +22,7 @@ use std::io; use asynchronous_codec::{FramedRead, FramedWrite}; use futures::prelude::*; -use libp2p_core::{multiaddr, Multiaddr, SignedEnvelope}; +use libp2p_core::{multiaddr, Multiaddr, PeerRecord, SignedEnvelope}; use libp2p_identity as identity; use libp2p_identity::PublicKey; use libp2p_swarm::StreamProtocol; @@ -226,16 +226,29 @@ impl TryFrom for Info { } }; + let signed_peer_record = msg + .signedPeerRecord + .and_then(|b| SignedEnvelope::from_protobuf_encoding(b.as_ref()).ok()); + + // When signedPeerRecord contains valid addresses, ignore addresses in listenAddrs. + // When signedPeerRecord is invalid or signed by others, ignore the signedPeerRecord(set to `None`). + let (signed_peer_record, listen_addrs) = signed_peer_record + .as_ref() + .and_then(|envelope| PeerRecord::try_deserialize_signed_envelope(&envelope).ok()) + .and_then(|(envelope_public_key, _, _, addresses)| { + (*envelope_public_key == public_key).then_some(addresses) + }) + .map(|addrs| (signed_peer_record, addrs)) + .unwrap_or_else(|| (None, parse_listen_addrs(msg.listenAddrs))); + let info = Info { public_key, protocol_version: msg.protocolVersion.unwrap_or_default(), agent_version: msg.agentVersion.unwrap_or_default(), - listen_addrs: parse_listen_addrs(msg.listenAddrs), + listen_addrs, protocols: parse_protocols(msg.protocols), observed_addr: parse_observed_addr(msg.observedAddr).unwrap_or(Multiaddr::empty()), - signed_peer_record: msg - .signedPeerRecord - .and_then(|b| SignedEnvelope::from_protobuf_encoding(b.as_ref()).ok()), + signed_peer_record, }; Ok(info) From 59151b6dbdb3ded365f39d96fc4aa88a3df41a2a Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Sat, 11 Jan 2025 10:06:26 +0800 Subject: [PATCH 7/8] rename symbols --- protocols/identify/src/behaviour.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/protocols/identify/src/behaviour.rs b/protocols/identify/src/behaviour.rs index 781df516240..79014a96eb2 100644 --- a/protocols/identify/src/behaviour.rs +++ b/protocols/identify/src/behaviour.rs @@ -162,11 +162,11 @@ impl Config { /// Creates a new configuration for the identify [`Behaviour`] that /// advertises the given protocol version and public key. /// Use [`new_with_keypair`](Config::new_with_keypair) for `signedPeerRecord` support. - pub fn new(protocol_version: String, public_key: PublicKey) -> Self { + pub fn new(protocol_version: String, local_public_key: PublicKey) -> Self { Self { protocol_version, agent_version: format!("rust-libp2p/{}", env!("CARGO_PKG_VERSION")), - local_key: public_key.into(), + local_key: local_public_key.into(), interval: Duration::from_secs(5 * 60), push_listen_addr_updates: false, cache_size: 100, @@ -178,11 +178,11 @@ impl Config { /// 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, keypair: &Keypair) -> Self { + 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: keypair.into(), + local_key: local_keypair.into(), interval: Duration::from_secs(5 * 60), push_listen_addr_updates: false, cache_size: 100, From 9b6a139f4af545176f69e01b5bf3fb51d9690eac Mon Sep 17 00:00:00 2001 From: drHuangMHT Date: Sat, 11 Jan 2025 11:42:57 +0800 Subject: [PATCH 8/8] lint and fmt --- protocols/identify/src/protocol.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index 69e14412225..e3354b5b02a 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -230,11 +230,12 @@ impl TryFrom for Info { .signedPeerRecord .and_then(|b| SignedEnvelope::from_protobuf_encoding(b.as_ref()).ok()); - // When signedPeerRecord contains valid addresses, ignore addresses in listenAddrs. - // When signedPeerRecord is invalid or signed by others, ignore the signedPeerRecord(set to `None`). + // When signedPeerRecord contains valid addresses, ignore addresses in listenAddrs. + // When signedPeerRecord is invalid or signed by others, ignore the signedPeerRecord(set to + // `None`). let (signed_peer_record, listen_addrs) = signed_peer_record .as_ref() - .and_then(|envelope| PeerRecord::try_deserialize_signed_envelope(&envelope).ok()) + .and_then(|envelope| PeerRecord::try_deserialize_signed_envelope(envelope).ok()) .and_then(|(envelope_public_key, _, _, addresses)| { (*envelope_public_key == public_key).then_some(addresses) })