Skip to content

Commit

Permalink
Add to_bytes to identity
Browse files Browse the repository at this point in the history
The get_hash and get_hash_array functions are unstable between rust
versions due to changes in the internal hashing function. This patch
adds deprecation tags to these functions and implements a to and from
bytes representation for the Identity struct.

This is distinct from the Ethereum ABI functions, as the byte
representation is entierly defined by the Identity struct and not the
Ethereum standard. As such it's a lot more straightforward to implement.

The downside is that the new implementation is quite a bit larger than
the old hash implementation at 68 bytes versus 8 bytes. But that's not
really a big deal, for 5000 routers thats an extra 300MB of data used in
indexing. Furthermore there's no risk of someone trying to generate an
Identity with a hash collision.
  • Loading branch information
jkilpatr committed Oct 29, 2024
1 parent d84dfbc commit cde7bd8
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion althea_types/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
authors = ["Jehan <[email protected]>"]
name = "althea_types"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"

Expand Down
14 changes: 14 additions & 0 deletions althea_types/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ use std::fmt::Result as FormatResult;
pub enum AltheaTypesError {
WgParseError(DecodeError),
BadEthAbiInput(String),
InvalidWgKeyLength,
InvalidIdentityBytesLength,
ClarityError(String),
}

impl From<clarity::Error> for AltheaTypesError {
fn from(e: clarity::Error) -> Self {
AltheaTypesError::ClarityError(e.to_string())
}
}

impl fmt::Display for AltheaTypesError {
Expand All @@ -16,6 +25,11 @@ impl fmt::Display for AltheaTypesError {
AltheaTypesError::BadEthAbiInput(e) => {
write!(f, "Failed to parse Eth ABI input with {e}")
}
AltheaTypesError::InvalidWgKeyLength => write!(f, "Invalid WgKey length"),
AltheaTypesError::InvalidIdentityBytesLength => {
write!(f, "Invalid identity bytes length")
}
AltheaTypesError::ClarityError(val) => write!(f, "Clarity error: {}", val),
}
}
}
Expand Down
101 changes: 101 additions & 0 deletions althea_types/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use std::fmt;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::net::IpAddr;
use std::net::Ipv4Addr;
use std::net::Ipv6Addr;

/// The EVM integer size
Expand Down Expand Up @@ -90,19 +91,87 @@ impl Identity {
AltheaAddress::from_slice(self.eth_address.as_bytes(), ALTHEA_PREFIX).unwrap()
}

#[deprecated(
since = "0.2.0",
note = "This is unstable between rust versions please use `to_bytes` instead"
)]
pub fn get_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}

#[deprecated(
since = "0.2.0",
note = "This is unstable between rust versions please use `to_bytes` instead"
)]
pub fn get_hash_array(&self) -> [u8; 8] {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
let bits = hasher.finish();
bits.to_be_bytes()
}

/// Returns a byte representation of the identity
/// simply the concatenation of the mesh_ip, eth_address and wg_public_key
/// in that order ignoring the nickname
/// [mesh ip 4 or 16 bytes][eth address 20 bytes][wg public key 32 bytes]
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
let mesh_ip_bytes = match self.mesh_ip {
IpAddr::V4(ip) => ip.octets().to_vec(),
IpAddr::V6(ip) => ip.octets().to_vec(),
};
bytes.extend_from_slice(&mesh_ip_bytes);
bytes.extend_from_slice(self.eth_address.as_bytes());
bytes.extend_from_slice(self.wg_public_key.as_bytes());
bytes
}

/// The inverse of the two bytes function, takes a byte slice and returns an identity
/// returns None if the byte slice can not possibly be an identity
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AltheaTypesError> {
const LEN_WITH_IPV4: usize = 56;
const LEN_WITH_IPV6: usize = 68;
match (bytes.len() == LEN_WITH_IPV4, bytes.len() == LEN_WITH_IPV6) {
// true true is impossible but rust doesn't know that (yet)
(false, false) | (true, true) => Err(AltheaTypesError::InvalidIdentityBytesLength),
(true, false) => {
// ipv4 case
let mesh_ip = IpAddr::V4(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]));
let eth_address = Address::from_slice(&bytes[4..24])?;
let wg_public_key = WgKey::from_slice(&bytes[24..56])?;
Ok(Identity {
mesh_ip,
eth_address,
wg_public_key,
nickname: None,
})
}
(false, true) => {
// ipv6 case
let mesh_ip = Ipv6Addr::new(
u16::from_be_bytes([bytes[0], bytes[1]]),
u16::from_be_bytes([bytes[2], bytes[3]]),
u16::from_be_bytes([bytes[4], bytes[5]]),
u16::from_be_bytes([bytes[6], bytes[7]]),
u16::from_be_bytes([bytes[8], bytes[9]]),
u16::from_be_bytes([bytes[10], bytes[11]]),
u16::from_be_bytes([bytes[12], bytes[13]]),
u16::from_be_bytes([bytes[14], bytes[15]]),
);
let eth_address = Address::from_slice(&bytes[16..36])?;
let wg_public_key = WgKey::from_slice(&bytes[36..68])?;
Ok(Identity {
mesh_ip: IpAddr::V6(mesh_ip),
eth_address,
wg_public_key,
nickname: None,
})
}
}
}

/// Returns the Identity in it's Ethereum ABI encoded form
/// as used by the exit registration smart contract
pub fn encode_to_eth_abi(&self) -> Vec<u8> {
Expand Down Expand Up @@ -361,6 +430,38 @@ pub mod tests {
assert_eq!(num, num_from_bytes);
}

#[test]
fn test_identity_byte_encoding() {
let id = random_identity();
let bytes = id.to_bytes();
let id2 = Identity::from_bytes(&bytes).unwrap();
assert_eq!(id, id2);
}

#[test]
fn fuzz_identity_byte_encoding_random_id() {
let start = Instant::now();
while Instant::now() - start < FUZZ_TIME {
let id = random_identity();
let bytes = id.to_bytes();
let id2 = Identity::from_bytes(&bytes).unwrap();
assert_eq!(id, id2);
}
}

#[test]
fn fuzz_identity_byte_encoding_random_bytes() {
let start = Instant::now();
let mut rng = thread_rng();
while Instant::now() - start < FUZZ_TIME {
let bytes = get_fuzz_bytes(&mut rng);
let id = Identity::from_bytes(&bytes);
if let Ok(r) = id {
println!("Random success should be rare {:?}", r);
}
}
}

#[test]
fn fuzz_pase_identity_abi_correct() {
let start = Instant::now();
Expand Down
21 changes: 21 additions & 0 deletions althea_types/src/wg_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ impl From<WgKey> for [u8; 32] {
}
}

impl WgKey {
pub fn to_vec(&self) -> Vec<u8> {
self.0.to_vec()
}

pub fn as_bytes(&self) -> &[u8] {
&self.0
}

pub fn from_slice(slice: &[u8]) -> Result<WgKey, AltheaTypesError> {
if slice.len() != 32 {
return Err(AltheaTypesError::InvalidWgKeyLength);
}

let mut key = [0u8; 32];
key.copy_from_slice(slice);

Ok(WgKey(key))
}
}

/// This is somewhat dangerous, since libsodium provides seperate
/// public and private key types while we don't have those here.
/// Be very careful not to use this on the public key! That would be bad
Expand Down

0 comments on commit cde7bd8

Please sign in to comment.