diff --git a/Cargo.lock b/Cargo.lock index 1e37c1768..530f4149f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -365,7 +365,7 @@ version = "0.1.11" [[package]] name = "althea_types" -version = "0.1.0" +version = "0.2.0" dependencies = [ "arrayvec", "babel_monitor", diff --git a/althea_types/Cargo.toml b/althea_types/Cargo.toml index 74d04ca50..897f9508b 100644 --- a/althea_types/Cargo.toml +++ b/althea_types/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Jehan "] name = "althea_types" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "Apache-2.0" diff --git a/althea_types/src/error.rs b/althea_types/src/error.rs index 378a12132..c2cf7fd8b 100644 --- a/althea_types/src/error.rs +++ b/althea_types/src/error.rs @@ -7,6 +7,15 @@ use std::fmt::Result as FormatResult; pub enum AltheaTypesError { WgParseError(DecodeError), BadEthAbiInput(String), + InvalidWgKeyLength, + InvalidIdentityBytesLength, + ClarityError(String), +} + +impl From for AltheaTypesError { + fn from(e: clarity::Error) -> Self { + AltheaTypesError::ClarityError(e.to_string()) + } } impl fmt::Display for AltheaTypesError { @@ -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), } } } diff --git a/althea_types/src/identity.rs b/althea_types/src/identity.rs index 1a34f8b03..7dc3208b0 100644 --- a/althea_types/src/identity.rs +++ b/althea_types/src/identity.rs @@ -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 @@ -90,12 +91,20 @@ 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); @@ -103,6 +112,66 @@ impl Identity { 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 { + 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 { + 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 { @@ -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(); diff --git a/althea_types/src/wg_key.rs b/althea_types/src/wg_key.rs index 15efc23b2..92b79f743 100644 --- a/althea_types/src/wg_key.rs +++ b/althea_types/src/wg_key.rs @@ -28,6 +28,27 @@ impl From for [u8; 32] { } } +impl WgKey { + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } + + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + pub fn from_slice(slice: &[u8]) -> Result { + 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