diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e8afa62..55ba945 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/Cargo.toml b/Cargo.toml index b4ab00f..774061f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/builder.rs b/src/builder.rs index 033691b..f2a768c 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -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}; @@ -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 { /// The identity scheme used to build the ENR record. - id: String, + id: Vec, /// The starting sequence number for the ENR record. seq: u64, @@ -27,7 +48,7 @@ impl Default for Builder { /// 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, @@ -63,50 +84,34 @@ impl Builder { /// 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 { @@ -121,10 +126,10 @@ impl Builder { 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, 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 @@ -142,17 +147,12 @@ impl Builder { /// # 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, 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(); diff --git a/src/keys/combined.rs b/src/keys/combined.rs index 275b6d5..10f527a 100644 --- a/src/keys/combined.rs +++ b/src/keys/combined.rs @@ -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 { @@ -28,11 +34,48 @@ impl From for CombinedKey { } impl From for CombinedKey { - fn from(keypair: ed25519_dalek::SigningKey) -> Self { + fn from(keypair: ed25519::SigningKey) -> Self { Self::Ed25519(keypair) } } +#[cfg(feature = "libp2p")] +impl TryFrom for CombinedKey { + type Error = &'static str; + + fn try_from(keypair: libp2p_identity::Keypair) -> Result { + 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 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 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; @@ -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) + } + } + } } diff --git a/src/keys/ed25519.rs b/src/keys/ed25519.rs index 8f89b8c..6eed605 100644 --- a/src/keys/ed25519.rs +++ b/src/keys/ed25519.rs @@ -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"; @@ -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) + } } diff --git a/src/keys/k256_key.rs b/src/keys/k256_key.rs index dbfa3e4..688dc55 100644 --- a/src/keys/k256_key.rs +++ b/src/keys/k256_key.rs @@ -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"; @@ -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() } diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 6d3b986..07d47fa 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -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; @@ -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. diff --git a/src/keys/rust_secp256k1.rs b/src/keys/rust_secp256k1.rs index 93cff1a..673d2b5 100644 --- a/src/keys/rust_secp256k1.rs +++ b/src/keys/rust_secp256k1.rs @@ -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))] @@ -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)] diff --git a/src/lib.rs b/src/lib.rs index d0648af..2a41e54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ //! - `ed25519`: Provides support for `ed25519_dalek` keypair types. //! - `k256`: Uses `k256` for secp256k1 keys. //! - `rust-secp256k1`: Uses `rust-secp256k1` for secp256k1 keys. +//! - `libp2p`: Adds libp2p functionality like peer-id from an ENR. +//! - `quic`: Adds extra fields that support the QUIC transport. +//! - `eth2`: Adds extra fields that support the Ethereum consensus layer. //! //! These can be enabled via adding the feature flag in your `Cargo.toml` //! @@ -206,6 +209,14 @@ pub use keys::k256; pub use keys::secp256k1; #[cfg(all(feature = "ed25519", feature = "k256"))] pub use keys::{ed25519_dalek, CombinedKey, CombinedPublicKey}; +#[cfg(feature = "libp2p")] +use libp2p_core::multiaddr::{Multiaddr, Protocol}; +#[cfg(feature = "libp2p")] +use libp2p_identity::PeerId; +#[cfg(feature = "eth2")] +use ssz::Decode; +#[cfg(feature = "eth2")] +use ssz_types::{typenum::Unsigned, BitVector}; pub use keys::{EnrKey, EnrKeyUnambiguous, EnrPublicKey}; pub use node_id::NodeId; @@ -217,6 +228,29 @@ type PreviousRlpEncodedValues = Vec>; const MAX_ENR_SIZE: usize = 300; +// Constants used for fields +const ID_ENR_KEY: &[u8] = b"id"; +const ENR_VERSION: &[u8] = b"v4"; +pub const IP_ENR_KEY: &[u8] = b"ip"; +pub const IP6_ENR_KEY: &[u8] = b"ip6"; +pub const TCP_ENR_KEY: &[u8] = b"tcp"; +pub const TCP6_ENR_KEY: &[u8] = b"tcp6"; +pub const UDP_ENR_KEY: &[u8] = b"udp"; +pub const UDP6_ENR_KEY: &[u8] = b"udp6"; +#[cfg(feature = "quic")] +pub const QUIC_ENR_KEY: &[u8] = b"quic"; +#[cfg(feature = "quic")] +pub const QUIC6_ENR_KEY: &[u8] = b"quic6"; +/// The ENR field specifying the fork id. +#[cfg(feature = "eth2")] +pub const ETH2_ENR_KEY: &[u8] = b"eth2"; +/// The ENR field specifying the attestation subnet bitfield. +#[cfg(feature = "eth2")] +pub const ATTESTATION_BITFIELD_ENR_KEY: &[u8] = b"attnets"; +/// The ENR field specifying the sync committee subnet bitfield. +#[cfg(feature = "eth2")] +pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &[u8] = b"syncnets"; + /// The ENR, allowing for arbitrary signing algorithms. /// /// This struct will always have a valid signature, known public key type, sequence number and `NodeId`. All other parameters are variable/optional. @@ -298,7 +332,7 @@ impl Enr { /// Returns the IPv4 address of the ENR record if it is defined. #[must_use] pub fn ip4(&self) -> Option { - if let Some(ip_bytes) = self.get("ip") { + if let Some(ip_bytes) = self.get(IP_ENR_KEY) { return match ip_bytes.len() { 4 => { let mut ip = [0_u8; 4]; @@ -314,7 +348,7 @@ impl Enr { /// Returns the IPv6 address of the ENR record if it is defined. #[must_use] pub fn ip6(&self) -> Option { - if let Some(ip_bytes) = self.get("ip6") { + if let Some(ip_bytes) = self.get(IP6_ENR_KEY) { return match ip_bytes.len() { 16 => { let mut ip = [0_u8; 16]; @@ -330,7 +364,7 @@ impl Enr { /// The `id` of ENR record if it is defined. #[must_use] pub fn id(&self) -> Option { - if let Some(id_bytes) = self.get("id") { + if let Some(id_bytes) = self.get(ID_ENR_KEY) { return Some(String::from_utf8_lossy(id_bytes).to_string()); } None @@ -339,25 +373,25 @@ impl Enr { /// The TCP port of ENR record if it is defined. #[must_use] pub fn tcp4(&self) -> Option { - self.get_decodable("tcp").and_then(Result::ok) + self.get_decodable(TCP_ENR_KEY).and_then(Result::ok) } /// The IPv6-specific TCP port of ENR record if it is defined. #[must_use] pub fn tcp6(&self) -> Option { - self.get_decodable("tcp6").and_then(Result::ok) + self.get_decodable(TCP6_ENR_KEY).and_then(Result::ok) } /// The UDP port of ENR record if it is defined. #[must_use] pub fn udp4(&self) -> Option { - self.get_decodable("udp").and_then(Result::ok) + self.get_decodable(UDP_ENR_KEY).and_then(Result::ok) } /// The IPv6-specific UDP port of ENR record if it is defined. #[must_use] pub fn udp6(&self) -> Option { - self.get_decodable("udp6").and_then(Result::ok) + self.get_decodable(UDP6_ENR_KEY).and_then(Result::ok) } /// Provides a socket (based on the UDP port), if the IPv4 and UDP fields are specified. @@ -424,7 +458,9 @@ impl Enr { pub fn verify(&self) -> bool { let pubkey = self.public_key(); match self.id() { - Some(ref id) if id == "v4" => pubkey.verify_v4(&self.rlp_content(), &self.signature), + Some(ref id) if id.as_bytes() == ENR_VERSION => { + pubkey.verify_v4(&self.rlp_content(), &self.signature) + } // unsupported identity schemes _ => false, } @@ -594,7 +630,7 @@ impl Enr { /// Sets the `tcp` field of the ENR. Returns any pre-existing tcp port in the record. pub fn set_tcp4(&mut self, tcp: u16, key: &K) -> Result, Error> { - if let Some(tcp_bytes) = self.insert("tcp", &tcp, key)? { + if let Some(tcp_bytes) = self.insert(TCP_ENR_KEY, &tcp, key)? { return Ok(rlp::decode(&tcp_bytes).ok()); } Ok(None) @@ -602,7 +638,7 @@ impl Enr { /// Sets the `tcp6` field of the ENR. Returns any pre-existing tcp6 port in the record. pub fn set_tcp6(&mut self, tcp: u16, key: &K) -> Result, Error> { - if let Some(tcp_bytes) = self.insert("tcp6", &tcp, key)? { + if let Some(tcp_bytes) = self.insert(TCP6_ENR_KEY, &tcp, key)? { return Ok(rlp::decode(&tcp_bytes).ok()); } Ok(None) @@ -621,15 +657,15 @@ impl Enr { /// Helper function for `set_tcp_socket()` and `set_udp_socket`. fn set_socket(&mut self, socket: SocketAddr, key: &K, is_tcp: bool) -> Result<(), Error> { let (port_string, port_v6_string): (Key, Key) = if is_tcp { - ("tcp".into(), "tcp6".into()) + (TCP_ENR_KEY.into(), TCP6_ENR_KEY.into()) } else { - ("udp".into(), "udp6".into()) + (UDP_ENR_KEY.into(), UDP6_ENR_KEY.into()) }; let (prev_ip, prev_port) = match socket.ip() { IpAddr::V4(addr) => ( self.content.insert( - "ip".into(), + IP_ENR_KEY.into(), rlp::encode(&(&addr.octets() as &[u8])).freeze(), ), self.content @@ -637,7 +673,7 @@ impl Enr { ), IpAddr::V6(addr) => ( self.content.insert( - "ip6".into(), + IP6_ENR_KEY.into(), rlp::encode(&(&addr.octets() as &[u8])).freeze(), ), self.content @@ -664,9 +700,9 @@ impl Enr { match socket.ip() { IpAddr::V4(_) => { if let Some(ip) = prev_ip { - self.content.insert("ip".into(), ip); + self.content.insert(IP_ENR_KEY.into(), ip); } else { - self.content.remove(b"ip".as_ref()); + self.content.remove(IP_ENR_KEY.as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_string, udp); @@ -676,9 +712,9 @@ impl Enr { } IpAddr::V6(_) => { if let Some(ip) = prev_ip { - self.content.insert("ip6".into(), ip); + self.content.insert(IP_ENR_KEY.into(), ip); } else { - self.content.remove(b"ip6".as_ref()); + self.content.remove(IP6_ENR_KEY.as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_v6_string, udp); @@ -734,28 +770,37 @@ impl Enr { let mut inserted = Vec::new(); for (key, value) in insert_key_values { // currently only support "v4" identity schemes - if key.as_ref() == b"id" && value != b"v4" { + if key.as_ref() == ID_ENR_KEY && value != ENR_VERSION { *self = enr_backup; return Err(Error::UnsupportedIdentityScheme); } let value = rlp::encode(&(value)).freeze(); // Prevent inserting invalid RLP integers - if is_keyof_u16(key.as_ref()) { - rlp::decode::(&value)?; + if let Err(e) = check_spec_reserved_keys(key.as_ref(), &value) { + { + // Revert the ENR and return the error + *self = enr_backup; + return Err(e); + } } inserted.push(self.content.insert(key.as_ref().to_vec(), value)); } // increment the sequence number - self.seq = self - .seq - .checked_add(1) - .ok_or(Error::SequenceNumberTooHigh)?; + if let Err(e) = self.seq.checked_add(1).ok_or(Error::SequenceNumberTooHigh) { + // Revert the ENR and return the error + *self = enr_backup; + return Err(e); + } // sign the record - self.sign(enr_key)?; + if let Err(e) = self.sign(enr_key) { + // Revert the ENR and return the error + *self = enr_backup; + return Err(e); + } // update the node id self.node_id = NodeId::from(enr_key.public()); @@ -817,7 +862,7 @@ impl Enr { /// Compute the enr's signature with the given key. fn compute_signature(&self, signing_key: &K) -> Result, Error> { match self.id() { - Some(ref id) if id == "v4" => signing_key + Some(ref id) if id.as_bytes() == ENR_VERSION => signing_key .sign_v4(&self.rlp_content()) .map_err(|_| Error::SigningError), // other identity schemes are unsupported @@ -831,6 +876,263 @@ impl Enr { let new_signature = self.compute_signature(key)?; Ok(std::mem::replace(&mut self.signature, new_signature)) } + + // Libp2p features + /// The libp2p `PeerId` for the record. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn peer_id(&self) -> PeerId { + self.public_key().as_peer_id() + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and either a `tcp`, `quic` or `udp` key **or** an `ip6` and either a `tcp6` `quic6` or `udp6`. + /// The vector remains empty if these fields are not defined. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(udp) = self.udp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(udp)); + multiaddrs.push(multiaddr); + } + #[cfg(feature = "quic")] + if let Some(quic) = self.quic4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(quic)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(udp6) = self.udp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(udp6)); + multiaddrs.push(multiaddr); + } + + #[cfg(feature = "quic")] + if let Some(quic6) = self.quic6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(quic6)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs with the `PeerId` prepended. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr_p2p(&self) -> Vec { + let peer_id = self.peer_id(); + self.multiaddr() + .into_iter() + .map(|mut multiaddr| { + multiaddr.push(Protocol::P2p(peer_id)); + multiaddr + }) + .collect() + } + + /// Returns any multiaddrs that contain the TCP protocol with the `PeerId` prepended. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr_p2p_tcp(&self) -> Vec { + let peer_id = self.peer_id(); + self.multiaddr_tcp() + .into_iter() + .map(|mut multiaddr| { + multiaddr.push(Protocol::P2p(peer_id)); + multiaddr + }) + .collect() + } + + /// Returns any multiaddrs that contain the UDP protocol with the `PeerId` prepended. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr_p2p_udp(&self) -> Vec { + let peer_id = self.peer_id(); + self.multiaddr_udp() + .into_iter() + .map(|mut multiaddr| { + multiaddr.push(Protocol::P2p(peer_id)); + multiaddr + }) + .collect() + } + + /// Returns any multiaddrs that contain the TCP protocol. + /// Returns a list of multiaddrs if the ENR has an `ip` and a `tcp` key **or** an `ip6` and a `tcp6` field. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr_tcp(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(tcp) = self.tcp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Tcp(tcp)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(tcp6) = self.tcp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Tcp(tcp6)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and a `udp` key **or** an `ip6` and a `udp6` field. + #[cfg(feature = "libp2p")] + #[must_use] + pub fn multiaddr_udp(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(ip) = self.ip4() { + if let Some(udp) = self.udp4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(udp)); + multiaddrs.push(multiaddr); + } + } + if let Some(ip6) = self.ip6() { + if let Some(udp6) = self.udp6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(udp6)); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns a list of multiaddrs if the ENR has an `ip` and a `quic` key **or** an `ip6` and a `quic6`. + #[cfg(all(feature = "libp2p", feature = "quic"))] + #[must_use] + pub fn multiaddr_quic(&self) -> Vec { + let mut multiaddrs: Vec = Vec::new(); + if let Some(quic_port) = self.quic4() { + if let Some(ip) = self.ip4() { + let mut multiaddr: Multiaddr = ip.into(); + multiaddr.push(Protocol::Udp(quic_port)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + } + + if let Some(quic6_port) = self.quic6() { + if let Some(ip6) = self.ip6() { + let mut multiaddr: Multiaddr = ip6.into(); + multiaddr.push(Protocol::Udp(quic6_port)); + multiaddr.push(Protocol::QuicV1); + multiaddrs.push(multiaddr); + } + } + multiaddrs + } + + /// Returns the quic port if one is set. + #[cfg(feature = "quic")] + #[must_use] + pub fn quic4(&self) -> Option { + self.get_decodable(QUIC_ENR_KEY).and_then(Result::ok) + } + + /// Sets the `quic` field of the ENR. Returns any pre-existing quic port in the record. + #[cfg(feature = "quic")] + #[must_use] + pub fn set_quic4(&mut self, quic: u16, key: &K) -> Result, Error> { + if let Some(quic_bytes) = self.insert(QUIC_ENR_KEY, &quic, key)? { + return Ok(rlp::decode(&quic_bytes).ok()); + } + Ok(None) + } + + /// Returns the quic6 port if one is set. + #[cfg(feature = "quic")] + #[must_use] + pub fn quic6(&self) -> Option { + self.get_decodable(QUIC6_ENR_KEY).and_then(Result::ok) + } + + /// Sets the `quic6` field of the ENR. Returns any pre-existing quic6 port in the record. + #[cfg(feature = "quic")] + #[must_use] + pub fn set_quic6(&mut self, quic6: u16, key: &K) -> Result, Error> { + if let Some(quic_bytes) = self.insert(QUIC6_ENR_KEY, &quic6, key)? { + return Ok(rlp::decode(&quic_bytes).ok()); + } + Ok(None) + } + + /// The attestation subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn attestation_bitfield(&self) -> Option> { + self.get(ATTESTATION_BITFIELD_ENR_KEY) + } + + /// Sets the attestation subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn set_attestation_bitfield( + &mut self, + bitfield: &[u8], + key: &K, + ) -> Result>, Error> { + if let Some(bitfield_bytes) = self.insert(ATTESTATION_BITFIELD_ENR_KEY, bitfield, key)? { + return Ok(rlp::decode(&bitfield_bytes).ok()); + } + Ok(None) + } + + /// The sync committee subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn sync_committee_bitfield(&self) -> Option> { + self.get(SYNC_COMMITTEE_BITFIELD_ENR_KEY) + } + + /// Sets the sync committee bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn set_sync_committee_bitfield( + &mut self, + bitfield: &[u8], + key: &K, + ) -> Result>, Error> { + if let Some(bitfield_bytes) = self.insert(SYNC_COMMITTEE_BITFIELD_ENR_KEY, bitfield, key)? { + return Ok(rlp::decode(&bitfield_bytes).ok()); + } + Ok(None) + } + + /// Returns the field that represents an `ENRForkId`. Users must make the type conversion externally. + #[cfg(feature = "eth2")] + pub fn eth2(&self) -> Option> { + self.get(ETH2_ENR_KEY).map(<[u8]>::to_vec) + } + + /// Sets the eth2 field associated with the ENR. + #[cfg(feature = "eth2")] + pub fn set_eth2(&mut self, eth2: &[u8], key: &K) -> Result>, Error> { + if let Some(eth2_bytes) = self.insert(ETH2_ENR_KEY, bitfield, key)? { + return Ok(rlp::decode(ð2_bytes).ok()); + } + Ok(None) + } } // traits // @@ -884,9 +1186,16 @@ impl std::fmt::Debug for Enr { .iter() .filter(|(key, _)| { // skip all pairs already covered as fields - !["id", "ip", "ip6", "udp", "udp6", "tcp", "tcp6"] - .iter() - .any(|k| k.as_bytes() == key.as_slice()) + ![ + ID_ENR_KEY, + IP_ENR_KEY, + IP6_ENR_KEY, + UDP_ENR_KEY, + UDP6_ENR_KEY, + TCP_ENR_KEY, + TCP6_ENR_KEY, + ] + .contains(&key.as_slice()) }) .map(|(key, val)| (String::from_utf8_lossy(key), hex::encode(val))), ) @@ -999,14 +1308,12 @@ impl rlp::Decodable for Enr { .next() .ok_or(DecoderError::Custom("List not a multiple of 2"))?; - // Sanitize the data - if is_keyof_u16(key) { - item.as_val::()?; - } else { - item.data()?; - } let value = item.as_raw(); + // Sanitize the data + check_spec_reserved_keys(key, value) + .map_err(|_| DecoderError::Custom("Invalid data/encoding in reserved key."))?; + if prev.is_some() && prev >= Some(key) { return Err(DecoderError::Custom("Unsorted keys")); } @@ -1069,22 +1376,18 @@ pub(crate) fn digest(b: &[u8]) -> [u8; 32] { output } -const fn is_keyof_u16(key: &[u8]) -> bool { - matches!(key, b"tcp" | b"tcp6" | b"udp" | b"udp6") -} - fn check_spec_reserved_keys(key: &[u8], value: &[u8]) -> Result<(), Error> { match key { - b"tcp" | b"tcp6" | b"udp" | b"udp6" => { + TCP_ENR_KEY | TCP6_ENR_KEY | UDP_ENR_KEY | UDP6_ENR_KEY => { rlp::decode::(value)?; } - b"id" => { + ID_ENR_KEY => { let id_bytes = rlp::decode::>(value)?; if id_bytes != b"v4" { return Err(Error::UnsupportedIdentityScheme); } } - b"ip" => { + IP_ENR_KEY => { let ip4_bytes = rlp::decode::>(value)?; if ip4_bytes.len() != 4 { return Err(Error::InvalidRlpData(rlp::DecoderError::Custom( @@ -1092,7 +1395,7 @@ fn check_spec_reserved_keys(key: &[u8], value: &[u8]) -> Result<(), Error> { ))); } } - b"ip6" => { + IP6_ENR_KEY => { let ip6_bytes = rlp::decode::>(value)?; if ip6_bytes.len() != 16 { return Err(Error::InvalidRlpData(rlp::DecoderError::Custom( @@ -1100,8 +1403,12 @@ fn check_spec_reserved_keys(key: &[u8], value: &[u8]) -> Result<(), Error> { ))); } } + #[cfg(feature = "quic")] + QUIC_ENR_KEY | QUIC6_ENR_KEY => { + rlp::decode::(value)?; + } _ => return Ok(()), - }; + } Ok(()) } @@ -1133,6 +1440,12 @@ mod tests { assert_eq!(enr.tcp4(), None); assert_eq!(enr.signature(), &signature[..]); assert_eq!(pubkey.to_vec(), expected_pubkey); + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAmSH2XVgZqYHWucap5kuPzLnt2TsNQkoppVxB5eJGvaXwm").unwrap() + ); + assert!(enr.verify()); } @@ -1160,6 +1473,11 @@ mod tests { assert_eq!(enr.signature(), &signature[..]); assert_eq!(pubkey.to_vec(), expected_pubkey); assert_eq!(enr.node_id().raw().to_vec(), expected_node_id); + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAmSH2XVgZqYHWucap5kuPzLnt2TsNQkoppVxB5eJGvaXwm").unwrap() + ); assert!(enr.verify()); } @@ -1188,7 +1506,11 @@ mod tests { assert_eq!(enr.signature(), &signature[..]); assert_eq!(pubkey.to_vec(), expected_pubkey); assert_eq!(enr.node_id().raw().to_vec(), expected_node_id); - + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAmSH2XVgZqYHWucap5kuPzLnt2TsNQkoppVxB5eJGvaXwm").unwrap() + ); assert!(enr.verify()); } @@ -1209,6 +1531,11 @@ mod tests { assert_eq!(enr.seq(), 40); assert_eq!(enr.signature(), &signature[..]); assert_eq!(enr.public_key().encode().to_vec(), expected_pubkey); + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAkypNfuZjWngxLrod9Buxz3foropE3WYZe78ZFgGeHfapb").unwrap() + ); assert!(enr.verify()); } @@ -1310,6 +1637,12 @@ mod tests { assert_eq!(pubkey.to_vec(), expected_pubkey); assert!(enr.verify()); + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAmSH2XVgZqYHWucap5kuPzLnt2TsNQkoppVxB5eJGvaXwm").unwrap() + ); + let invalid_record = hex::decode(record_hex2).unwrap(); rlp::decode::(&invalid_record).expect_err("should reject extra data"); @@ -1409,6 +1742,11 @@ mod tests { assert_eq!(enr_base64, expected_enr_base64); let enr = enr_base64.parse::>().unwrap(); + #[cfg(feature = "libp2p")] + assert_eq!( + enr.peer_id(), + PeerId::from_str("16Uiu2HAmSH2XVgZqYHWucap5kuPzLnt2TsNQkoppVxB5eJGvaXwm").unwrap() + ); assert!(enr.verify()); } @@ -1542,6 +1880,8 @@ mod tests { .parse() .expect("Can decode both secp"); let _decoded_enr: Enr = base64_string_ed25519.parse().unwrap(); + #[cfg(feature = "libp2p")] + _decoded_enr.peer_id(); // Check that the peer-id can be decoded } #[test] @@ -1630,6 +1970,7 @@ mod tests { for tcp in LOW_INT_PORTS { let mut enr = Enr::empty(&key).unwrap(); + println!("Inserting: {}", tcp); let res = enr.insert(b"tcp", &tcp.to_be_bytes().as_ref(), &key); if u8::try_from(tcp).is_ok() { assert_eq!(res.unwrap_err().to_string(), "invalid rlp data"); @@ -1647,6 +1988,7 @@ mod tests { for tcp in LOW_INT_PORTS { let mut enr = Enr::empty(&key).unwrap(); + println!("Inserting: {}", tcp); let res = enr.remove_insert( [b"none"].iter(), vec![(b"tcp".as_slice(), tcp.to_be_bytes().as_slice())].into_iter(), @@ -1673,10 +2015,7 @@ mod tests { for (tcp, enr_str) in vectors { let res = DefaultEnr::from_str(enr_str); if u8::try_from(tcp).is_ok() { - assert_eq!( - res.unwrap_err().to_string(), - "Invalid ENR: RlpInvalidIndirection" - ); + assert!(res.is_err()); // Should fail trying to input low integers } else { assert_tcp4(&res.unwrap(), tcp); }