From a323fedb2feb7b5dce1faba768f456d130f363fa Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 27 Sep 2023 13:15:09 +1000 Subject: [PATCH 01/12] Add extension features --- .github/workflows/build.yml | 38 ++-- Cargo.toml | 4 + src/keys/combined.rs | 28 +++ src/keys/ed25519.rs | 15 ++ src/keys/k256_key.rs | 12 ++ src/keys/mod.rs | 8 + src/keys/rust_secp256k1.rs | 12 ++ src/lib.rs | 391 +++++++++++++++++++++++++++++------- 8 files changed, 411 insertions(+), 97 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 22c7b36..fd6f856 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: pull_request: jobs: - cargo-fmt: + code-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -15,15 +15,9 @@ jobs: run: rustup update stable - name: Check formatting with cargofmt run: cargo fmt -- --check - cargo-clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable - name: Check for lint warnings run: cargo clippy --all-features -- -D warnings - release-tests-ubuntu: + release-tests-ubuntu runs-on: ubuntu-latest needs: cargo-fmt steps: @@ -32,24 +26,18 @@ jobs: run: rustup update stable - name: Run tests in release run: cargo test --all --release --tests - release-tests-ubuntu-all-features: - runs-on: ubuntu-latest - needs: cargo-fmt - steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable - - name: Run tests in release - run: cargo test --all --release --all-features - release-tests-ubuntu-ed25519: - runs-on: ubuntu-latest - needs: cargo-fmt - steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable - - name: Run tests in release + - name: Run tests in release ed25519 run: cargo test --all --release --features "ed25519" --tests + - name: Run tests in release secp256k1 + run: cargo test --all --release --features "secp256k1" --tests + - name: Run tests in release rust-secp256k1 + run: cargo test --all --release --features "rust-secp256k1" --tests + - name: Run tests in release libp2p + run: cargo test --all --release --features "libp2p" --tests + - name: Run tests in release libp2p, rust-secp256k1 + run: cargo test --all --release --features "libp2p,rust-secp256k1" --tests + - name: Run tests in release all features + run: cargo test --all --release --all-features cargo-audit: runs-on: ubuntu-latest needs: cargo-fmt diff --git a/Cargo.toml b/Cargo.toml index 2f2ecde..8145e47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,8 @@ 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.1", optional = true } +libp2p-identity = { version = "0.2.3", optional = true, features = ["peerid", "secp256k1", "ed25519"] } [dev-dependencies] secp256k1 = { features = ["rand-std"], version = "0.27" } @@ -35,6 +37,8 @@ serde_json = { version = "1.0.95" } default = ["serde", "k256"] ed25519 = ["ed25519-dalek"] rust-secp256k1 = ["secp256k1"] +quic = [] +libp2p = ["libp2p-core", "libp2p-identity"] [lib] name = "enr" diff --git a/src/keys/combined.rs b/src/keys/combined.rs index 275b6d5..12d61e9 100644 --- a/src/keys/combined.rs +++ b/src/keys/combined.rs @@ -12,6 +12,11 @@ use zeroize::Zeroize; use crate::Key; +#[cfg(feature = "libp2p")] +use libp2p_identity::{ + ed25519 as libp2p_ed25519, secp256k1 as libp2p_secp256k1, 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 { @@ -166,4 +171,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..ec5898c 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 6dd1e35..c50a484 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` //! @@ -209,6 +212,10 @@ 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; pub use keys::{EnrKey, EnrKeyUnambiguous, EnrPublicKey}; pub use node_id::NodeId; @@ -220,6 +227,20 @@ type PreviousRlpEncodedValues = Vec>; const MAX_ENR_SIZE: usize = 300; +// Constants used for fields +const ID_ENR_KEY: &str = "id"; +const ENR_VERSION: &str = "v4"; +pub const IP_ENR_KEY: &str = "ip"; +pub const IP6_ENR_KEY: &str = "ip6"; +pub const TCP_ENR_KEY: &str = "tcp"; +pub const TCP6_ENR_KEY: &str = "tcp6"; +pub const UDP_ENR_KEY: &str = "udp"; +pub const UDP6_ENR_KEY: &str = "udp6"; +#[cfg(feature = "quic")] +pub const QUIC_ENR_KEY: &str = "quic"; +#[cfg(feature = "quic")] +pub const QUIC6_ENR_KEY: &str = "quic6"; + /// 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. @@ -289,7 +310,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]; @@ -305,7 +326,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]; @@ -321,7 +342,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 @@ -330,25 +351,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. @@ -415,7 +436,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 == ENR_VERSION => { + pubkey.verify_v4(&self.rlp_content(), &self.signature) + } // unsupported identity schemes _ => false, } @@ -570,7 +593,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, EnrError> { - 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) @@ -578,7 +601,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, EnrError> { - 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) @@ -597,15 +620,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<(), EnrError> { 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 @@ -613,7 +636,7 @@ impl Enr { ), IpAddr::V6(addr) => ( self.content.insert( - "ip6".into(), + IP6_ENR_KEY.into(), rlp::encode(&(&addr.octets() as &[u8])).freeze(), ), self.content @@ -640,9 +663,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_bytes().as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_string, udp); @@ -652,9 +675,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_bytes().as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_v6_string, udp); @@ -710,29 +733,41 @@ 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.as_bytes() && value != ENR_VERSION.as_bytes() { *self = enr_backup; return Err(EnrError::UnsupportedIdentityScheme); } let value = rlp::encode(&(value)).freeze(); // Prevent inserting invalid RLP integers - if is_keyof_u16(key.as_ref()) { - rlp::decode::(&value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + 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 + if let Err(e) = self .seq .checked_add(1) - .ok_or(EnrError::SequenceNumberTooHigh)?; + .ok_or(EnrError::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()); @@ -785,7 +820,7 @@ impl Enr { fn sign(&mut self, key: &K) -> Result<(), EnrError> { self.signature = { match self.id() { - Some(ref id) if id == "v4" => key + Some(ref id) if id == ENR_VERSION => key .sign_v4(&self.rlp_content()) .map_err(|_| EnrError::SigningError)?, // other identity schemes are unsupported @@ -794,6 +829,179 @@ impl Enr { }; Ok(()) } + + // Libp2p features + /// The libp2p `PeerId` for the record. + #[cfg(feature = "libp2p")] + 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")] + 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")] + 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")] + 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")] + 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")] + 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")] + 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"))] + 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")] + pub fn quic4(&self) -> Option { + self.get_decodable(QUIC_ENR_KEY).and_then(Result::ok) + } + /// Returns the quic6 port if one is set. + #[cfg(feature = "quic")] + pub fn quic6(&self) -> Option { + self.get_decodable(QUIC6_ENR_KEY).and_then(Result::ok) + } } // traits // @@ -847,9 +1055,17 @@ 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, + ] + .iter() + .any(|k| k.as_bytes() == key.as_slice()) }) .map(|(key, val)| (String::from_utf8_lossy(key), hex::encode(val))), ) @@ -858,7 +1074,7 @@ impl std::fmt::Debug for Enr { } f.debug_struct("Enr") - .field("id", &self.id()) + .field(ID_ENR_KEY, &self.id()) .field("seq", &self.seq()) .field("NodeId", &self.node_id()) .field("signature", &hex::encode(&self.signature)) @@ -969,14 +1185,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")); } @@ -1039,41 +1253,42 @@ 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<(), EnrError> { - match key { - b"tcp" | b"tcp6" | b"udp" | b"udp6" => { - rlp::decode::(value).map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - } - b"id" => { - let id_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if id_bytes != b"v4" { - return Err(EnrError::UnsupportedIdentityScheme); + match std::str::from_utf8(key) { + Err(_) => return Ok(()), // Ignore if the key is not a string format + Ok(key_string) => match key_string { + TCP_ENR_KEY | TCP6_ENR_KEY | UDP_ENR_KEY | UDP6_ENR_KEY => { + rlp::decode::(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; } - } - b"ip" => { - let ip4_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if ip4_bytes.len() != 4 { - return Err(EnrError::InvalidRlpData("Invalid Ipv4 size".to_string())); + ID_ENR_KEY => { + let id_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if id_bytes != b"v4" { + return Err(EnrError::UnsupportedIdentityScheme); + } } - } - b"ip6" => { - let ip6_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if ip6_bytes.len() != 16 { - return Err(EnrError::InvalidRlpData("Invalid Ipv6 size".to_string())); + IP_ENR_KEY => { + let ip4_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if ip4_bytes.len() != 4 { + return Err(EnrError::InvalidRlpData("Invalid Ipv4 size".to_string())); + } } - } - b"secp256k1" => { - rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - } - _ => return Ok(()), + IP6_ENR_KEY => { + let ip6_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if ip6_bytes.len() != 16 { + return Err(EnrError::InvalidRlpData("Invalid Ipv6 size".to_string())); + } + } + #[cfg(feature = "quic")] + QUIC_ENR_KEY | QUIC6_ENR_KEY => { + rlp::decode::(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + } + _ => return Ok(()), + }, }; Ok(()) } @@ -1106,6 +1321,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()); } @@ -1133,6 +1354,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()); } @@ -1161,7 +1387,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()); } @@ -1182,6 +1412,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()); } @@ -1283,6 +1518,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"); @@ -1387,6 +1628,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()); } @@ -1548,6 +1794,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] @@ -1648,6 +1896,7 @@ mod tests { for tcp in LOW_INT_PORTS { let mut enr = EnrBuilder::new("v4").build(&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"); @@ -1665,6 +1914,7 @@ mod tests { for tcp in LOW_INT_PORTS { let mut enr = EnrBuilder::new("v4").build(&key).unwrap(); + println!("Inserting: {}", tcp); let res = enr.remove_insert( vec![b"none"].iter(), vec![(b"tcp".as_slice(), tcp.to_be_bytes().as_slice())].into_iter(), @@ -1691,10 +1941,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); } From 94e883dc691b5b487626f3eb7ac7257fab4801aa Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 27 Sep 2023 13:17:54 +1000 Subject: [PATCH 02/12] Correct workflow yml --- .github/workflows/build.yml | 51 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd6f856..03d2583 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,45 +10,44 @@ jobs: code-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable + - uses: actions/checkout@v3 + - name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-target: release + components: rustfmt, clippy + bins: cargo-audit - name: Check formatting with cargofmt run: cargo fmt -- --check - name: Check for lint warnings run: cargo clippy --all-features -- -D warnings - release-tests-ubuntu + - name: Run cargo audit to identify known security vulnerabilities reported to the RustSec Advisory Database + run: cargo audit + release-tests-ubuntu: runs-on: ubuntu-latest - needs: cargo-fmt steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable + - uses: actions/checkout@v3 + - name: Get latest version of stable Rust + uses: moonrepo/setup-rust@v1 + with: + channel: stable + cache-target: release + bins: cargo-nextest - name: Run tests in release - run: cargo test --all --release --tests + run: cargo nextest run --all --release --tests - name: Run tests in release ed25519 - run: cargo test --all --release --features "ed25519" --tests + run: cargo nextest run --all --release --features "ed25519" --tests - name: Run tests in release secp256k1 - run: cargo test --all --release --features "secp256k1" --tests + run: cargo nextest run --all --release --features "secp256k1" --tests - name: Run tests in release rust-secp256k1 - run: cargo test --all --release --features "rust-secp256k1" --tests + run: cargo nextest run --all --release --features "rust-secp256k1" --tests - name: Run tests in release libp2p - run: cargo test --all --release --features "libp2p" --tests + run: cargo nextest run --all --release --features "libp2p" --tests - name: Run tests in release libp2p, rust-secp256k1 - run: cargo test --all --release --features "libp2p,rust-secp256k1" --tests + run: cargo nextest run --all --release --features "libp2p,rust-secp256k1" --tests - name: Run tests in release all features - run: cargo test --all --release --all-features - cargo-audit: - runs-on: ubuntu-latest - needs: cargo-fmt - steps: - - uses: actions/checkout@v2 - - name: Get latest version of stable rust - run: rustup update stable - - name: Get latest cargo audit - run: cargo install --force cargo-audit - - name: Run cargo audit to identify known security vulnerabilities reported to the RustSec Advisory Database - run: cargo audit + run: cargo nextest run --all --release --all-features check-rustdoc-links: name: Check rustdoc intra-doc links runs-on: ubuntu-latest From 2004a0ba615fafa0e211898ca87a17130a361418 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 27 Sep 2023 13:27:41 +1000 Subject: [PATCH 03/12] Clippy --- .github/workflows/build.yml | 2 +- src/keys/mod.rs | 2 +- src/lib.rs | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03d2583..903ff4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,6 @@ jobs: container: image: rust steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Check rustdoc links run: RUSTDOCFLAGS="--deny broken_intra_doc_links" cargo doc --verbose --workspace --no-deps --document-private-items diff --git a/src/keys/mod.rs b/src/keys/mod.rs index ec5898c..07d47fa 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -78,7 +78,7 @@ pub trait EnrPublicKey: Clone + Debug + Send + Sync + Unpin + 'static { /// is `secp256k1`. fn enr_key(&self) -> Key; - /// Converts the PublicKey into a peer id, without consuming the key. + /// Converts the `PublicKey` into a peer id, without consuming the key. /// /// This is only available with the `libp2p` feature flag. #[cfg(feature = "libp2p")] diff --git a/src/lib.rs b/src/lib.rs index c50a484..fcd2f12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -833,6 +833,7 @@ impl Enr { // 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() } @@ -840,6 +841,7 @@ impl Enr { /// 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() { @@ -888,6 +890,7 @@ impl Enr { /// 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() @@ -901,6 +904,7 @@ impl Enr { /// 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() @@ -914,6 +918,7 @@ impl Enr { /// 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() @@ -928,6 +933,7 @@ impl Enr { /// 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() { @@ -949,6 +955,7 @@ impl Enr { /// 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() { @@ -970,6 +977,7 @@ impl Enr { /// 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() { @@ -994,11 +1002,13 @@ impl Enr { /// 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) } /// 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) } From 233146b79011804b7758c9d8427d796662f71147 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 27 Sep 2023 17:47:29 +1000 Subject: [PATCH 04/12] Add eth2 feature --- .github/workflows/build.yml | 2 ++ Cargo.toml | 3 +++ src/lib.rs | 45 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 903ff4a..55ba945 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,8 @@ jobs: 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 8145e47..1e2093f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,8 @@ secp256k1 = { version = "0.27", optional = true, default-features = false, featu ] } libp2p-core = { version = "0.40.1", optional = true } libp2p-identity = { version = "0.2.3", 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" } @@ -39,6 +41,7 @@ ed25519 = ["ed25519-dalek"] rust-secp256k1 = ["secp256k1"] quic = [] libp2p = ["libp2p-core", "libp2p-identity"] +eth2 = ["ssz_types", "ethereum_ssz"] [lib] name = "enr" diff --git a/src/lib.rs b/src/lib.rs index fcd2f12..a8e1e89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -216,6 +216,10 @@ pub use keys::{ed25519_dalek, CombinedKey, CombinedPublicKey}; 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; @@ -240,6 +244,15 @@ pub const UDP6_ENR_KEY: &str = "udp6"; pub const QUIC_ENR_KEY: &str = "quic"; #[cfg(feature = "quic")] pub const QUIC6_ENR_KEY: &str = "quic6"; +/// The ENR field specifying the fork id. +#[cfg(feature = "eth2")] +pub const ETH2_ENR_KEY: &str = "eth2"; +/// The ENR field specifying the attestation subnet bitfield. +#[cfg(feature = "eth2")] +pub const ATTESTATION_BITFIELD_ENR_KEY: &str = "attnets"; +/// The ENR field specifying the sync committee subnet bitfield. +#[cfg(feature = "eth2")] +pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &str = "syncnets"; /// The ENR, allowing for arbitrary signing algorithms. /// @@ -1012,6 +1025,38 @@ impl Enr { pub fn quic6(&self) -> Option { self.get_decodable(QUIC6_ENR_KEY).and_then(Result::ok) } + + /// The attestation subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn attestation_bitfield(&self) -> Result, &'static str> { + let bitfield_bytes = self + .get(ATTESTATION_BITFIELD_ENR_KEY) + .ok_or("ENR attestation bitfield non-existent")?; + + BitVector::::from_ssz_bytes(bitfield_bytes) + .map_err(|_| "Could not decode the ENR attnets bitfield") + } + + /// The sync committee subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn sync_committee_bitfield( + &self, + ) -> Result, &'static str> { + let bitfield_bytes = self + .get(SYNC_COMMITTEE_BITFIELD_ENR_KEY) + .ok_or("ENR sync committee bitfield non-existent")?; + + BitVector::::from_ssz_bytes(bitfield_bytes) + .map_err(|_| "Could not decode the ENR syncnets bitfield") + } + + /// Returns the field that represents an `ENRForkId`. Users must make the type conversion externally. + #[cfg(feature = "eth2")] + pub fn eth2(&self) -> Result, &'static str> { + self.get(ETH2_ENR_KEY) + .map(<[u8]>::to_vec) + .ok_or("ENR has no eth2 field") + } } // traits // From 1c650c271e3e031874c29ed40de2669538c4a165 Mon Sep 17 00:00:00 2001 From: Diva M Date: Wed, 27 Sep 2023 23:10:02 -0500 Subject: [PATCH 05/12] use bytes --- src/lib.rs | 102 +++++++++++++++++++++++++---------------------------- 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a8e1e89..c1a6ad4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -232,27 +232,27 @@ type PreviousRlpEncodedValues = Vec>; const MAX_ENR_SIZE: usize = 300; // Constants used for fields -const ID_ENR_KEY: &str = "id"; -const ENR_VERSION: &str = "v4"; -pub const IP_ENR_KEY: &str = "ip"; -pub const IP6_ENR_KEY: &str = "ip6"; -pub const TCP_ENR_KEY: &str = "tcp"; -pub const TCP6_ENR_KEY: &str = "tcp6"; -pub const UDP_ENR_KEY: &str = "udp"; -pub const UDP6_ENR_KEY: &str = "udp6"; +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: &str = "quic"; +pub const QUIC_ENR_KEY: &[u8] = b"quic"; #[cfg(feature = "quic")] -pub const QUIC6_ENR_KEY: &str = "quic6"; +pub const QUIC6_ENR_KEY: &[u8] = b"quic6"; /// The ENR field specifying the fork id. #[cfg(feature = "eth2")] -pub const ETH2_ENR_KEY: &str = "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: &str = "attnets"; +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: &str = "syncnets"; +pub const SYNC_COMMITTEE_BITFIELD_ENR_KEY: &[u8] = b"syncnets"; /// The ENR, allowing for arbitrary signing algorithms. /// @@ -449,7 +449,7 @@ impl Enr { pub fn verify(&self) -> bool { let pubkey = self.public_key(); match self.id() { - Some(ref id) if id == ENR_VERSION => { + Some(ref id) if id.as_bytes() == ENR_VERSION => { pubkey.verify_v4(&self.rlp_content(), &self.signature) } // unsupported identity schemes @@ -678,7 +678,7 @@ impl Enr { if let Some(ip) = prev_ip { self.content.insert(IP_ENR_KEY.into(), ip); } else { - self.content.remove(IP_ENR_KEY.as_bytes().as_ref()); + self.content.remove(IP_ENR_KEY.as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_string, udp); @@ -690,7 +690,7 @@ impl Enr { if let Some(ip) = prev_ip { self.content.insert(IP_ENR_KEY.into(), ip); } else { - self.content.remove(IP6_ENR_KEY.as_bytes().as_ref()); + self.content.remove(IP6_ENR_KEY.as_ref()); } if let Some(udp) = prev_port { self.content.insert(port_v6_string, udp); @@ -746,7 +746,7 @@ impl Enr { let mut inserted = Vec::new(); for (key, value) in insert_key_values { // currently only support "v4" identity schemes - if key.as_ref() == ID_ENR_KEY.as_bytes() && value != ENR_VERSION.as_bytes() { + if key.as_ref() == ID_ENR_KEY && value != ENR_VERSION { *self = enr_backup; return Err(EnrError::UnsupportedIdentityScheme); } @@ -833,7 +833,7 @@ impl Enr { fn sign(&mut self, key: &K) -> Result<(), EnrError> { self.signature = { match self.id() { - Some(ref id) if id == ENR_VERSION => key + Some(ref id) if id.as_bytes() == ENR_VERSION => key .sign_v4(&self.rlp_content()) .map_err(|_| EnrError::SigningError)?, // other identity schemes are unsupported @@ -1119,8 +1119,7 @@ impl std::fmt::Debug for Enr { TCP_ENR_KEY, TCP6_ENR_KEY, ] - .iter() - .any(|k| k.as_bytes() == key.as_slice()) + .contains(&key.as_slice()) }) .map(|(key, val)| (String::from_utf8_lossy(key), hex::encode(val))), ) @@ -1129,7 +1128,7 @@ impl std::fmt::Debug for Enr { } f.debug_struct("Enr") - .field(ID_ENR_KEY, &self.id()) + .field("id", &self.id()) .field("seq", &self.seq()) .field("NodeId", &self.node_id()) .field("signature", &hex::encode(&self.signature)) @@ -1309,42 +1308,37 @@ pub(crate) fn digest(b: &[u8]) -> [u8; 32] { } fn check_spec_reserved_keys(key: &[u8], value: &[u8]) -> Result<(), EnrError> { - match std::str::from_utf8(key) { - Err(_) => return Ok(()), // Ignore if the key is not a string format - Ok(key_string) => match key_string { - TCP_ENR_KEY | TCP6_ENR_KEY | UDP_ENR_KEY | UDP6_ENR_KEY => { - rlp::decode::(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - } - ID_ENR_KEY => { - let id_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if id_bytes != b"v4" { - return Err(EnrError::UnsupportedIdentityScheme); - } - } - IP_ENR_KEY => { - let ip4_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if ip4_bytes.len() != 4 { - return Err(EnrError::InvalidRlpData("Invalid Ipv4 size".to_string())); - } + match key { + TCP_ENR_KEY | TCP6_ENR_KEY | UDP_ENR_KEY | UDP6_ENR_KEY => { + rlp::decode::(value).map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + } + ID_ENR_KEY => { + let id_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if id_bytes != b"v4" { + return Err(EnrError::UnsupportedIdentityScheme); } - IP6_ENR_KEY => { - let ip6_bytes = rlp::decode::>(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; - if ip6_bytes.len() != 16 { - return Err(EnrError::InvalidRlpData("Invalid Ipv6 size".to_string())); - } + } + IP_ENR_KEY => { + let ip4_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if ip4_bytes.len() != 4 { + return Err(EnrError::InvalidRlpData("Invalid Ipv4 size".to_string())); } - #[cfg(feature = "quic")] - QUIC_ENR_KEY | QUIC6_ENR_KEY => { - rlp::decode::(value) - .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + } + IP6_ENR_KEY => { + let ip6_bytes = rlp::decode::>(value) + .map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + if ip6_bytes.len() != 16 { + return Err(EnrError::InvalidRlpData("Invalid Ipv6 size".to_string())); } - _ => return Ok(()), - }, - }; + } + #[cfg(feature = "quic")] + QUIC_ENR_KEY | QUIC6_ENR_KEY => { + rlp::decode::(value).map_err(|err| EnrError::InvalidRlpData(err.to_string()))?; + } + _ => return Ok(()), + } Ok(()) } From 3c4947beb895bfe9af3b9f828a74c3b583198c65 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 9 Oct 2023 15:50:36 +1100 Subject: [PATCH 06/12] Modifying builder --- src/builder.rs | 98 +++++++++++++++++++++++++++----------------------- src/lib.rs | 20 +++++------ 2 files changed, 64 insertions(+), 54 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 0ec0b3d..00da026 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,4 +1,13 @@ use crate::{Enr, EnrError, EnrKey, EnrPublicKey, Key, NodeId, MAX_ENR_SIZE}; +#[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 bytes::{Bytes, BytesMut}; use rlp::{Encodable, RlpStream}; use std::{ @@ -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:ident, $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 EnrBuilder { /// The identity scheme used to build the ENR record. - id: String, + id: Vec, /// The starting sequence number for the ENR record. seq: u64, @@ -27,9 +48,9 @@ impl EnrBuilder { /// Constructs a minimal `EnrBuilder` providing only a sequence number. /// Currently only supports the id v4 scheme and therefore disallows creation of any other /// scheme. - pub fn new(id: impl Into) -> Self { + pub fn new() -> Self { Self { - id: id.into(), + id: ENR_VERSION.into(), seq: 1, content: BTreeMap::new(), phantom: PhantomData, @@ -63,48 +84,42 @@ impl EnrBuilder { /// 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.add_value(IP6_ENR_KEY, &ip.octets().as_ref()); self } - */ - /// Adds a `tcp` field to the `ENRBuilder`. - pub fn tcp4(&mut self, tcp: u16) -> &mut Self { - self.add_value("tcp", &tcp); + 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")] + /// Adds an 'attestation_bitfield` field to the `ENRBuilder`. + pub fn attestation_bitfield(&mut self, attestation_bitfield: BitVector) -> &mut Self { + self.add_value( + ATTESTATION_BITFIELD_ENR_KEY, + attestation_bitfield.to_bytes(), + ); self } - - /// Adds a `tcp6` field to the `ENRBuilder`. - pub fn tcp6(&mut self, tcp: u16) -> &mut Self { - self.add_value("tcp6", &tcp); - 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); + #[cfg(feature = "eth2")] + /// Adds an 'attestation_bitfield` field to the `ENRBuilder`. + pub fn sync_committee_bitfield(&mut self, sync_committee_bitfield: BitVector) -> &mut Self { + self.add_value( + SYNC_COMMITTEE_BITFIELD_ENR_KEY, + sync_committee_bitfield.to_bytes(), + ); self } @@ -121,10 +136,10 @@ impl EnrBuilder { 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, EnrError> { - match self.id.as_str() { - "v4" => key + match self.id.as_slice() { + ENR_VERSION => key .sign_v4(&self.rlp_content()) .map_err(|_| EnrError::SigningError), // unsupported identity schemes @@ -142,11 +157,6 @@ impl EnrBuilder { /// # 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, EnrError> { - // add the identity scheme to the content - if self.id != "v4" { - return Err(EnrError::UnsupportedIdentityScheme); - } - // Sanitize all data, ensuring all RLP data is correctly formatted. for (key, value) in &self.content { if rlp::Rlp::new(value).data().is_err() { @@ -156,7 +166,7 @@ impl EnrBuilder { } } - 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/lib.rs b/src/lib.rs index c1a6ad4..0715790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1693,7 +1693,7 @@ mod tests { let tcp = 3000; let enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.ip(ip.into()); builder.tcp4(tcp); builder.build(&key).unwrap() @@ -1745,7 +1745,7 @@ mod tests { let tcp = 30303; let mut enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.ip(ip.into()); builder.tcp4(tcp); builder.build(&key).unwrap() @@ -1763,7 +1763,7 @@ mod tests { let ip = Ipv4Addr::new(10, 0, 0, 1); let mut enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.tcp4(tcp); builder.build(&key).unwrap() }; @@ -1787,7 +1787,7 @@ mod tests { let ip = Ipv4Addr::new(10, 0, 0, 1); let mut enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.ip(ip.into()); builder.tcp4(tcp); builder.udp4(udp); @@ -1860,7 +1860,7 @@ mod tests { topics.extend_from_slice(&s.out().freeze()); let mut enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.tcp4(tcp); builder.build(&key).unwrap() }; @@ -1905,7 +1905,7 @@ mod tests { for tcp in LOW_INT_PORTS { let enr = { - let mut builder = EnrBuilder::new("v4"); + let mut builder = EnrBuilder::new(); builder.tcp4(tcp); builder.build(&key).unwrap() }; @@ -1919,7 +1919,7 @@ mod tests { let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng()); for tcp in LOW_INT_PORTS { - let mut enr = EnrBuilder::new("v4").build(&key).unwrap(); + let mut enr = EnrBuilder::new().build(&key).unwrap(); enr.set_tcp4(tcp, &key).unwrap(); assert_tcp4(&enr, tcp); } @@ -1931,7 +1931,7 @@ mod tests { let ipv4 = Ipv4Addr::new(127, 0, 0, 1); for tcp in LOW_INT_PORTS { - let mut enr = EnrBuilder::new("v4").build(&key).unwrap(); + let mut enr = EnrBuilder::new().build(&key).unwrap(); enr.set_socket(SocketAddr::V4(SocketAddrV4::new(ipv4, tcp)), &key, true) .unwrap(); assert_tcp4(&enr, tcp); @@ -1943,7 +1943,7 @@ mod tests { let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng()); for tcp in LOW_INT_PORTS { - let mut enr = EnrBuilder::new("v4").build(&key).unwrap(); + let mut enr = EnrBuilder::new().build(&key).unwrap(); println!("Inserting: {}", tcp); let res = enr.insert(b"tcp", &tcp.to_be_bytes().as_ref(), &key); @@ -1961,7 +1961,7 @@ mod tests { let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng()); for tcp in LOW_INT_PORTS { - let mut enr = EnrBuilder::new("v4").build(&key).unwrap(); + let mut enr = EnrBuilder::new().build(&key).unwrap(); println!("Inserting: {}", tcp); let res = enr.remove_insert( From b1b98b20c4c2c6dcf604c3c996d550708cb5bf7e Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 9 Oct 2023 17:07:32 +1100 Subject: [PATCH 07/12] Add libp2p features --- src/builder.rs | 24 +++++++----------------- src/keys/combined.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 00da026..5eb97a5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -19,7 +19,7 @@ use std::{ // Generates function setters on the `EnrBuilder`. macro_rules! generate_setter { // Function name, variable type and key - ($name:ident, $type:ident, $key:ident) => { + ($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); @@ -105,23 +105,13 @@ impl EnrBuilder { #[cfg(feature = "eth2")] generate_setter!(eth2, &[u8], ETH2_ENR_KEY); #[cfg(feature = "eth2")] - /// Adds an 'attestation_bitfield` field to the `ENRBuilder`. - pub fn attestation_bitfield(&mut self, attestation_bitfield: BitVector) -> &mut Self { - self.add_value( - ATTESTATION_BITFIELD_ENR_KEY, - attestation_bitfield.to_bytes(), - ); - self - } + generate_setter!(attestation_bitfield, &[u8], ATTESTATION_BITFIELD_ENR_KEY); #[cfg(feature = "eth2")] - /// Adds an 'attestation_bitfield` field to the `ENRBuilder`. - pub fn sync_committee_bitfield(&mut self, sync_committee_bitfield: BitVector) -> &mut Self { - self.add_value( - SYNC_COMMITTEE_BITFIELD_ENR_KEY, - sync_committee_bitfield.to_bytes(), - ); - self - } + 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 { diff --git a/src/keys/combined.rs b/src/keys/combined.rs index 12d61e9..10f527a 100644 --- a/src/keys/combined.rs +++ b/src/keys/combined.rs @@ -7,6 +7,7 @@ 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; @@ -14,7 +15,7 @@ use crate::Key; #[cfg(feature = "libp2p")] use libp2p_identity::{ - ed25519 as libp2p_ed25519, secp256k1 as libp2p_secp256k1, PeerId, PublicKey, + 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 @@ -33,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; From e7b495c253b4fb2727484a881bf87e7dc8d9fcc6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Mon, 9 Oct 2023 17:11:23 +1100 Subject: [PATCH 08/12] Less restrictive public types --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e2093f..2182c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ 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.1", optional = true } -libp2p-identity = { version = "0.2.3", optional = true, features = ["peerid", "secp256k1", "ed25519"] } +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 } From af428fcc82938fac70862c296b2e2c512c15ed74 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Tue, 10 Oct 2023 10:46:59 +1100 Subject: [PATCH 09/12] Setters --- src/lib.rs | 78 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0715790..ed52dac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1019,6 +1019,17 @@ impl Enr { 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, EnrError> { + 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] @@ -1026,36 +1037,67 @@ impl Enr { 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 = "quic6")] + #[must_use] + pub fn set_quic6(&mut self, quic6: u16, key: &K) -> Result, EnrError> { + 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) -> Result, &'static str> { - let bitfield_bytes = self - .get(ATTESTATION_BITFIELD_ENR_KEY) - .ok_or("ENR attestation bitfield non-existent")?; + pub fn attestation_bitfield(&self) -> Option> { + self.get(ATTESTATION_BITFIELD_ENR_KEY) + } - BitVector::::from_ssz_bytes(bitfield_bytes) - .map_err(|_| "Could not decode the ENR attnets bitfield") + /// Sets the attestation subnet bitfield associated with the ENR. + #[cfg(feature = "eth2")] + pub fn set_attestation_bitfield( + &mut self, + bitfield: &[u8], + key: &K, + ) -> Result>, EnrError> { + 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, - ) -> Result, &'static str> { - let bitfield_bytes = self - .get(SYNC_COMMITTEE_BITFIELD_ENR_KEY) - .ok_or("ENR sync committee bitfield non-existent")?; + pub fn sync_committee_bitfield(&self) -> Option> { + self.get(SYNC_COMMITTEE_BITFIELD_ENR_KEY) + } - BitVector::::from_ssz_bytes(bitfield_bytes) - .map_err(|_| "Could not decode the ENR syncnets bitfield") + /// 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>, EnrError> { + 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) -> Result, &'static str> { - self.get(ETH2_ENR_KEY) - .map(<[u8]>::to_vec) - .ok_or("ENR has no eth2 field") + 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>, EnrError> { + if let Some(eth2_bytes) = self.insert(ETH2_ENR_KEY, bitfield, key)? { + return Ok(rlp::decode(ð2_bytes).ok()); + } + Ok(None) } } From 0f5aa705d2da1fe28e91d24c41ac7b079df00abd Mon Sep 17 00:00:00 2001 From: Diva M Date: Wed, 11 Oct 2023 23:01:46 -0500 Subject: [PATCH 10/12] remove unnecessary function --- src/builder.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 2883faf..3deeb14 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -57,13 +57,6 @@ impl Default for Builder { } impl Builder { - /// Constructs a minimal `EnrBuilder` providing only a sequence number. - /// Currently only supports the id v4 scheme and therefore disallows creation of any other - /// scheme. - pub fn new() -> Self { - Self::default() - } - /// Modifies the sequence number of the builder. pub fn seq(&mut self, seq: u64) -> &mut Self { self.seq = seq; From 925df564c584ba4126f7a573acee287f8e66cb8b Mon Sep 17 00:00:00 2001 From: Diva M Date: Wed, 11 Oct 2023 23:02:10 -0500 Subject: [PATCH 11/12] temp turning on all features for dev --- Cargo.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2182c21..5ade6e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,9 @@ 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 = [] From 484f9de2ccd5bb5b0c347839a01cb1a463dd829d Mon Sep 17 00:00:00 2001 From: Diva M Date: Sun, 15 Oct 2023 13:06:55 -0500 Subject: [PATCH 12/12] fix unintended feature --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7f0c8fa..0fd06bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1076,7 +1076,7 @@ impl Enr { } /// Sets the `quic6` field of the ENR. Returns any pre-existing quic6 port in the record. - #[cfg(feature = "quic6")] + #[cfg(feature = "quic")] #[must_use] pub fn set_quic6(&mut self, quic6: u16, key: &K) -> Result, EnrError> { if let Some(quic_bytes) = self.insert(QUIC6_ENR_KEY, &quic6, key)? {