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

Add extension features #60

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ jobs:
run: cargo nextest run --all --release --features "secp256k1" --tests
- name: Run tests in release rust-secp256k1
run: cargo nextest run --all --release --features "rust-secp256k1" --tests
- name: Run tests in release libp2p
run: cargo nextest run --all --release --features "libp2p" --tests
- name: Run tests in release libp2p, rust-secp256k1
run: cargo nextest run --all --release --features "libp2p,rust-secp256k1" --tests
- name: Run tests in release eth2
run: cargo nextest run --all --release --features "eth2" --tests
- name: Run tests in release all features
run: cargo nextest run --all --release --all-features
check-rustdoc-links:
Expand Down
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,24 @@ ed25519-dalek = { version = "2.0.0", optional = true, features = ["rand_core"] }
secp256k1 = { version = "0.27", optional = true, default-features = false, features = [
"global-context",
] }
libp2p-core = { version = "0.40", optional = true }
libp2p-identity = { version = "0.2", optional = true, features = ["peerid", "secp256k1", "ed25519"] }
ssz_types = {version = "0.5.4", optional = true }
ethereum_ssz = { version = "0.5.3", optional = true }

[dev-dependencies]
secp256k1 = { features = ["rand-std"], version = "0.27" }
serde_json = { version = "1.0.95" }

[features]
default = ["serde", "k256"]
# default = ["serde", "k256"]
# TODO(@divma): easier for dev, remove later
default = ["serde", "k256", "quic", "libp2p", "eth2", "ed25519", "secp256k1"]
ed25519 = ["ed25519-dalek"]
rust-secp256k1 = ["secp256k1"]
quic = []
libp2p = ["libp2p-core", "libp2p-identity"]
eth2 = ["ssz_types", "ethereum_ssz"]

[lib]
name = "enr"
Expand Down
94 changes: 47 additions & 47 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#[cfg(feature = "eth2")]
use crate::{ATTESTATION_BITFIELD_ENR_KEY, ETH2_ENR_KEY, SYNC_COMMITTEE_BITFIELD_ENR_KEY};
use crate::{
ENR_VERSION, ID_ENR_KEY, IP6_ENR_KEY, IP_ENR_KEY, TCP6_ENR_KEY, TCP_ENR_KEY, UDP6_ENR_KEY,
UDP_ENR_KEY,
};
#[cfg(feature = "quic")]
use crate::{QUIC6_ENR_KEY, QUIC_ENR_KEY};

use crate::{Enr, EnrKey, EnrPublicKey, Error, Key, NodeId, MAX_ENR_SIZE};
use bytes::{Bytes, BytesMut};
use rlp::{Encodable, RlpStream};
Expand All @@ -7,10 +16,22 @@ use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};

// Generates function setters on the `EnrBuilder`.
macro_rules! generate_setter {
// Function name, variable type and key
($name:ident, $type:ty, $key:ident) => {
#[doc = concat!(" Adds a `", stringify!($name),"` field to the `ENRBuilder.")]
pub fn $name(&mut self, var: $type) -> &mut Self {
self.add_value($key, &var);
self
}
};
}

/// The base builder for generating ENR records with arbitrary signing algorithms.
pub struct Builder<K: EnrKey> {
/// The identity scheme used to build the ENR record.
id: String,
id: Vec<u8>,

/// The starting sequence number for the ENR record.
seq: u64,
Expand All @@ -27,7 +48,7 @@ impl<K: EnrKey> Default for Builder<K> {
/// Constructs a minimal [`Builder`] for the v4 identity scheme.
fn default() -> Self {
Self {
id: String::from("v4"),
id: ENR_VERSION.into(),
seq: 1,
content: BTreeMap::new(),
phantom: PhantomData,
Expand Down Expand Up @@ -63,50 +84,34 @@ impl<K: EnrKey> Builder<K> {

/// Adds an `ip` field to the `ENRBuilder`.
pub fn ip4(&mut self, ip: Ipv4Addr) -> &mut Self {
self.add_value("ip", &ip.octets().as_ref());
self.add_value(IP_ENR_KEY, &ip.octets().as_ref());
self
}

/// Adds an `ip6` field to the `ENRBuilder`.
pub fn ip6(&mut self, ip: Ipv6Addr) -> &mut Self {
self.add_value("ip6", &ip.octets().as_ref());
self
}

/*
* Removed from the builder as only the v4 scheme is currently supported.
* This is set as default in the builder.

/// Adds an `Id` field to the `ENRBuilder`.
pub fn id(&mut self, id: &str) -> &mut Self {
self.add_value("id", &id.as_bytes());
self
}
*/

/// Adds a `tcp` field to the `ENRBuilder`.
pub fn tcp4(&mut self, tcp: u16) -> &mut Self {
self.add_value("tcp", &tcp);
self
}

/// Adds a `tcp6` field to the `ENRBuilder`.
pub fn tcp6(&mut self, tcp: u16) -> &mut Self {
self.add_value("tcp6", &tcp);
self.add_value(IP6_ENR_KEY, &ip.octets().as_ref());
self
}

/// Adds a `udp` field to the `ENRBuilder`.
pub fn udp4(&mut self, udp: u16) -> &mut Self {
self.add_value("udp", &udp);
self
}

/// Adds a `udp6` field to the `ENRBuilder`.
pub fn udp6(&mut self, udp: u16) -> &mut Self {
self.add_value("udp6", &udp);
self
}
generate_setter!(tcp4, u16, TCP_ENR_KEY);
generate_setter!(tcp6, u16, TCP6_ENR_KEY);
generate_setter!(udp4, u16, UDP_ENR_KEY);
generate_setter!(udp6, u16, UDP6_ENR_KEY);
#[cfg(feature = "quic")]
generate_setter!(quic, u16, QUIC_ENR_KEY);
#[cfg(feature = "quic")]
generate_setter!(quic6, u16, QUIC6_ENR_KEY);
#[cfg(feature = "eth2")]
generate_setter!(eth2, &[u8], ETH2_ENR_KEY);
#[cfg(feature = "eth2")]
generate_setter!(attestation_bitfield, &[u8], ATTESTATION_BITFIELD_ENR_KEY);
#[cfg(feature = "eth2")]
generate_setter!(
sync_committee_bitfield,
&[u8],
SYNC_COMMITTEE_BITFIELD_ENR_KEY
);

/// Generates the rlp-encoded form of the ENR specified by the builder config.
fn rlp_content(&self) -> BytesMut {
Expand All @@ -121,10 +126,10 @@ impl<K: EnrKey> Builder<K> {
stream.out()
}

/// Signs record based on the identity scheme. Currently only "v4" is supported.
/// Signs record based on the identity scheme. Currently only [`ENR_VERSION`] is supported.
fn signature(&self, key: &K) -> Result<Vec<u8>, Error> {
match self.id.as_str() {
"v4" => key
match self.id.as_slice() {
ENR_VERSION => key
.sign_v4(&self.rlp_content())
.map_err(|_| Error::SigningError),
// unsupported identity schemes
Expand All @@ -142,17 +147,12 @@ impl<K: EnrKey> Builder<K> {
/// # Errors
/// Fails if the identity scheme is not supported, or the record size exceeds `MAX_ENR_SIZE`.
pub fn build(&mut self, key: &K) -> Result<Enr<K>, Error> {
// add the identity scheme to the content
if self.id != "v4" {
return Err(Error::UnsupportedIdentityScheme);
}

// Sanitize all data, ensuring all RLP data is correctly formatted.
for value in self.content.values() {
rlp::Rlp::new(value).data()?;
}

self.add_value_rlp("id", rlp::encode(&self.id.as_bytes()).freeze());
self.add_value_rlp(ID_ENR_KEY, rlp::encode(&self.id).freeze());

self.add_public_key(&key.public());
let rlp_content = self.rlp_content();
Expand Down
68 changes: 67 additions & 1 deletion src/keys/combined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ use super::{ed25519_dalek as ed25519, EnrKey, EnrPublicKey, SigningError};
use bytes::Bytes;
pub use k256;
use rlp::DecoderError;
use std::convert::TryInto;
use std::{collections::BTreeMap, convert::TryFrom};
use zeroize::Zeroize;

use crate::Key;

#[cfg(feature = "libp2p")]
use libp2p_identity::{
ed25519 as libp2p_ed25519, secp256k1 as libp2p_secp256k1, KeyType, PeerId, PublicKey,
};

/// A standard implementation of the `EnrKey` trait used to sign and modify ENR records. The variants here represent the currently
/// supported in-built signing schemes.
pub enum CombinedKey {
Expand All @@ -28,11 +34,48 @@ impl From<k256::ecdsa::SigningKey> for CombinedKey {
}

impl From<ed25519::SigningKey> for CombinedKey {
fn from(keypair: ed25519_dalek::SigningKey) -> Self {
fn from(keypair: ed25519::SigningKey) -> Self {
Self::Ed25519(keypair)
}
}

#[cfg(feature = "libp2p")]
impl TryFrom<libp2p_identity::Keypair> for CombinedKey {
type Error = &'static str;

fn try_from(keypair: libp2p_identity::Keypair) -> Result<Self, Self::Error> {
match keypair.key_type() {
KeyType::Secp256k1 => Ok(keypair
.try_into_secp256k1()
.expect("must be the right key type")
.into()),
KeyType::Ed25519 => Ok(keypair.try_into_ed25519().expect("right key type").into()),
_ => Err("Unsupported keypair kind"),
}
}
}

#[cfg(feature = "libp2p")]
impl From<libp2p_secp256k1::Keypair> for CombinedKey {
fn from(keypair: libp2p_secp256k1::Keypair) -> Self {
let secret = k256::ecdsa::SigningKey::from_slice(&keypair.secret().to_bytes())
.expect("libp2p key must be valid");
CombinedKey::Secp256k1(secret)
}
}

#[cfg(feature = "libp2p")]
impl From<libp2p_ed25519::Keypair> for CombinedKey {
fn from(keypair: libp2p_ed25519::Keypair) -> Self {
let ed_keypair = ed25519::SigningKey::from_bytes(
&(keypair.to_bytes()[..32])
.try_into()
.expect("libp2p key must be valid"),
);
CombinedKey::from(ed_keypair)
}
}

impl EnrKey for CombinedKey {
type PublicKey = CombinedPublicKey;

Expand Down Expand Up @@ -166,4 +209,27 @@ impl EnrPublicKey for CombinedPublicKey {
Self::Ed25519(key) => key.enr_key(),
}
}

/// Converts the publickey into a peer id, without consuming the key.
///
/// This is only available with the `libp2p` feature flag.
#[cfg(feature = "libp2p")]
fn as_peer_id(&self) -> PeerId {
match self {
Self::Secp256k1(pk) => {
let pk_bytes = pk.to_sec1_bytes();
let libp2p_pk: PublicKey = libp2p_secp256k1::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
Self::Ed25519(pk) => {
let pk_bytes = pk.to_bytes();
let libp2p_pk: PublicKey = libp2p_ed25519::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
}
}
}
15 changes: 15 additions & 0 deletions src/keys/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ use bytes::Bytes;
use rlp::DecoderError;
use std::{collections::BTreeMap, convert::TryFrom};

#[cfg(feature = "libp2p")]
use libp2p_identity::{ed25519 as libp2p_ed25519, PeerId, PublicKey};

/// The ENR key that stores the public key in the ENR record.
pub const ENR_KEY: &str = "ed25519";

Expand Down Expand Up @@ -72,4 +75,16 @@ impl EnrPublicKey for ed25519::VerifyingKey {
fn enr_key(&self) -> Key {
ENR_KEY.into()
}

/// Converts the publickey into a peer id, without consuming the key.
///
/// This is only available with the `libp2p` feature flag.
#[cfg(feature = "libp2p")]
fn as_peer_id(&self) -> PeerId {
let pk_bytes = self.to_bytes();
let libp2p_pk: PublicKey = libp2p_ed25519::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
}
12 changes: 12 additions & 0 deletions src/keys/k256_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ use rlp::DecoderError;
use sha3::{Digest, Keccak256};
use std::{collections::BTreeMap, convert::TryFrom};

#[cfg(feature = "libp2p")]
use libp2p_identity::{secp256k1, PeerId, PublicKey};

/// The ENR key that stores the public key in the ENR record.
pub const ENR_KEY: &str = "secp256k1";

Expand Down Expand Up @@ -100,6 +103,15 @@ impl EnrPublicKey for VerifyingKey {
coords
}

#[cfg(feature = "libp2p")]
fn as_peer_id(&self) -> PeerId {
let pk_bytes = self.to_sec1_bytes();
let libp2p_pk: PublicKey = secp256k1::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}

fn enr_key(&self) -> Key {
ENR_KEY.into()
}
Expand Down
8 changes: 8 additions & 0 deletions src/keys/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ pub use combined::{CombinedKey, CombinedPublicKey};
pub use ed25519_dalek;
#[cfg(feature = "k256")]
pub use k256;
#[cfg(feature = "libp2p")]
use libp2p_identity::PeerId;
#[cfg(feature = "rust-secp256k1")]
pub use secp256k1;

Expand Down Expand Up @@ -75,6 +77,12 @@ pub trait EnrPublicKey: Clone + Debug + Send + Sync + Unpin + 'static {
/// Returns the ENR key identifier for the public key type. For `secp256k1` keys this
/// is `secp256k1`.
fn enr_key(&self) -> Key;

/// Converts the `PublicKey` into a peer id, without consuming the key.
///
/// This is only available with the `libp2p` feature flag.
#[cfg(feature = "libp2p")]
fn as_peer_id(&self) -> PeerId;
}

/// An error during signing of a message.
Expand Down
12 changes: 12 additions & 0 deletions src/keys/rust_secp256k1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use rlp::DecoderError;
use secp256k1::SECP256K1;
use std::collections::BTreeMap;

#[cfg(feature = "libp2p")]
use libp2p_identity::{secp256k1 as libp2p_secp256k1, PeerId, PublicKey};

#[cfg(test)]
use self::MockOsRng as OsRng;
#[cfg(not(test))]
Expand Down Expand Up @@ -81,6 +84,15 @@ impl EnrPublicKey for secp256k1::PublicKey {
fn enr_key(&self) -> Key {
ENR_KEY.into()
}

#[cfg(feature = "libp2p")]
fn as_peer_id(&self) -> PeerId {
let pk_bytes = self.serialize();
let libp2p_pk: PublicKey = libp2p_secp256k1::PublicKey::try_from_bytes(&pk_bytes)
.expect("valid public key")
.into();
PeerId::from_public_key(&libp2p_pk)
}
}

#[cfg(test)]
Expand Down
Loading
Loading