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

Signed Peer Records #2081

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 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
2 changes: 1 addition & 1 deletion core/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
// DEALINGS IN THE SOFTWARE.

fn main() {
prost_build::compile_protos(&["src/keys.proto"], &["src"]).unwrap();
prost_build::compile_protos(&["src/keys.proto", "src/envelope.proto", "src/peer_record.proto"], &["src"]).unwrap();
}
12 changes: 12 additions & 0 deletions core/src/envelope.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto2";

package envelope_proto;

import "keys.proto";

message Envelope {
required keys_proto.PublicKey public_key = 1;
required bytes payload_type = 2;
required bytes payload = 3;
required bytes signature = 5;
}
53 changes: 34 additions & 19 deletions core/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod error;

use self::error::*;
use crate::{PeerId, keys_proto};
use std::convert::{TryFrom, TryInto};

/// Identity keypair of a node.
///
Expand Down Expand Up @@ -166,6 +167,7 @@ impl PublicKey {
/// that the signature has been produced by the corresponding
/// private key (authenticity), and that the message has not been
/// tampered with (integrity).
#[must_use]
pub fn verify(&self, msg: &[u8], sig: &[u8]) -> bool {
use PublicKey::*;
match self {
Expand All @@ -182,7 +184,33 @@ impl PublicKey {
pub fn into_protobuf_encoding(self) -> Vec<u8> {
use prost::Message;

let public_key = match self {
let public_key = keys_proto::PublicKey::from(self);

let mut buf = Vec::with_capacity(public_key.encoded_len());
public_key.encode(&mut buf).expect("Vec<u8> provides capacity as needed");
buf
}

/// Decode a public key from a protobuf structure, e.g. read from storage
/// or received from another node.
pub fn from_protobuf_encoding(bytes: &[u8]) -> Result<PublicKey, DecodingError> {
use prost::Message;

let pubkey = keys_proto::PublicKey::decode(bytes)
.map_err(|e| DecodingError::new("Protobuf").source(e))?;

pubkey.try_into()
}

/// Convert the `PublicKey` into the corresponding `PeerId`.
pub fn into_peer_id(self) -> PeerId {
self.into()
}
}

impl From<PublicKey> for keys_proto::PublicKey {
fn from(key: PublicKey) -> Self {
match key {
PublicKey::Ed25519(key) =>
keys_proto::PublicKey {
r#type: keys_proto::KeyType::Ed25519 as i32,
Expand All @@ -200,22 +228,14 @@ impl PublicKey {
r#type: keys_proto::KeyType::Secp256k1 as i32,
data: key.encode().to_vec()
}
};

let mut buf = Vec::with_capacity(public_key.encoded_len());
public_key.encode(&mut buf).expect("Vec<u8> provides capacity as needed");
buf
}
}
}

/// Decode a public key from a protobuf structure, e.g. read from storage
/// or received from another node.
pub fn from_protobuf_encoding(bytes: &[u8]) -> Result<PublicKey, DecodingError> {
use prost::Message;

#[allow(unused_mut)] // Due to conditional compilation.
let mut pubkey = keys_proto::PublicKey::decode(bytes)
.map_err(|e| DecodingError::new("Protobuf").source(e))?;
impl TryFrom<keys_proto::PublicKey> for PublicKey {
type Error = DecodingError;

fn try_from(pubkey: keys_proto::PublicKey) -> Result<Self, Self::Error> {
let key_type = keys_proto::KeyType::from_i32(pubkey.r#type)
.ok_or_else(|| DecodingError::new(format!("unknown key type: {}", pubkey.r#type)))?;

Expand Down Expand Up @@ -243,11 +263,6 @@ impl PublicKey {
}
}
}

/// Convert the `PublicKey` into the corresponding `PeerId`.
pub fn into_peer_id(self) -> PeerId {
self.into()
}
}

#[cfg(test)]
Expand Down
12 changes: 12 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ mod keys_proto {
include!(concat!(env!("OUT_DIR"), "/keys_proto.rs"));
}

mod envelope_proto {
include!(concat!(env!("OUT_DIR"), "/envelope_proto.rs"));
}

mod peer_record_proto {
include!(concat!(env!("OUT_DIR"), "/peer_record_proto.rs"));
}

/// Multi-address re-export.
pub use multiaddr;
pub type Negotiated<T> = multistream_select::Negotiated<T>;
Expand All @@ -53,6 +61,8 @@ pub mod muxing;
pub mod network;
pub mod transport;
pub mod upgrade;
pub mod signed_envelope;
pub mod peer_record;

pub use multiaddr::Multiaddr;
pub use multihash;
Expand All @@ -64,6 +74,8 @@ pub use translation::address_translation;
pub use upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo, UpgradeError, ProtocolName};
pub use connection::{Connected, Endpoint, ConnectedPoint};
pub use network::Network;
pub use signed_envelope::SignedEnvelope;
pub use peer_record::PeerRecord;

use std::{future::Future, pin::Pin};

Expand Down
22 changes: 22 additions & 0 deletions core/src/peer_record.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto2";

package peer_record_proto;

// PeerRecord contains the listen addresses for a peer at a particular point in time.
message PeerRecord {
// AddressInfo wraps a multiaddr. In the future, it may be extended to
// contain additional metadata, such as "routability" (whether an address is
// local or global, etc).
message AddressInfo {
required bytes multiaddr = 1;
}

// the peer id of the subject of the record (who these addresses belong to).
required bytes peer_id = 1;

// A monotonically increasing sequence number, used for record ordering.
required uint64 seq = 2;

// All current listen addresses
repeated AddressInfo addresses = 3;
}
199 changes: 199 additions & 0 deletions core/src/peer_record.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use crate::identity::error::SigningError;
use crate::identity::Keypair;
use crate::signed_envelope::SignedEnvelope;
use crate::{peer_record_proto, signed_envelope, Multiaddr, PeerId};
use std::convert::TryInto;
use std::fmt;
use std::time::SystemTime;

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

/// Represents a peer routing record.
///
/// Peer records are designed to be distributable and carry a signature by being wrapped in a signed envelope.
/// For more information see RFC0003 of the libp2p specifications: https://github.com/libp2p/specs/blob/master/RFC/0003-routing-records.md
#[derive(Debug, PartialEq, Clone)]
pub struct PeerRecord {
peer_id: PeerId,
seq: u64,
addresses: Vec<Multiaddr>,

/// A signed envelope representing this [`PeerRecord`].
///
/// If this [`PeerRecord`] was constructed from a [`SignedEnvelope`], this is the original instance.
envelope: SignedEnvelope,
}

impl PeerRecord {
/// Attempt to re-construct a [`PeerRecord`] from a [`SignedEnvelope`].
///
/// 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 prost::Message;

let payload = envelope.payload(String::from(DOMAIN_SEP), PAYLOAD_TYPE.as_bytes())?;
let record = peer_record_proto::PeerRecord::decode(payload)?;

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

Ok(Self {
peer_id,
seq,
addresses,
envelope,
})
}

/// Construct a new [`PeerRecord`] by authenticating the provided addresses with the given key.
///
/// This is the same key that is used for authenticating every libp2p connection of your application, i.e. what you use when setting up your [`crate::transport::Transport`].
pub fn new(key: Keypair, addresses: Vec<Multiaddr>) -> Result<Self, SigningError> {
use prost::Message;

let seq = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("now() is never before UNIX_EPOCH")
.as_secs();
let peer_id = key.public().into_peer_id();

let payload = {
let record = peer_record_proto::PeerRecord {
peer_id: peer_id.to_bytes(),
seq,
addresses: addresses
.iter()
.map(|m| peer_record_proto::peer_record::AddressInfo {
multiaddr: m.to_vec(),
})
.collect(),
};

let mut buf = Vec::with_capacity(record.encoded_len());
record
.encode(&mut buf)
.expect("Vec<u8> provides capacity as needed");
buf
};

let envelope = SignedEnvelope::new(
key,
String::from(DOMAIN_SEP),
PAYLOAD_TYPE.as_bytes().to_vec(),
payload,
)?;

Ok(Self {
peer_id,
seq,
addresses,
envelope,
})
}

pub fn to_signed_envelope(&self) -> SignedEnvelope {
self.envelope.clone()
}

pub fn into_signed_envelope(self) -> SignedEnvelope {
self.envelope
}

pub fn peer_id(&self) -> PeerId {
self.peer_id
}

pub fn seq(&self) -> u64 {
self.seq
}

pub fn addresses(&self) -> &[Multiaddr] {
self.addresses.as_slice()
}
}

#[derive(Debug)]
pub enum FromEnvelopeError {
/// Failed to extract the payload from the envelope.
BadPayload(signed_envelope::ReadPayloadError),
/// Failed to decode the provided bytes as a [`PeerRecord`].
InvalidPeerRecord(prost::DecodeError),
/// Failed to decode the peer ID.
InvalidPeerId(multihash::Error),
/// Failed to decode a multi-address.
InvalidMultiaddr(multiaddr::Error),
}

impl From<signed_envelope::ReadPayloadError> for FromEnvelopeError {
fn from(e: signed_envelope::ReadPayloadError) -> Self {
Self::BadPayload(e)
}
}

impl From<prost::DecodeError> for FromEnvelopeError {
fn from(e: prost::DecodeError) -> Self {
Self::InvalidPeerRecord(e)
}
}

impl From<multihash::Error> for FromEnvelopeError {
fn from(e: multihash::Error) -> Self {
Self::InvalidPeerId(e)
}
}

impl From<multiaddr::Error> for FromEnvelopeError {
fn from(e: multiaddr::Error) -> Self {
Self::InvalidMultiaddr(e)
}
}

impl fmt::Display for FromEnvelopeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BadPayload(_) => write!(f, "Failed to extract payload from envelope"),
Self::InvalidPeerRecord(_) => {
write!(f, "Failed to decode bytes as PeerRecord")
}
Self::InvalidPeerId(_) => write!(f, "Failed to decode bytes as PeerId"),
Self::InvalidMultiaddr(_) => {
write!(f, "Failed to decode bytes as MultiAddress")
}
}
}
}

impl std::error::Error for FromEnvelopeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidPeerRecord(inner) => Some(inner),
Self::InvalidPeerId(inner) => Some(inner),
Self::InvalidMultiaddr(inner) => Some(inner),
Self::BadPayload(inner) => Some(inner),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

const HOME: &str = "/ip4/127.0.0.1/tcp/1337";

#[test]
fn roundtrip_envelope() {
let record =
PeerRecord::new(Keypair::generate_ed25519(), vec![HOME.parse().unwrap()]).unwrap();

let envelope = record.to_signed_envelope();
let reconstructed = PeerRecord::from_signed_envelope(envelope).unwrap();

assert_eq!(reconstructed, record)
}
}
Loading