#![warn(clippy::all, rustdoc::all)]
#![allow(
clippy::map_err_ignore,
clippy::missing_errors_doc,
clippy::module_name_repetitions,
clippy::option_if_let_else
)]
mod builder;
mod error;
mod keys;
mod node_id;
use alloy_rlp::{Decodable, Encodable, Error as DecoderError, Header};
use bytes::{Buf, Bytes, BytesMut};
use std::{
collections::BTreeMap,
hash::{Hash, Hasher},
net::{SocketAddrV4, SocketAddrV6},
};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
#[cfg(feature = "serde")]
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
use sha3::{Digest, Keccak256};
use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
str::FromStr,
};
pub use error::Error;
#[cfg(feature = "k256")]
pub use keys::k256;
#[cfg(feature = "rust-secp256k1")]
pub use keys::secp256k1;
#[cfg(all(feature = "ed25519", feature = "k256"))]
pub use keys::{ed25519_dalek, CombinedKey, CombinedPublicKey};
pub use builder::Builder;
pub use keys::{EnrKey, EnrKeyUnambiguous, EnrPublicKey};
pub use node_id::NodeId;
use std::marker::PhantomData;
type Key = Vec<u8>;
type PreviousRlpEncodedValues = Vec<Option<Bytes>>;
const MAX_ENR_SIZE: usize = 300;
pub struct Enr<K: EnrKey> {
seq: u64,
node_id: NodeId,
content: BTreeMap<Key, Bytes>,
signature: Vec<u8>,
phantom: PhantomData<K>,
}
impl<K: EnrKey> Enr<K> {
pub fn builder() -> builder::Builder<K> {
builder::Builder::default()
}
pub fn empty(signing_key: &K) -> Result<Self, Error> {
Self::builder().build(signing_key)
}
#[must_use]
pub const fn node_id(&self) -> NodeId {
self.node_id
}
#[must_use]
pub const fn seq(&self) -> u64 {
self.seq
}
#[deprecated(since = "0.11.2", note = "use get_decodable")]
#[allow(clippy::missing_panics_doc)]
pub fn get(&self, key: impl AsRef<[u8]>) -> Option<Bytes> {
self.get_raw_rlp(key).map(|mut rlp_data| {
let raw_data = &mut rlp_data;
let header = Header::decode(raw_data).expect("All data is sanitized");
raw_data[..header.payload_length].to_vec().into()
})
}
pub fn get_decodable<T: Decodable>(
&self,
key: impl AsRef<[u8]>,
) -> Option<Result<T, DecoderError>> {
self.get_raw_rlp(key)
.map(|mut rlp_data| Decodable::decode(&mut rlp_data))
}
pub fn get_raw_rlp(&self, key: impl AsRef<[u8]>) -> Option<&[u8]> {
self.content.get(key.as_ref()).map(AsRef::as_ref)
}
pub fn iter(&self) -> impl Iterator<Item = (&Key, &[u8])> {
self.content.iter().map(|(k, v)| (k, v.as_ref()))
}
#[must_use]
pub fn ip4(&self) -> Option<Ipv4Addr> {
if let Some(Ok(ip_bytes)) = self.get_decodable::<Bytes>("ip") {
return match ip_bytes.as_ref().len() {
4 => {
let mut ip = [0_u8; 4];
ip.copy_from_slice(ip_bytes.as_ref());
Some(Ipv4Addr::from(ip))
}
_ => None,
};
}
None
}
#[must_use]
pub fn ip6(&self) -> Option<Ipv6Addr> {
if let Some(Ok(ip_bytes)) = self.get_decodable::<Bytes>("ip6") {
return match ip_bytes.as_ref().len() {
16 => {
let mut ip = [0_u8; 16];
ip.copy_from_slice(ip_bytes.as_ref());
Some(Ipv6Addr::from(ip))
}
_ => None,
};
}
None
}
#[must_use]
pub fn id(&self) -> Option<String> {
if let Some(Ok(id_bytes)) = self.get_decodable::<Bytes>("id") {
return Some(String::from_utf8_lossy(id_bytes.as_ref()).to_string());
}
None
}
#[must_use]
pub fn client_info(&self) -> Option<(String, String, Option<String>)> {
if let Some(Ok(client_list)) = self.get_decodable::<Vec<Bytes>>("client") {
match client_list.len() {
2 => {
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();
let client_version =
String::from_utf8_lossy(client_list[1].as_ref()).to_string();
return Some((client_name, client_version, None));
}
3 => {
let client_name = String::from_utf8_lossy(client_list[0].as_ref()).to_string();
let client_version =
String::from_utf8_lossy(client_list[1].as_ref()).to_string();
let client_additional =
String::from_utf8_lossy(client_list[2].as_ref()).to_string();
return Some((client_name, client_version, Some(client_additional)));
}
_ => {}
}
}
None
}
#[must_use]
pub fn tcp4(&self) -> Option<u16> {
self.get_decodable("tcp").and_then(Result::ok)
}
#[must_use]
pub fn tcp6(&self) -> Option<u16> {
self.get_decodable("tcp6").and_then(Result::ok)
}
#[must_use]
pub fn udp4(&self) -> Option<u16> {
self.get_decodable("udp").and_then(Result::ok)
}
#[must_use]
pub fn udp6(&self) -> Option<u16> {
self.get_decodable("udp6").and_then(Result::ok)
}
#[must_use]
pub fn udp4_socket(&self) -> Option<SocketAddrV4> {
if let Some(ip) = self.ip4() {
if let Some(udp) = self.udp4() {
return Some(SocketAddrV4::new(ip, udp));
}
}
None
}
#[must_use]
pub fn udp6_socket(&self) -> Option<SocketAddrV6> {
if let Some(ip6) = self.ip6() {
if let Some(udp6) = self.udp6() {
return Some(SocketAddrV6::new(ip6, udp6, 0, 0));
}
}
None
}
#[must_use]
pub fn tcp4_socket(&self) -> Option<SocketAddrV4> {
if let Some(ip) = self.ip4() {
if let Some(tcp) = self.tcp4() {
return Some(SocketAddrV4::new(ip, tcp));
}
}
None
}
#[must_use]
pub fn tcp6_socket(&self) -> Option<SocketAddrV6> {
if let Some(ip6) = self.ip6() {
if let Some(tcp6) = self.tcp6() {
return Some(SocketAddrV6::new(ip6, tcp6, 0, 0));
}
}
None
}
#[must_use]
pub fn signature(&self) -> &[u8] {
&self.signature
}
#[must_use]
pub fn public_key(&self) -> K::PublicKey {
K::enr_to_public(&self.content).expect("ENR's can only be created with supported keys")
}
#[must_use]
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),
_ => false,
}
}
#[must_use]
pub fn compare_content(&self, other: &Self) -> bool {
self.rlp_content() == other.rlp_content()
}
#[must_use]
pub fn to_base64(&self) -> String {
let mut out = BytesMut::new();
self.encode(&mut out);
let hex = URL_SAFE_NO_PAD.encode(out);
format!("enr:{hex}")
}
#[must_use]
pub fn size(&self) -> usize {
let mut out = BytesMut::new();
self.encode(&mut out);
out.len()
}
pub fn set_seq(&mut self, seq: u64, key: &K) -> Result<(), Error> {
let prev_seq = self.seq;
self.seq = seq;
let prev_signature = match self.sign(key) {
Ok(signature) => signature,
Err(e) => {
self.seq = prev_seq;
return Err(e);
}
};
if self.size() > MAX_ENR_SIZE {
self.seq = prev_seq;
self.signature = prev_signature;
return Err(Error::ExceedsMaxSize);
}
self.node_id = NodeId::from(key.public());
Ok(())
}
pub fn insert<T: Encodable>(
&mut self,
key: impl AsRef<[u8]>,
value: &T,
enr_key: &K,
) -> Result<Option<Bytes>, Error> {
let mut out = BytesMut::new();
value.encode(&mut out);
self.insert_raw_rlp(key, out.freeze(), enr_key)
}
pub fn insert_raw_rlp(
&mut self,
key: impl AsRef<[u8]>,
value: Bytes,
enr_key: &K,
) -> Result<Option<Bytes>, Error> {
check_spec_reserved_keys(key.as_ref(), &value)?;
let raw_key = key.as_ref().to_vec();
let previous_value = self.content.insert(raw_key.clone(), value);
let public_key = enr_key.public();
let mut pubkey = BytesMut::new();
public_key.encode().as_ref().encode(&mut pubkey);
let previous_key = self.content.insert(public_key.enr_key(), pubkey.freeze());
if self.size() > MAX_ENR_SIZE {
if let Some(key) = previous_key {
self.content.insert(public_key.enr_key(), key);
} else {
self.content.remove(&public_key.enr_key());
}
if let Some(prev_value) = previous_value {
self.content.insert(raw_key, prev_value);
} else {
self.content.remove(key.as_ref());
}
return Err(Error::ExceedsMaxSize);
}
self.seq = self
.seq
.checked_add(1)
.ok_or(Error::SequenceNumberTooHigh)?;
self.sign(enr_key)?;
self.node_id = NodeId::from(enr_key.public());
if self.size() > MAX_ENR_SIZE {
return Err(Error::ExceedsMaxSize);
}
Ok(previous_value)
}
pub fn set_ip(&mut self, ip: IpAddr, key: &K) -> Result<Option<IpAddr>, Error> {
match ip {
IpAddr::V4(addr) => {
let prev_value = self.insert("ip", &addr.octets().as_ref(), key)?;
if let Some(bytes) = prev_value {
if bytes.len() == 4 {
let mut v = [0_u8; 4];
v.copy_from_slice(&bytes);
return Ok(Some(IpAddr::V4(Ipv4Addr::from(v))));
}
}
}
IpAddr::V6(addr) => {
let prev_value = self.insert("ip6", &addr.octets().as_ref(), key)?;
if let Some(bytes) = prev_value {
if bytes.len() == 16 {
let mut v = [0_u8; 16];
v.copy_from_slice(&bytes);
return Ok(Some(IpAddr::V6(Ipv6Addr::from(v))));
}
}
}
}
Ok(None)
}
pub fn set_udp4(&mut self, udp: u16, key: &K) -> Result<Option<u16>, Error> {
if let Some(udp_bytes) = self.insert("udp", &udp, key)? {
return Ok(u16::decode(&mut udp_bytes.as_ref()).ok());
}
Ok(None)
}
pub fn set_udp6(&mut self, udp: u16, key: &K) -> Result<Option<u16>, Error> {
if let Some(udp_bytes) = self.insert("udp6", &udp, key)? {
return Ok(u16::decode(&mut udp_bytes.as_ref()).ok());
}
Ok(None)
}
pub fn set_tcp4(&mut self, tcp: u16, key: &K) -> Result<Option<u16>, Error> {
if let Some(tcp_bytes) = self.insert("tcp", &tcp, key)? {
return Ok(u16::decode(&mut tcp_bytes.as_ref()).ok());
}
Ok(None)
}
pub fn set_tcp6(&mut self, tcp: u16, key: &K) -> Result<Option<u16>, Error> {
if let Some(tcp_bytes) = self.insert("tcp6", &tcp, key)? {
return Ok(u16::decode(&mut tcp_bytes.as_ref()).ok());
}
Ok(None)
}
pub fn set_client_info(
&mut self,
name: String,
version: String,
build: Option<String>,
key: &K,
) -> Result<(), Error> {
if build.is_none() {
self.insert("client", &vec![name, version], key)?;
} else {
self.insert("client", &vec![name, version, build.unwrap()], key)?;
}
Ok(())
}
pub fn set_udp_socket(&mut self, socket: SocketAddr, key: &K) -> Result<(), Error> {
self.set_socket(socket, key, false)
}
pub fn set_tcp_socket(&mut self, socket: SocketAddr, key: &K) -> Result<(), Error> {
self.set_socket(socket, key, true)
}
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())
} else {
("udp".into(), "udp6".into())
};
let (prev_ip, prev_port) = match socket.ip() {
IpAddr::V4(addr) => {
let mut ip = BytesMut::new();
addr.encode(&mut ip);
let mut port = BytesMut::new();
socket.port().encode(&mut port);
(
self.content.insert("ip".into(), ip.freeze()),
self.content.insert(port_string.clone(), port.freeze()),
)
}
IpAddr::V6(addr) => {
let mut ip6 = BytesMut::new();
addr.encode(&mut ip6);
let mut port = BytesMut::new();
socket.port().encode(&mut port);
(
self.content.insert("ip6".into(), ip6.freeze()),
self.content.insert(port_v6_string.clone(), port.freeze()),
)
}
};
let public_key = key.public();
let mut pubkey = BytesMut::new();
public_key.encode().as_ref().encode(&mut pubkey);
let previous_key = self.content.insert(public_key.enr_key(), pubkey.freeze());
if self.size() > MAX_ENR_SIZE {
if let Some(key) = previous_key {
self.content.insert(public_key.enr_key(), key);
} else {
self.content.remove(&public_key.enr_key());
}
match socket.ip() {
IpAddr::V4(_) => {
if let Some(ip) = prev_ip {
self.content.insert("ip".into(), ip);
} else {
self.content.remove(b"ip".as_ref());
}
if let Some(udp) = prev_port {
self.content.insert(port_string, udp);
} else {
self.content.remove(&port_string);
}
}
IpAddr::V6(_) => {
if let Some(ip) = prev_ip {
self.content.insert("ip6".into(), ip);
} else {
self.content.remove(b"ip6".as_ref());
}
if let Some(udp) = prev_port {
self.content.insert(port_v6_string, udp);
} else {
self.content.remove(&port_v6_string);
}
}
}
return Err(Error::ExceedsMaxSize);
}
self.seq = self
.seq
.checked_add(1)
.ok_or(Error::SequenceNumberTooHigh)?;
self.sign(key)?;
self.node_id = NodeId::from(key.public());
Ok(())
}
pub fn remove_insert<'a>(
&mut self,
remove_keys: impl Iterator<Item = impl AsRef<[u8]>>,
insert_key_values: impl Iterator<Item = (impl AsRef<[u8]>, &'a [u8])>,
enr_key: &K,
) -> Result<(PreviousRlpEncodedValues, PreviousRlpEncodedValues), Error> {
let enr_backup = self.clone();
let mut removed = Vec::new();
for key in remove_keys {
removed.push(self.content.remove(key.as_ref()));
}
let public_key = enr_key.public();
let mut pubkey = BytesMut::new();
public_key.encode().as_ref().encode(&mut pubkey);
self.content.insert(public_key.enr_key(), pubkey.freeze());
let mut inserted = Vec::new();
for (key, value) in insert_key_values {
if key.as_ref() == b"id" && value != b"v4" {
*self = enr_backup;
return Err(Error::UnsupportedIdentityScheme);
}
let mut out = BytesMut::new();
value.encode(&mut out);
let value = out.freeze();
if is_keyof_u16(key.as_ref()) {
u16::decode(&mut value.as_ref())?;
}
inserted.push(self.content.insert(key.as_ref().to_vec(), value));
}
self.seq = self
.seq
.checked_add(1)
.ok_or(Error::SequenceNumberTooHigh)?;
self.sign(enr_key)?;
self.node_id = NodeId::from(enr_key.public());
if self.size() > MAX_ENR_SIZE {
*self = enr_backup;
return Err(Error::ExceedsMaxSize);
}
Ok((removed, inserted))
}
pub fn set_public_key(&mut self, public_key: &K::PublicKey, key: &K) -> Result<(), Error> {
self.insert(&public_key.enr_key(), &public_key.encode().as_ref(), key)
.map(|_| {})
}
#[must_use]
pub fn is_udp_reachable(&self) -> bool {
self.udp4_socket().is_some() || self.udp6_socket().is_some()
}
#[must_use]
pub fn is_tcp_reachable(&self) -> bool {
self.tcp4_socket().is_some() || self.tcp6_socket().is_some()
}
fn append_rlp_content(&self, stream: &mut BytesMut, include_signature: bool) {
if include_signature {
self.signature.as_slice().encode(stream);
}
self.seq.encode(stream);
for (k, v) in &self.content {
k.as_slice().encode(stream);
stream.extend_from_slice(v);
}
}
fn rlp_content(&self) -> BytesMut {
let mut stream = BytesMut::with_capacity(MAX_ENR_SIZE);
let include_signature = false;
self.append_rlp_content(&mut stream, include_signature);
let header = Header {
list: true,
payload_length: stream.len(),
};
let mut out = BytesMut::new();
header.encode(&mut out);
out.extend_from_slice(&stream);
out
}
fn compute_signature(&self, signing_key: &K) -> Result<Vec<u8>, Error> {
match self.id() {
Some(ref id) if id == "v4" => signing_key
.sign_v4(&self.rlp_content())
.map_err(|_| Error::SigningError),
_ => Err(Error::UnsupportedIdentityScheme),
}
}
fn sign(&mut self, key: &K) -> Result<Vec<u8>, Error> {
let new_signature = self.compute_signature(key)?;
Ok(std::mem::replace(&mut self.signature, new_signature))
}
}
impl<K: EnrKey> Clone for Enr<K> {
fn clone(&self) -> Self {
Self {
seq: self.seq,
node_id: self.node_id,
content: self.content.clone(),
signature: self.signature.clone(),
phantom: self.phantom,
}
}
}
impl<K: EnrKey> std::cmp::Eq for Enr<K> {}
impl<K: EnrKey> PartialEq for Enr<K> {
fn eq(&self, other: &Self) -> bool {
self.seq == other.seq && self.node_id == other.node_id && self.signature == other.signature
}
}
impl<K: EnrKey> Hash for Enr<K> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.seq.hash(state);
self.node_id.hash(state);
self.signature.hash(state);
}
}
impl<K: EnrKey> std::fmt::Display for Enr<K> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_base64())
}
}
#[allow(clippy::missing_fields_in_debug)]
impl<K: EnrKey> std::fmt::Debug for Enr<K> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
struct OtherPairs<'a>(&'a BTreeMap<Key, Bytes>);
impl<'a> std::fmt::Debug for OtherPairs<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_list()
.entries(
self.0
.iter()
.filter(|(key, _)| {
!["id", "ip", "ip6", "udp", "udp6", "tcp", "tcp6"]
.iter()
.any(|k| k.as_bytes() == key.as_slice())
})
.map(|(key, val)| (String::from_utf8_lossy(key), hex::encode(val))),
)
.finish()
}
}
f.debug_struct("Enr")
.field("id", &self.id())
.field("seq", &self.seq())
.field("NodeId", &self.node_id())
.field("signature", &hex::encode(&self.signature))
.field("IpV4 UDP Socket", &self.udp4_socket())
.field("IpV6 UDP Socket", &self.udp6_socket())
.field("IpV4 TCP Socket", &self.tcp4_socket())
.field("IpV6 TCP Socket", &self.tcp6_socket())
.field("Other Pairs", &OtherPairs(&self.content))
.finish_non_exhaustive()
}
}
impl<K: EnrKey> FromStr for Enr<K> {
type Err = String;
fn from_str(base64_string: &str) -> Result<Self, Self::Err> {
if base64_string.len() < 4 {
return Err("Invalid ENR string".to_string());
}
let mut decode_string = base64_string;
if base64_string.starts_with("enr:") {
decode_string = decode_string
.get(4..)
.ok_or_else(|| "Invalid ENR string".to_string())?;
}
let bytes = URL_SAFE_NO_PAD
.decode(decode_string)
.map_err(|e| format!("Invalid base64 encoding: {e:?}"))?;
Self::decode(&mut bytes.as_ref()).map_err(|e| format!("Invalid ENR: {e:?}"))
}
}
#[cfg(feature = "serde")]
impl<K: EnrKey> Serialize for Enr<K> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_base64())
}
}
#[cfg(feature = "serde")]
impl<'de, K: EnrKey> Deserialize<'de> for Enr<K> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
Self::from_str(&s).map_err(D::Error::custom)
}
}
impl<K: EnrKey> Encodable for Enr<K> {
fn encode(&self, out: &mut dyn bytes::BufMut) {
let mut stream = BytesMut::with_capacity(MAX_ENR_SIZE);
let include_signature = true;
self.append_rlp_content(&mut stream, include_signature);
let header = Header {
list: true,
payload_length: stream.len(),
};
header.encode(out);
out.put_slice(&stream);
}
}
impl<K: EnrKey> Decodable for Enr<K> {
fn decode(buf: &mut &[u8]) -> Result<Self, DecoderError> {
if buf.len() > MAX_ENR_SIZE {
return Err(DecoderError::Custom("enr exceeds max size"));
}
let payload = &mut Header::decode_bytes(buf, true)?;
if payload.is_empty() {
return Err(DecoderError::Custom("Payload is empty"));
}
let signature = Header::decode_bytes(payload, false)?;
if payload.is_empty() {
return Err(DecoderError::Custom("Seq is missing"));
}
let seq = u64::decode(payload)?;
let mut content = BTreeMap::new();
let mut prev = None;
while !payload.is_empty() {
let key = Header::decode_bytes(payload, false)?;
if let Some(prev) = prev {
if prev >= key {
return Err(DecoderError::Custom("Unsorted keys"));
}
}
prev = Some(key);
let value = match key {
b"id" => {
let id = Header::decode_bytes(payload, false)?;
if id != b"v4" {
return Err(DecoderError::Custom("Unsupported identity scheme"));
}
alloy_rlp::encode(id)
}
b"tcp" | b"tcp6" | b"udp" | b"udp6" => {
let port = u16::decode(payload)?;
alloy_rlp::encode(port)
}
b"ip" => {
let ip = Ipv4Addr::decode(payload)?;
alloy_rlp::encode(ip)
}
b"ip6" => {
let ip6 = Ipv6Addr::decode(payload)?;
alloy_rlp::encode(ip6)
}
b"secp256k1" | b"ed25519" => {
let keys = Header::decode_bytes(payload, false)?;
alloy_rlp::encode(keys)
}
_ => {
let other_header = Header::decode(payload)?;
let value = &payload[..other_header.payload_length];
payload.advance(other_header.payload_length);
if other_header.list {
let mut out = Vec::<u8>::new();
other_header.encode(&mut out);
out.extend_from_slice(value);
out
} else {
alloy_rlp::encode(value)
}
}
};
content.insert(key.to_vec(), Bytes::from(value));
}
let public_key = K::enr_to_public(&content)?;
let node_id = NodeId::from(public_key);
let enr = Self {
seq,
node_id,
signature: signature.to_vec(),
content,
phantom: PhantomData,
};
if !enr.verify() {
return Err(DecoderError::Custom("Invalid Signature"));
}
Ok(enr)
}
}
pub struct EnrIntoIter {
inner: <BTreeMap<Key, Bytes> as IntoIterator>::IntoIter,
}
impl Iterator for EnrIntoIter {
type Item = (Key, Bytes);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
impl<K: EnrKey> IntoIterator for Enr<K> {
type Item = (Key, Bytes);
type IntoIter = EnrIntoIter;
fn into_iter(self) -> Self::IntoIter {
EnrIntoIter {
inner: self.content.into_iter(),
}
}
}
pub(crate) fn digest(b: &[u8]) -> [u8; 32] {
let mut output = [0_u8; 32];
output.copy_from_slice(&Keccak256::digest(b));
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], mut value: &[u8]) -> Result<(), Error> {
match key {
b"tcp" | b"tcp6" | b"udp" | b"udp6" => {
u16::decode(&mut value)?;
}
b"id" => {
let id_bytes = Bytes::decode(&mut value)?;
if id_bytes.as_ref() != b"v4" {
return Err(Error::UnsupportedIdentityScheme);
}
}
b"ip" => {
Ipv4Addr::decode(&mut value)?;
}
b"ip6" => {
Ipv6Addr::decode(&mut value)?;
}
b"secp256k1" => {
#[cfg(all(feature = "k256", not(feature = "rust-secp256k1")))]
<Enr<k256::ecdsa::SigningKey>>::decode(&mut value)?;
#[cfg(feature = "rust-secp256k1")]
<Enr<secp256k1::SecretKey>>::decode(&mut value)?;
}
_ => return Ok(()),
};
Ok(())
}
#[cfg(test)]
#[cfg(feature = "k256")]
mod tests {
use super::*;
use alloy_rlp::{RlpDecodable, RlpEncodable};
type DefaultEnr = Enr<k256::ecdsa::SigningKey>;
#[derive(RlpEncodable, RlpDecodable, Debug, PartialEq, Eq)]
struct GenericListValue {
first_field: [u8; 4],
second_field: [u8; 4],
third_field: u64,
}
impl GenericListValue {
fn gen_random() -> Self {
Self {
first_field: rand::random(),
second_field: rand::random(),
third_field: rand::random(),
}
}
}
#[derive(RlpEncodable, RlpDecodable, Debug, PartialEq, Eq)]
struct DoubleListValue {
first_field: [u8; 4],
second_field: GenericListValue,
}
impl DoubleListValue {
fn gen_random() -> Self {
Self {
first_field: rand::random(),
second_field: GenericListValue::gen_random(),
}
}
}
#[cfg(feature = "k256")]
#[test]
fn test_vector_k256() {
let valid_record = hex::decode("f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c01826964827634826970847f00000189736563703235366b31a103ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31388375647082765f").unwrap();
let signature = hex::decode("7098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c").unwrap();
let expected_pubkey =
hex::decode("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
.unwrap();
let mut buf = valid_record.as_slice();
let enr = DefaultEnr::decode(&mut buf).unwrap();
assert!(buf.is_empty());
let pubkey = enr.public_key().encode();
assert_eq!(enr.ip4(), Some(Ipv4Addr::new(127, 0, 0, 1)));
assert_eq!(enr.id(), Some(String::from("v4")));
assert_eq!(enr.udp4(), Some(30303));
assert_eq!(enr.tcp4(), None);
assert_eq!(enr.signature(), &signature[..]);
assert_eq!(pubkey.to_vec(), expected_pubkey);
assert!(enr.verify());
}
#[cfg(feature = "k256")]
#[test]
fn test_vector_2() {
let text = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
let signature = hex::decode("7098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c").unwrap();
let expected_pubkey =
hex::decode("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
.unwrap();
let expected_node_id =
hex::decode("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
.unwrap();
let enr = text.parse::<DefaultEnr>().unwrap();
let pubkey = enr.public_key().encode();
assert_eq!(enr.ip4(), Some(Ipv4Addr::new(127, 0, 0, 1)));
assert_eq!(enr.ip6(), None);
assert_eq!(enr.id(), Some(String::from("v4")));
assert_eq!(enr.udp4(), Some(30303));
assert_eq!(enr.udp6(), None);
assert_eq!(enr.tcp4(), None);
assert_eq!(enr.tcp6(), None);
assert_eq!(enr.signature(), &signature[..]);
assert_eq!(pubkey.to_vec(), expected_pubkey);
assert_eq!(enr.node_id().raw().to_vec(), expected_node_id);
assert!(enr.verify());
}
#[cfg(feature = "k256")]
#[test]
fn test_vector_2_k256() {
let text = "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
let signature = hex::decode("7098ad865b00a582051940cb9cf36836572411a47278783077011599ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1145ccb9c").unwrap();
let expected_pubkey =
hex::decode("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
.unwrap();
let expected_node_id =
hex::decode("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7")
.unwrap();
let enr = text.parse::<Enr<k256::ecdsa::SigningKey>>().unwrap();
let pubkey = enr.public_key().encode();
assert_eq!(enr.ip4(), Some(Ipv4Addr::new(127, 0, 0, 1)));
assert_eq!(enr.ip6(), None);
assert_eq!(enr.id(), Some(String::from("v4")));
assert_eq!(enr.udp4(), Some(30303));
assert_eq!(enr.udp6(), None);
assert_eq!(enr.tcp4(), None);
assert_eq!(enr.tcp6(), None);
assert_eq!(enr.signature(), &signature[..]);
assert_eq!(pubkey.to_vec(), expected_pubkey);
assert_eq!(enr.node_id().raw().to_vec(), expected_node_id);
assert!(enr.verify());
}
#[test]
fn test_encode_decode_list_value() {
let key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
let ip = Ipv4Addr::new(127, 0, 0, 1);
let tcp = 3000;
let list_value = GenericListValue::gen_random();
let enr = Enr::builder()
.ip4(ip)
.tcp4(tcp)
.add_value("list_value", &list_value)
.build(&key)
.unwrap();
let mut encoded_enr = BytesMut::new();
enr.encode(&mut encoded_enr);
let decoded_enr =
Enr::<k256::ecdsa::SigningKey>::decode(&mut encoded_enr.to_vec().as_slice()).unwrap();
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.ip4(), Some(ip));
assert_eq!(decoded_enr.tcp4(), Some(tcp));
assert_eq!(
decoded_enr
.get_decodable::<GenericListValue>("list_value")
.unwrap()
.unwrap(),
list_value
);
assert_eq!(decoded_enr.public_key().encode(), key.public().encode());
decoded_enr.public_key().encode_uncompressed();
assert!(decoded_enr.verify());
}
#[test]
fn test_encode_decode_double_list_value() {
let key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
let ip = Ipv4Addr::new(127, 0, 0, 1);
let tcp = 3000;
let list_value = DoubleListValue::gen_random();
let enr = Enr::builder()
.ip4(ip)
.tcp4(tcp)
.add_value("list_value", &list_value)
.build(&key)
.unwrap();
let mut encoded_enr = BytesMut::new();
enr.encode(&mut encoded_enr);
let decoded_enr =
Enr::<k256::ecdsa::SigningKey>::decode(&mut encoded_enr.to_vec().as_slice()).unwrap();
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.ip4(), Some(ip));
assert_eq!(decoded_enr.tcp4(), Some(tcp));
assert_eq!(
decoded_enr
.get_decodable::<DoubleListValue>("list_value")
.unwrap()
.unwrap(),
list_value
);
assert_eq!(decoded_enr.public_key().encode(), key.public().encode());
decoded_enr.public_key().encode_uncompressed();
assert!(decoded_enr.verify());
}
#[test]
fn test_rlp_list_value() {
let text = "enr:-Je4QH0uN2HkMRmscUp6yvyTOPGtOg9U6lCxBFvCGynyystnDNRJbfz5GhXXY2lcu9tsghMxRiYHoznBwG46GQ7dfm0og2V0aMfGhMvbiDiAgmlkgnY0gmlwhA6hJmuJc2VjcDI1NmsxoQJBP4kg9GNBurV3uVXgR72u1n-XIABibUZLT1WvJLKwvIN0Y3CCdyeDdWRwgncn";
let signature = hex::decode("7d2e3761e43119ac714a7acafc9338f1ad3a0f54ea50b1045bc21b29f2cacb670cd4496dfcf91a15d763695cbbdb6c821331462607a339c1c06e3a190edd7e6d").unwrap();
let expected_pubkey =
hex::decode("02413f8920f46341bab577b955e047bdaed67f972000626d464b4f55af24b2b0bc")
.unwrap();
let enr = text.parse::<DefaultEnr>().unwrap();
assert_eq!(enr.ip4(), Some(Ipv4Addr::new(14, 161, 38, 107)));
assert_eq!(enr.id(), Some(String::from("v4")));
assert_eq!(enr.udp4(), Some(30503));
assert_eq!(enr.tcp4(), Some(30503));
assert_eq!(enr.seq(), 40);
assert_eq!(enr.signature(), &signature[..]);
assert_eq!(enr.public_key().encode().to_vec(), expected_pubkey);
assert!(enr.verify());
}
#[cfg(feature = "k256")]
#[test]
fn test_read_enr_base64url_decoding_enforce_no_pad_no_extra_trailingbits() {
let test_data = [
("padded", "Invalid base64 encoding: InvalidPadding", "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8="),
("extra trailing bits", "Invalid base64 encoding: InvalidLastSymbol(178, 57)", "enr:-IS4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5wBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl9"),
];
for (test_name, err, text) in test_data {
assert_eq!(text.parse::<DefaultEnr>().unwrap_err(), err, "{test_name}",);
}
}
#[cfg(feature = "k256")]
#[test]
fn test_read_enr_no_prefix() {
let text = "-Iu4QM-YJF2RRpMcZkFiWzMf2kRd1A5F1GIekPa4Sfi_v0DCLTDBfOMTMMWJhhawr1YLUPb5008CpnBKrgjY3sstjfgCgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQP8u1uyQFyJYuQUTyA1raXKhSw1HhhxNUQ2VE52LNHWMIN0Y3CCIyiDdWRwgiMo";
text.parse::<DefaultEnr>().unwrap();
}
#[cfg(feature = "k256")]
#[test]
fn test_read_enr_prefix() {
let text = "enr:-Iu4QM-YJF2RRpMcZkFiWzMf2kRd1A5F1GIekPa4Sfi_v0DCLTDBfOMTMMWJhhawr1YLUPb5008CpnBKrgjY3sstjfgCgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQP8u1uyQFyJYuQUTyA1raXKhSw1HhhxNUQ2VE52LNHWMIN0Y3CCIyiDdWRwgiMo";
text.parse::<DefaultEnr>().unwrap();
}
#[cfg(feature = "k256")]
#[test]
fn test_read_enr_reject_too_large_record() {
let text = concat!("enr:-QEpuEDaLyrPP4gxBI9YL7QE9U1tZig_Nt8rue8bRIuYv_IMziFc8OEt3LQMwkwt6da-Z0Y8BaqkDalZbBq647UtV2ei",
"AYJpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTiDdWRwgnZferiieHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4");
let mut record = text.parse::<DefaultEnr>().unwrap();
let key_data =
hex::decode("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
.unwrap();
let key = k256::ecdsa::SigningKey::from_slice(&key_data).unwrap();
assert!(record.set_udp4(record.udp4().unwrap(), &key).is_ok());
let text = concat!("enr:-QEquEBxABglcZbIGKJ8RHDCp2Ft59tdf61RhV3XXf2BKTlKE2XwzNfihH-46hKkANsXaGRwH8Dp7a3lTrKiv2FMMaFY",
"AYJpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTiDdWRwgnZferijeHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4eA");
assert!(text
.parse::<DefaultEnr>()
.unwrap_err()
.contains("enr exceeds max size"));
}
#[cfg(feature = "k256")]
#[test]
fn test_read_enr_rlp_decoding_reject_extra_data() {
let record_hex1 = concat!(
"f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599",
"ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1",
"145ccb9c01826964827634826970847f00000189736563703235366b31a103ca",
"634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd313883",
"75647082765f"
);
let record_hex2 = concat!(
"f883b8407098ad865b00a582051940cb9cf36836572411a47278783077011599",
"ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1",
"145ccb9c01826964827634826970847f00000189736563703235366b31a103ca",
"634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd313883",
"75647082765f"
);
let record_hex3 = concat!(
"f884b8407098ad865b00a582051940cb9cf36836572411a47278783077011599",
"ed5cd16b76f2635f4e234738f30813a89eb9137e3e3df5266e3a1f11df72ecf1",
"145ccb9c01826964827634826970847f00000189736563703235366b31a103ca",
"634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd313883",
"75647082765ffc"
);
let valid_record = hex::decode(record_hex1).unwrap();
let expected_pubkey =
hex::decode("03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138")
.unwrap();
let enr = DefaultEnr::decode(&mut valid_record.as_slice()).unwrap();
let pubkey = enr.public_key().encode();
assert_eq!(pubkey.to_vec(), expected_pubkey);
assert!(enr.verify());
let invalid_record = hex::decode(record_hex2).unwrap();
DefaultEnr::decode(&mut invalid_record.as_slice()).expect_err("should reject extra data");
let invalid_record = hex::decode(record_hex3).unwrap();
DefaultEnr::decode(&mut invalid_record.as_slice()).expect("should reject extra data");
}
#[cfg(feature = "k256")]
#[test]
fn test_rlp_integer_decoding() {
let text = "enr:-Ia4QHCYrYZbAKWCBRlAy5zzaDZXJBGkcnh4MHcBFZntXNFrdvJjX04jRzjzCBOonrkTfj499SZuOh8R33Ls8RRcy5yCAAGCaWSCdjSCaXCEfwAAAYlzZWNwMjU2azGhA8pjTK4NSay0Adikxrb-jFW3DRFb9AB2nMFADzJYzTE4g3VkcIJ2Xw";
assert_eq!(
text.parse::<DefaultEnr>().unwrap_err(),
"Invalid ENR: LeadingZero"
);
}
#[cfg(feature = "rust-secp256k1")]
#[test]
fn test_encode_decode_secp256k1() {
let mut rng = secp256k1::rand::thread_rng();
let key = secp256k1::SecretKey::new(&mut rng);
let ip = Ipv4Addr::new(127, 0, 0, 1);
let tcp = 3000;
let enr = Enr::builder().ip4(ip).tcp4(tcp).build(&key).unwrap();
let encoded_enr = alloy_rlp::encode(enr);
let decoded_enr = DefaultEnr::decode(&mut encoded_enr.as_slice()).unwrap();
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.ip4(), Some(ip));
assert_eq!(decoded_enr.tcp4(), Some(tcp));
assert_eq!(
decoded_enr.public_key().encode(),
key.public().encode().into()
);
assert!(decoded_enr.verify());
}
#[cfg(feature = "rust-secp256k1")]
#[test]
fn test_secp256k1_sign_ecdsa_with_mock_noncedata() {
let expected_enr_base64 = "enr:-IS4QLJYdRwxdy-AbzWC6wL9ooB6O6uvCvJsJ36rbJztiAs1JzPY0__YkgFzZwNUuNhm1BDN6c4-UVRCJP9bXNCmoDYBgmlkgnY0gmlwhH8AAAGJc2VjcDI1NmsxoQPKY0yuDUmstAHYpMa2_oxVtw0RW_QAdpzBQA8yWM0xOIN1ZHCCdl8";
let key_data =
hex::decode("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
.unwrap();
let ip = Ipv4Addr::new(127, 0, 0, 1);
let udp = 30303;
let key = secp256k1::SecretKey::from_slice(&key_data).unwrap();
let enr = Enr::builder().ip4(ip).udp4(udp).build(&key).unwrap();
let enr_base64 = enr.to_base64();
assert_eq!(enr_base64, expected_enr_base64);
let enr = enr_base64.parse::<Enr<secp256k1::SecretKey>>().unwrap();
assert!(enr.verify());
}
#[cfg(feature = "k256")]
#[test]
fn test_encode_decode_k256() {
let key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
let ip = Ipv4Addr::new(127, 0, 0, 1);
let tcp = 3000;
let enr = Enr::builder().ip4(ip).tcp4(tcp).build(&key).unwrap();
let mut encoded_enr = BytesMut::new();
enr.encode(&mut encoded_enr);
let decoded_enr =
Enr::<k256::ecdsa::SigningKey>::decode(&mut encoded_enr.to_vec().as_slice()).unwrap();
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.ip4(), Some(ip));
assert_eq!(decoded_enr.tcp4(), Some(tcp));
assert_eq!(decoded_enr.public_key().encode(), key.public().encode());
decoded_enr.public_key().encode_uncompressed();
assert!(decoded_enr.verify());
}
#[cfg(all(feature = "ed25519", feature = "k256"))]
#[test]
fn test_encode_decode_ed25519() {
let mut rng = rand::thread_rng();
let key = ed25519_dalek::SigningKey::generate(&mut rng);
let ip = Ipv4Addr::new(10, 0, 0, 1);
let tcp = 30303;
let enr = Enr::builder().ip4(ip).tcp4(tcp).build(&key).unwrap();
let mut out = BytesMut::new();
enr.encode(&mut out);
let decoded_enr = Enr::<CombinedKey>::decode(&mut out.to_vec().as_slice()).unwrap();
assert_eq!(decoded_enr.id(), Some("v4".into()));
assert_eq!(decoded_enr.ip4(), Some(ip));
assert_eq!(decoded_enr.tcp4(), Some(tcp));
assert_eq!(decoded_enr.public_key().encode(), key.public().encode());
assert!(decoded_enr.verify());
}
#[test]
fn test_add_content_value() {
#[derive(PartialEq, Eq, Debug, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
struct Proto {
name: String,
version: u64,
}
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let proto = Proto {
name: "test".to_string(),
version: 1,
};
let enr = Enr::builder()
.add_value("proto", &proto)
.build(&key)
.unwrap();
let decoded_proto = enr.get_decodable::<Proto>("proto").unwrap().unwrap();
assert_eq!(decoded_proto, proto);
}
#[test]
fn test_add_content_value_decoding() {
#[derive(PartialEq, Eq, Debug, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)]
struct Proto {
name: String,
version: u64,
}
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let proto = Proto {
name: "test".to_string(),
version: 1,
};
let enr = Enr::builder()
.add_value("proto", &proto)
.build(&key)
.unwrap();
let encoded_enr = alloy_rlp::encode(enr.clone());
let decoded_enr = Enr::<k256::ecdsa::SigningKey>::decode(&mut &encoded_enr[..]).unwrap();
let decoded_proto = decoded_enr
.get_decodable::<Proto>("proto")
.unwrap()
.unwrap();
assert_eq!(decoded_proto, proto);
}
#[test]
fn test_add_key() {
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let ip = Ipv4Addr::new(10, 0, 0, 1);
let tcp = 30303;
let mut enr = Enr::builder().ip4(ip).tcp4(tcp).build(&key).unwrap();
enr.insert("random", &Vec::<u8>::new(), &key).unwrap();
assert!(enr.verify());
}
#[test]
fn test_set_ip() {
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let tcp = 30303;
let ip = Ipv4Addr::new(10, 0, 0, 1);
let mut enr = Enr::builder().tcp4(tcp).build(&key).unwrap();
assert!(enr.set_ip(ip.into(), &key).is_ok());
assert_eq!(enr.id(), Some("v4".into()));
assert_eq!(enr.ip4(), Some(ip));
assert_eq!(enr.tcp4(), Some(tcp));
assert!(enr.verify());
assert_eq!(enr.public_key().encode(), key.public().encode());
}
#[test]
fn ip_mutation_static_node_id() {
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let tcp = 30303;
let udp = 30304;
let ip = Ipv4Addr::new(10, 0, 0, 1);
let mut enr = Enr::builder()
.ip4(ip)
.tcp4(tcp)
.udp4(udp)
.build(&key)
.unwrap();
let node_id = enr.node_id();
enr.set_udp_socket("192.168.0.1:800".parse::<SocketAddr>().unwrap(), &key)
.unwrap();
assert_eq!(node_id, enr.node_id());
assert_eq!(
enr.udp4_socket(),
"192.168.0.1:800".parse::<SocketAddrV4>().unwrap().into()
);
}
#[cfg(all(feature = "ed25519", feature = "k256"))]
#[test]
fn combined_key_can_decode_all() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let ip = Ipv4Addr::new(192, 168, 0, 1);
let enr_secp256k1 = Enr::builder().ip(ip.into()).tcp4(8000).build(&key).unwrap();
let base64_string_secp256k1 = enr_secp256k1.to_base64();
let key = ed25519_dalek::SigningKey::generate(&mut rand::thread_rng());
let enr_ed25519 = Enr::builder().ip(ip.into()).tcp4(8000).build(&key).unwrap();
let base64_string_ed25519 = enr_ed25519.to_base64();
let _decoded_enr_secp256k1: DefaultEnr = base64_string_secp256k1.parse().unwrap();
let _decoded_enr_ed25519: Enr<ed25519_dalek::SigningKey> =
base64_string_ed25519.parse().unwrap();
let _decoded_enr: Enr<CombinedKey> = base64_string_secp256k1
.parse()
.expect("Can decode both secp");
let _decoded_enr: Enr<CombinedKey> = base64_string_ed25519.parse().unwrap();
}
fn encoded_list<T: Encodable + Clone>(t: &[T]) -> BytesMut {
let mut out = BytesMut::new();
alloy_rlp::encode_list(t, &mut out);
out
}
#[test]
fn test_remove_insert() {
let mut rng = rand::thread_rng();
let key = k256::ecdsa::SigningKey::random(&mut rng);
let tcp = 30303;
let list = &["lighthouse", "eth_sync"];
let out = encoded_list(list);
let mut enr = Enr::builder().tcp4(tcp).build(&key).unwrap();
assert_eq!(enr.tcp4(), Some(tcp));
assert_eq!(enr.get_decodable::<BytesMut>("topics"), None);
let topics: &[u8] = &out;
let (removed, inserted) = enr
.remove_insert([b"tcp"].iter(), vec![(b"topics", topics)].into_iter(), &key)
.unwrap();
let mut buffer = Vec::<u8>::new();
tcp.encode(&mut buffer);
assert_eq!(removed[0], Some(Bytes::copy_from_slice(&buffer)));
assert_eq!(inserted[0], None);
assert_eq!(enr.tcp4(), None);
assert_eq!(enr.get_decodable("topics"), Some(Ok(out)));
assert_eq!(enr.public_key().encode(), key.public().encode());
}
const LOW_INT_PORTS: [u16; 4] = [0, 30, 255, 30303];
#[test]
fn test_low_integer_build() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
for tcp in LOW_INT_PORTS {
let enr = Enr::builder().tcp4(tcp).build(&key).unwrap();
assert_tcp4(&enr, tcp);
}
}
#[test]
fn test_low_integer_set() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
for tcp in LOW_INT_PORTS {
let mut enr = Enr::empty(&key).unwrap();
enr.set_tcp4(tcp, &key).unwrap();
assert_tcp4(&enr, tcp);
}
}
#[test]
fn test_low_integer_set_socket() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let ipv4 = Ipv4Addr::new(127, 0, 0, 1);
for tcp in LOW_INT_PORTS {
let mut enr = Enr::empty(&key).unwrap();
enr.set_socket(SocketAddr::V4(SocketAddrV4::new(ipv4, tcp)), &key, true)
.unwrap();
assert_tcp4(&enr, tcp);
}
}
#[test]
fn test_low_integer_insert() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
for tcp in LOW_INT_PORTS {
let mut enr = Enr::empty(&key).unwrap();
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");
} else {
res.unwrap(); assert_tcp4(&enr, tcp);
}
}
}
#[test]
fn test_low_integer_remove_insert() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
for tcp in LOW_INT_PORTS {
let mut enr = Enr::empty(&key).unwrap();
let res = enr.remove_insert(
[b"none"].iter(),
vec![(b"tcp".as_slice(), tcp.to_be_bytes().as_slice())].into_iter(),
&key,
);
if u8::try_from(tcp).is_ok() {
assert_eq!(res.unwrap_err().to_string(), "invalid rlp data");
} else {
res.unwrap(); assert_tcp4(&enr, tcp);
}
}
}
#[test]
fn test_low_integer_bad_enr() {
let vectors = vec![
(0, "enr:-Hy4QDMsoimQl2Qb9CuIWlNjyt0C0DmZC4QpAsJzgUHowOq2Nph9UbAtZ_qS_8fl6SU-eSWrswHiLCoMUGQfjhl_GW0BgmlkgnY0iXNlY3AyNTZrMaECMoYV0PAXMueQz19FHpBO0jGBoLYCWhfSxGf5kQgk9KqDdGNwggAA"),
(30, "enr:-Hy4QCCgTB9tAEJL1DFwTTtwd79xxQx2hvi5RX9vWvcdKqbpS3SDzHHBivpOgxE40HGt6P0NtCE5QKzOQ5fzBwepDfMBgmlkgnY0iXNlY3AyNTZrMaECMoYV0PAXMueQz19FHpBO0jGBoLYCWhfSxGf5kQgk9KqDdGNwggAe"),
(255, "enr:-Hy4QOrU9C35gZyJigIi-u19sRP42eEjVEhzO-LnKXKM5VlDMZ45vnOIa3bqm15ap8pmLjq5kmRPzjuA0RUdzSsieqcBgmlkgnY0iXNlY3AyNTZrMaECMoYV0PAXMueQz19FHpBO0jGBoLYCWhfSxGf5kQgk9KqDdGNwggD_"),
(30303, "enr:-Hy4QF_mn4BuM6hY4CuLH8xDQd7U8kVZe9fyNgRB1vjdToGWQsQhetRvsByoJCWGQ6kf2aiWC0le24lkp0IPIJkLSTUBgmlkgnY0iXNlY3AyNTZrMaECMoYV0PAXMueQz19FHpBO0jGBoLYCWhfSxGf5kQgk9KqDdGNwgnZf"),
];
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: LeadingZero");
} else {
assert_tcp4(&res.unwrap(), tcp);
}
}
}
#[test]
fn test_compare_content() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let ip = Ipv4Addr::new(10, 0, 0, 1);
let tcp = 30303;
let enr1 = Enr::builder().ip4(ip).tcp4(tcp).build(&key).unwrap();
let mut enr2 = enr1.clone();
enr2.set_seq(1, &key).unwrap();
let mut enr3 = enr1.clone();
enr3.set_seq(2, &key).unwrap();
assert_ne!(enr1.signature(), enr2.signature());
assert!(enr1.compare_content(&enr2));
assert_ne!(enr1, enr2);
assert_ne!(enr1.signature(), enr3.signature());
assert!(!enr1.compare_content(&enr3));
assert_ne!(enr1, enr3);
}
fn assert_tcp4(enr: &DefaultEnr, tcp: u16) {
let mut buffer = Vec::<u8>::new();
tcp.encode(&mut buffer);
assert!(enr.verify());
assert_eq!(enr.get_raw_rlp("tcp").unwrap(), buffer);
assert_eq!(enr.tcp4(), Some(tcp));
}
#[test]
fn test_large_enr_decoding_is_rejected() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut huge_enr = Enr::empty(&key).unwrap();
let large_vec: Vec<u8> = std::iter::repeat(0).take(MAX_ENR_SIZE).collect();
let large_vec_encoded = alloy_rlp::encode(large_vec);
huge_enr
.content
.insert(b"large vec".to_vec(), large_vec_encoded.into());
huge_enr.sign(&key).unwrap();
assert!(huge_enr.verify());
let encoded = alloy_rlp::encode(&huge_enr);
assert!(encoded.len() > MAX_ENR_SIZE);
assert_eq!(
DefaultEnr::decode(&mut encoded.as_ref()).unwrap_err(),
DecoderError::Custom("enr exceeds max size")
)
}
#[test]
fn test_set_seq() {
const LARGE_ENR : &str = concat!(
"enr:-QEpuEDaLyrPP4gxBI9YL7QE9U1tZig_Nt8rue8bRIuYv_IMziFc8OEt3LQMwkwt6da-Z0Y8BaqkDalZbBq647UtV2ei",
"AYJpZIJ2NIJpcIR_AAABiXNlY3AyNTZrMaEDymNMrg1JrLQB2KTGtv6MVbcNEVv0AHacwUAPMljNMTiDdWRwgnZferiieHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4",
"eHh4eHh4eHh4eHh4eHh4"
);
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut record = LARGE_ENR.parse::<DefaultEnr>().unwrap();
let enr_bkp = record.clone();
assert_eq!(record.set_seq(u64::MAX, &key), Err(Error::ExceedsMaxSize));
assert_eq!(record, enr_bkp);
record.set_seq(30, &key).unwrap();
assert_eq!(record.seq(), 30);
}
#[test]
fn test_set_client_eip7636() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut enr = Enr::empty(&key).unwrap();
enr.set_client_info(
"Test".to_string(),
"v1.0.0".to_string(),
Some("Test".to_string()),
&key,
)
.unwrap();
assert!(enr.verify());
enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
.unwrap();
assert!(enr.verify());
}
#[test]
fn test_get_eip7636() {
let example_eip = "enr:-MO4QBn4OF-y-dqULg4WOIlc8gQAt-arldNFe0_YQ4HNX28jDtg41xjDyKfCXGfZaPN97I-MCfogeK91TyqmWTpb0_AChmNsaWVudNqKTmV0aGVybWluZIYxLjkuNTOHN2ZjYjU2N4JpZIJ2NIJpcIR_AAABg2lwNpAAAAAAAAAAAAAAAAAAAAABiXNlY3AyNTZrMaECn-TTdCwfZP4XgJyq8Lxoj-SgEoIFgDLVBEUqQk4HnAqDdWRwgiMshHVkcDaCIyw";
let enr = example_eip.parse::<DefaultEnr>().unwrap();
let info = enr.client_info().unwrap();
assert_eq!(info.0, "Nethermind");
assert_eq!(info.1, "1.9.53");
assert_eq!(info.2.unwrap(), "7fcb567");
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let mut enr = Enr::empty(&key).unwrap();
enr.set_client_info("Test".to_string(), "v1.0.0".to_string(), None, &key)
.unwrap();
let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2, None);
}
#[test]
fn test_builder_eip7636() {
let key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
let enr = Enr::builder()
.ip4(Ipv4Addr::new(127, 0, 0, 1))
.tcp4(30303)
.client_info(
"Test".to_string(),
"v1.0.0".to_string(),
Some("Test".to_string()),
)
.build(&key)
.unwrap();
let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2.unwrap(), "Test");
let enr = Enr::builder()
.ip4(Ipv4Addr::new(127, 0, 0, 1))
.tcp4(30303)
.client_info("Test".to_string(), "v1.0.0".to_string(), None)
.build(&key)
.unwrap();
let info = enr.client_info().unwrap();
assert_eq!(info.0, "Test");
assert_eq!(info.1, "v1.0.0");
assert_eq!(info.2, None);
}
#[test]
fn test_common_rlp_convention() {
const COMMON_VALID_ENR: &str = concat!(
"-LW4QCAyOCtqvQjd8AgpqbaCgfjy8oN8cBBRT5jtzarkGJQWZx1eN70EM0QafVCugLa-Bv493DPNzflagqfTOsWSF78Ih2F0d",
"G5ldHOIAGAAAAAAAACEZXRoMpBqlaGpBAAAAP__________gmlkgnY0hHF1aWOCIymJc2VjcDI1NmsxoQPg_HgqXzwRIK39Oy",
"lGdC30YUFwsfXvATnGUvEZ6MtBQIhzeW5jbmV0cwCDdGNwgiMo"
);
let _decoded: DefaultEnr = COMMON_VALID_ENR.parse().unwrap();
}
}