protocols/rendezvous: Implement protocol (#2107)

Implement the libp2p rendezvous protocol.

> A lightweight mechanism for generalized peer discovery. It can be used for
bootstrap purposes, real time peer discovery, application specific routing, and
so on.

Co-authored-by: rishflab <rishflab@hotmail.com>
Co-authored-by: Daniel Karzel <daniel@comit.network>
This commit is contained in:
Thomas Eizinger
2021-09-08 00:36:52 +10:00
committed by GitHub
parent c1ae8a046c
commit adcfdc0750
32 changed files with 4463 additions and 25 deletions

30
core/src/envelope.proto Normal file
View File

@ -0,0 +1,30 @@
syntax = "proto3";
package envelope_proto;
import "keys.proto";
// Envelope encloses a signed payload produced by a peer, along with the public
// key of the keypair it was signed with so that it can be statelessly validated
// by the receiver.
//
// The payload is prefixed with a byte string that determines the type, so it
// can be deserialized deterministically. Often, this byte string is a
// multicodec.
message Envelope {
// public_key is the public key of the keypair the enclosed payload was
// signed with.
keys_proto.PublicKey public_key = 1;
// payload_type encodes the type of payload, so that it can be deserialized
// deterministically.
bytes payload_type = 2;
// payload is the actual payload carried inside this envelope.
bytes payload = 3;
// signature is the signature produced by the private key corresponding to
// the enclosed public key, over the payload, prefixing a domain string for
// additional security.
bytes signature = 5;
}

View File

@ -42,6 +42,7 @@ pub mod error;
use self::error::*;
use crate::{keys_proto, PeerId};
use std::convert::{TryFrom, TryInto};
/// Identity keypair of a node.
///
@ -205,6 +206,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 {
@ -221,7 +223,35 @@ impl PublicKey {
pub fn to_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 to_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,
data: key.encode().to_vec(),
@ -236,24 +266,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)))?;
@ -281,11 +301,6 @@ impl PublicKey {
}
}
}
/// Convert the `PublicKey` into the corresponding `PeerId`.
pub fn to_peer_id(&self) -> PeerId {
self.into()
}
}
#[cfg(test)]

View File

@ -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>;
@ -51,6 +59,8 @@ pub mod either;
pub mod identity;
pub mod muxing;
pub mod network;
pub mod peer_record;
pub mod signed_envelope;
pub mod transport;
pub mod upgrade;
@ -61,6 +71,8 @@ pub use multihash;
pub use muxing::StreamMuxer;
pub use network::Network;
pub use peer_id::PeerId;
pub use peer_record::PeerRecord;
pub use signed_envelope::SignedEnvelope;
pub use translation::address_translation;
pub use transport::Transport;
pub use upgrade::{InboundUpgrade, OutboundUpgrade, ProtocolName, UpgradeError, UpgradeInfo};

View File

@ -109,7 +109,7 @@ where
/// A connection may close if
///
/// * it encounters an error, which includes the connection being
/// closed by the remote. In this case `error` is `Some`.
/// closed by the remote. In this case `error` is `ome`.
/// * it was actively closed by [`EstablishedConnection::start_close`],
/// i.e. a successful, orderly close. In this case `error` is `None`.
/// * it was actively closed by [`super::peer::ConnectedPeer::disconnect`] or

View File

@ -0,0 +1,27 @@
syntax = "proto3";
package peer_record_proto;
// PeerRecord messages contain information that is useful to share with other peers.
// Currently, a PeerRecord contains the public listen addresses for a peer, but this
// is expected to expand to include other information in the future.
//
// PeerRecords are designed to be serialized to bytes and placed inside of
// SignedEnvelopes before sharing with other peers.
message PeerRecord {
// AddressInfo is a wrapper around a binary multiaddr. It is defined as a
// separate message to allow us to add per-address metadata in the future.
message AddressInfo {
bytes multiaddr = 1;
}
// peer_id contains a libp2p peer id in its binary representation.
bytes peer_id = 1;
// seq contains a monotonically-increasing sequence counter to order PeerRecords in time.
uint64 seq = 2;
// addresses is a list of public listen addresses for the peer.
repeated AddressInfo addresses = 3;
}

199
core/src/peer_record.rs Normal file
View File

@ -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().to_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)
}
}

203
core/src/signed_envelope.rs Normal file
View File

@ -0,0 +1,203 @@
use crate::identity::error::SigningError;
use crate::identity::Keypair;
use crate::{identity, PublicKey};
use std::convert::TryInto;
use std::fmt;
use unsigned_varint::encode::usize_buffer;
/// A signed envelope contains an arbitrary byte string payload, a signature of the payload, and the public key that can be used to verify the signature.
///
/// For more details see libp2p RFC0002: <https://github.com/libp2p/specs/blob/master/RFC/0002-signed-envelopes.md>
#[derive(Debug, Clone, PartialEq)]
pub struct SignedEnvelope {
key: PublicKey,
payload_type: Vec<u8>,
payload: Vec<u8>,
signature: Vec<u8>,
}
impl SignedEnvelope {
/// Constructs a new [`SignedEnvelope`].
pub fn new(
key: Keypair,
domain_separation: String,
payload_type: Vec<u8>,
payload: Vec<u8>,
) -> Result<Self, SigningError> {
let buffer = signature_payload(domain_separation, &payload_type, &payload);
let signature = key.sign(&buffer)?;
Ok(Self {
key: key.public(),
payload_type,
payload,
signature,
})
}
/// Verify this [`SignedEnvelope`] against the provided domain-separation string.
#[must_use]
pub fn verify(&self, domain_separation: String) -> bool {
let buffer = signature_payload(domain_separation, &self.payload_type, &self.payload);
self.key.verify(&buffer, &self.signature)
}
/// Extract the payload of this [`SignedEnvelope`].
///
/// You must provide the correct domain-separation string and expected payload type in order to get the payload.
/// This guards against accidental mis-use of the payload where the signature was created for a different purpose or payload type.
pub fn payload(
&self,
domain_separation: String,
expected_payload_type: &[u8],
) -> Result<&[u8], ReadPayloadError> {
if &self.payload_type != expected_payload_type {
return Err(ReadPayloadError::UnexpectedPayloadType {
expected: expected_payload_type.to_vec(),
got: self.payload_type.clone(),
});
}
if !self.verify(domain_separation) {
return Err(ReadPayloadError::InvalidSignature);
}
Ok(&self.payload)
}
/// Encode this [`SignedEnvelope`] using the protobuf encoding specified in the RFC.
pub fn into_protobuf_encoding(self) -> Vec<u8> {
use prost::Message;
let envelope = crate::envelope_proto::Envelope {
public_key: Some((&self.key).into()),
payload_type: self.payload_type,
payload: self.payload,
signature: self.signature,
};
let mut buf = Vec::with_capacity(envelope.encoded_len());
envelope
.encode(&mut buf)
.expect("Vec<u8> provides capacity as needed");
buf
}
/// Decode a [`SignedEnvelope`] using the protobuf encoding specified in the RFC.
pub fn from_protobuf_encoding(bytes: &[u8]) -> Result<Self, DecodingError> {
use prost::Message;
let envelope = crate::envelope_proto::Envelope::decode(bytes)?;
Ok(Self {
key: envelope
.public_key
.ok_or(DecodingError::MissingPublicKey)?
.try_into()?,
payload_type: envelope.payload_type,
payload: envelope.payload,
signature: envelope.signature,
})
}
}
fn signature_payload(domain_separation: String, payload_type: &[u8], payload: &[u8]) -> Vec<u8> {
let mut domain_sep_length_buffer = usize_buffer();
let domain_sep_length =
unsigned_varint::encode::usize(domain_separation.len(), &mut domain_sep_length_buffer);
let mut payload_type_length_buffer = usize_buffer();
let payload_type_length =
unsigned_varint::encode::usize(payload_type.len(), &mut payload_type_length_buffer);
let mut payload_length_buffer = usize_buffer();
let payload_length = unsigned_varint::encode::usize(payload.len(), &mut payload_length_buffer);
let mut buffer = Vec::with_capacity(
domain_sep_length.len()
+ domain_separation.len()
+ payload_type_length.len()
+ payload_type.len()
+ payload_length.len()
+ payload.len(),
);
buffer.extend_from_slice(domain_sep_length);
buffer.extend_from_slice(domain_separation.as_bytes());
buffer.extend_from_slice(payload_type_length);
buffer.extend_from_slice(payload_type);
buffer.extend_from_slice(payload_length);
buffer.extend_from_slice(payload);
buffer
}
/// Errors that occur whilst decoding a [`SignedEnvelope`] from its byte representation.
#[derive(Debug)]
pub enum DecodingError {
/// Decoding the provided bytes as a signed envelope failed.
InvalidEnvelope(prost::DecodeError),
/// The public key in the envelope could not be converted to our internal public key type.
InvalidPublicKey(identity::error::DecodingError),
/// The public key in the envelope could not be converted to our internal public key type.
MissingPublicKey,
}
impl From<prost::DecodeError> for DecodingError {
fn from(e: prost::DecodeError) -> Self {
Self::InvalidEnvelope(e)
}
}
impl From<identity::error::DecodingError> for DecodingError {
fn from(e: identity::error::DecodingError) -> Self {
Self::InvalidPublicKey(e)
}
}
impl fmt::Display for DecodingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidEnvelope(_) => write!(f, "Failed to decode envelope"),
Self::InvalidPublicKey(_) => write!(f, "Failed to convert public key"),
Self::MissingPublicKey => write!(f, "Public key is missing from protobuf struct"),
}
}
}
impl std::error::Error for DecodingError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidEnvelope(inner) => Some(inner),
Self::InvalidPublicKey(inner) => Some(inner),
Self::MissingPublicKey => None,
}
}
}
/// Errors that occur whilst extracting the payload of a [`SignedEnvelope`].
#[derive(Debug)]
pub enum ReadPayloadError {
/// The signature on the signed envelope does not verify with the provided domain separation string.
InvalidSignature,
/// The payload contained in the envelope is not of the expected type.
UnexpectedPayloadType { expected: Vec<u8>, got: Vec<u8> },
}
impl fmt::Display for ReadPayloadError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidSignature => write!(f, "Invalid signature"),
Self::UnexpectedPayloadType { expected, got } => write!(
f,
"Unexpected payload type, expected {:?} but got {:?}",
expected, got
),
}
}
}
impl std::error::Error for ReadPayloadError {}