Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(identify): implement signedPeerRecord #5785

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 28 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)?;
Ok(Self {
peer_id,
seq,
Expand Down Expand Up @@ -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<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
72 changes: 63 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 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 @@
/// 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,29 @@

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_keypair`](Config::new_with_keypair) for `signedPeerRecord` support.

Check failure on line 164 in protocols/identify/src/behaviour.rs

View workflow job for this annotation

GitHub Actions / Check rustdoc intra-doc links

unresolved link to `Config::new_with_keypair`
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,
}
}

/// 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 +230,7 @@

/// 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 +401,7 @@
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 +434,7 @@
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 +691,39 @@
}
}

#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum KeyType {
// With public key only the behaviour will not
// be able to produce a `SignedEnvelope`.
PublicKey(PublicKey),
Keypair {
keypair: Keypair,
public_key: PublicKey,
},
}
impl From<PublicKey> for KeyType {
fn from(value: PublicKey) -> Self {
Self::PublicKey(value.clone())
}
}
impl From<&Keypair> for KeyType {
fn from(value: &Keypair) -> Self {
Self::Keypair {
public_key: value.public(),
keypair: value.clone(),
}
}
}
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.
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 @@ -232,13 +232,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
Loading