diff --git a/network-hypervisor/Cargo.lock b/network-hypervisor/Cargo.lock index 24fe6f17b..a657dcd51 100644 --- a/network-hypervisor/Cargo.lock +++ b/network-hypervisor/Cargo.lock @@ -61,6 +61,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "concat-arrays" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df715824eb382e34b7afb7463b0247bf41538aeba731fba05241ecdb5dc3747" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cpufeatures" version = "0.1.5" @@ -605,6 +616,7 @@ version = "2.0.0" dependencies = [ "aes-gmac-siv", "base64", + "concat-arrays", "dashmap", "ed25519-dalek", "gcrypt", diff --git a/network-hypervisor/Cargo.toml b/network-hypervisor/Cargo.toml index e2839c54b..ce744c0fe 100644 --- a/network-hypervisor/Cargo.toml +++ b/network-hypervisor/Cargo.toml @@ -20,6 +20,7 @@ urlencoding = "^2" lz4_flex = { version = "^0", features = ["safe-encode", "safe-decode", "checked-decode"] } dashmap = "^4" parking_lot = "^0" +concat-arrays = "^0" [target."cfg(not(windows))".dependencies] libc = "^0" diff --git a/network-hypervisor/src/crypto/balloon.rs b/network-hypervisor/src/crypto/balloon.rs index dd2b16e84..b07947a3f 100644 --- a/network-hypervisor/src/crypto/balloon.rs +++ b/network-hypervisor/src/crypto/balloon.rs @@ -1,6 +1,8 @@ use std::convert::TryInto; use std::mem::MaybeUninit; +use crate::crypto::hash::{SHA384, SHA512}; + #[inline(always)] fn hash_int_le(sha: &mut crate::crypto::hash::SHA512, i: u64) { #[cfg(target_endian = "big")] { @@ -11,10 +13,10 @@ fn hash_int_le(sha: &mut crate::crypto::hash::SHA512, i: u64) { } } -/// Compute balloon memory-hard hash using SHA-512. +/// Compute balloon memory-hard hash using SHA-512 and SHA-384 for the final. /// SPACE_COST must be a multiple of 64. This is checked with an assertion. /// DELTA is usually 3. -pub fn hash(password: &[u8], salt: &[u8]) -> [u8; crate::crypto::hash::SHA512_HASH_SIZE] { +pub fn hash(password: &[u8], salt: &[u8]) -> [u8; crate::crypto::hash::SHA384_HASH_SIZE] { debug_assert_ne!(SPACE_COST, 0); debug_assert_ne!(TIME_COST, 0); debug_assert_ne!(DELTA, 0); @@ -24,7 +26,7 @@ pub fn hash let zero64 = [0_u8; 8]; /* Initial hash */ - let mut sha = crate::crypto::hash::SHA512::new(); + let mut sha = SHA512::new(); sha.update(&zero64); // 0 cnt sha.update(password); sha.update(salt); @@ -106,8 +108,9 @@ pub fn hash } } - /* Extract */ - buf[(SPACE_COST - 64)..SPACE_COST].try_into().unwrap() + // Standard balloon hashing just returns the last hash, but we want a 384-bit + // result. This just hashes the whole buffer including the last hash. + SHA384::hash(&buf) } /* diff --git a/network-hypervisor/src/crypto/secret.rs b/network-hypervisor/src/crypto/secret.rs index f6692a923..f0cded230 100644 --- a/network-hypervisor/src/crypto/secret.rs +++ b/network-hypervisor/src/crypto/secret.rs @@ -25,6 +25,11 @@ impl Secret { pub fn from_bytes(b: &[u8]) -> Self { Self(b.try_into().unwrap()) } + + #[inline(always)] + pub fn as_bytes(&self) -> &[u8; L] { + return &self.0 + } } impl Drop for Secret { diff --git a/network-hypervisor/src/util/mod.rs b/network-hypervisor/src/util/mod.rs index d3bfedbba..8fe878dbf 100644 --- a/network-hypervisor/src/util/mod.rs +++ b/network-hypervisor/src/util/mod.rs @@ -5,7 +5,7 @@ pub mod gate; pub(crate) const ZEROES: [u8; 64] = [0_u8; 64]; #[inline(always)] -pub(crate) unsafe fn equal_bytes(a: *const u8, b: *const u8, l: usize) -> bool { +pub(crate) unsafe fn equal_ptr(a: *const u8, b: *const u8, l: usize) -> bool { for i in 0..l { if *a.offset(i as isize) != *b.offset(i as isize) { return false; @@ -14,22 +14,43 @@ pub(crate) unsafe fn equal_bytes(a: *const u8, b: *const u8, l: usize) -> bool { true } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_store_be_u16(i: u16, d: &mut [u8]) { +pub(crate) fn store_u16_be(i: u16, d: &mut [u8]) { + unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn store_u16_be(i: u16, d: &mut [u8]) { d[0] = (i >> 8) as u8; d[1] = i as u8; } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_store_be_u32(i: u32, d: &mut [u8]) { +pub(crate) fn store_u32_be(i: u32, d: &mut [u8]) { + unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn store_u32_be(i: u32, d: &mut [u8]) { d[0] = (i >> 24) as u8; d[1] = (i >> 16) as u8; d[2] = (i >> 8) as u8; d[3] = i as u8; } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_store_be_u64(i: u64, d: &mut [u8]) { +pub(crate) fn store_u64_be(i: u64, d: &mut [u8]) { + unsafe { *d.as_mut_ptr().cast::() = i.to_be() }; +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn store_u64_be(i: u64, d: &mut [u8]) { d[0] = (i >> 56) as u8; d[1] = (i >> 48) as u8; d[2] = (i >> 40) as u8; @@ -40,18 +61,39 @@ pub(crate) fn integer_store_be_u64(i: u64, d: &mut [u8]) { d[7] = i as u8; } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_load_be_u16(d: &[u8]) -> u16 { +pub(crate) fn load_u16_be(d: &[u8]) -> u16 { + unsafe { *d.as_ptr().cast::() } +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn load_u16_be(d: &[u8]) -> u16 { (d[0] as u16) << 8 | (d[1] as u16) } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_load_be_u32(d: &[u8]) -> u32 { +pub(crate) fn load_u32_be(d: &[u8]) -> u32 { + unsafe { *d.as_ptr().cast::() } +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn load_u32_be(d: &[u8]) -> u32 { (d[0] as u32) << 24 | (d[1] as u32) << 16 | (d[2] as u32) << 8 | (d[3] as u32) } +#[cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))] #[inline(always)] -pub(crate) fn integer_load_be_u64(d: &[u8]) -> u64 { +pub(crate) fn load_u64_be(d: &[u8]) -> u64 { + unsafe { *d.as_ptr().cast::() } +} + +#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] +#[inline(always)] +pub(crate) fn load_u64_be(d: &[u8]) -> u64 { (d[0] as u64) << 56 | (d[1] as u64) << 48 | (d[2] as u64) << 40 | (d[3] as u64) << 32 | (d[4] as u64) << 24 | (d[5] as u64) << 16 | (d[6] as u64) << 8 | (d[7] as u64) } diff --git a/network-hypervisor/src/vl1/buffer.rs b/network-hypervisor/src/vl1/buffer.rs index 951fae1fd..6b056c841 100644 --- a/network-hypervisor/src/vl1/buffer.rs +++ b/network-hypervisor/src/vl1/buffer.rs @@ -193,7 +193,7 @@ impl Buffer { let end = ptr + 2; if end <= L { self.0 = end; - crate::util::integer_store_be_u16(i, &mut self.1[ptr..end]); + crate::util::store_u16_be(i, &mut self.1[ptr..end]); Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) @@ -207,7 +207,7 @@ impl Buffer { let end = ptr + 4; if end <= L { self.0 = end; - crate::util::integer_store_be_u32(i, &mut self.1[ptr..end]); + crate::util::store_u32_be(i, &mut self.1[ptr..end]); Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) @@ -221,7 +221,7 @@ impl Buffer { let end = ptr + 8; if end <= L { self.0 = end; - crate::util::integer_store_be_u64(i, &mut self.1[ptr..end]); + crate::util::store_u64_be(i, &mut self.1[ptr..end]); Ok(()) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) @@ -330,7 +330,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::integer_load_be_u16(&self.1[ptr..end])) + Ok(crate::util::load_u16_be(&self.1[ptr..end])) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } @@ -344,7 +344,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::integer_load_be_u32(&self.1[ptr..end])) + Ok(crate::util::load_u32_be(&self.1[ptr..end])) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } @@ -358,7 +358,7 @@ impl Buffer { debug_assert!(end <= L); if end <= self.0 { *cursor = end; - Ok(crate::util::integer_load_be_u64(&self.1[ptr..end])) + Ok(crate::util::load_u64_be(&self.1[ptr..end])) } else { Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, OVERFLOW_ERR_MSG)) } diff --git a/network-hypervisor/src/vl1/dictionary.rs b/network-hypervisor/src/vl1/dictionary.rs index aec5d8d14..4e7b23b51 100644 --- a/network-hypervisor/src/vl1/dictionary.rs +++ b/network-hypervisor/src/vl1/dictionary.rs @@ -11,13 +11,6 @@ use crate::util::hex::HEX_CHARS; #[derive(Clone, PartialEq, Eq)] pub struct Dictionary(BTreeMap>); -impl Default for Dictionary { - #[inline(always)] - fn default() -> Self { - Self(BTreeMap::new()) - } -} - fn write_escaped(b: &[u8], w: &mut W) -> std::io::Result<()> { let mut i = 0_usize; let l = b.len(); diff --git a/network-hypervisor/src/vl1/fragmentedpacket.rs b/network-hypervisor/src/vl1/fragmentedpacket.rs index 615594400..d5038febb 100644 --- a/network-hypervisor/src/vl1/fragmentedpacket.rs +++ b/network-hypervisor/src/vl1/fragmentedpacket.rs @@ -3,6 +3,10 @@ use crate::vl1::constants::FRAGMENT_COUNT_MAX; /// Packet fragment re-assembler and container. /// This is only used in the receive path. +/// +/// Performance note: PacketBuffer is Pooled which is NotNull<*mut Buffer>. +/// That means Option is just a pointer, since NotNull permits the +/// compiler to optimize out any additional state in Option. pub(crate) struct FragmentedPacket { pub ts_ticks: i64, pub frags: [Option; FRAGMENT_COUNT_MAX], diff --git a/network-hypervisor/src/vl1/identity.rs b/network-hypervisor/src/vl1/identity.rs index 315d47e63..d9289f4f8 100644 --- a/network-hypervisor/src/vl1/identity.rs +++ b/network-hypervisor/src/vl1/identity.rs @@ -1,19 +1,23 @@ -use std::alloc::{Layout, dealloc, alloc}; -use std::ptr::{slice_from_raw_parts_mut, slice_from_raw_parts}; -use std::io::Write; -use std::str::FromStr; -use std::convert::TryInto; +use std::alloc::{alloc, dealloc, Layout}; use std::cmp::Ordering; +use std::convert::TryInto; +use std::hash::{Hash, Hasher}; +use std::io::Write; +use std::ptr::{slice_from_raw_parts, slice_from_raw_parts_mut}; +use std::str::FromStr; -use crate::vl1::Address; -use crate::vl1::buffer::Buffer; -use crate::crypto::c25519::{C25519_PUBLIC_KEY_SIZE, ED25519_PUBLIC_KEY_SIZE, C25519_SECRET_KEY_SIZE, ED25519_SECRET_KEY_SIZE, C25519KeyPair, Ed25519KeyPair, ED25519_SIGNATURE_SIZE}; -use crate::crypto::p521::{P521KeyPair, P521PublicKey, P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521_SECRET_KEY_SIZE}; -use crate::crypto::hash::{SHA384, SHA512, SHA512_HASH_SIZE}; use crate::crypto::balloon; +use crate::crypto::c25519::*; +use crate::crypto::hash::*; +use crate::crypto::p521::*; use crate::crypto::salsa::Salsa; use crate::crypto::secret::Secret; use crate::error::InvalidFormatError; +use crate::vl1::Address; +use crate::vl1::buffer::Buffer; +use crate::vl1::constants::PACKET_SIZE_MAX; + +use concat_arrays::concat_arrays; // Memory parameter for V0 address derivation work function. const V0_IDENTITY_GEN_MEMORY: usize = 2097152; @@ -22,10 +26,11 @@ const V0_IDENTITY_GEN_MEMORY: usize = 2097152; const V1_BALLOON_SPACE_COST: usize = 16384; const V1_BALLOON_TIME_COST: usize = 3; const V1_BALLOON_DELTA: usize = 3; - const V1_BALLOON_SALT: &'static [u8] = b"zt_id_v1"; -pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = ED25519_SIGNATURE_SIZE + 32; +const V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE: usize = C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA384_HASH_SIZE; + +pub const IDENTITY_TYPE_0_SIGNATURE_SIZE: usize = 96; pub const IDENTITY_TYPE_1_SIGNATURE_SIZE: usize = P521_ECDSA_SIGNATURE_SIZE + ED25519_SIGNATURE_SIZE; #[derive(Copy, Clone)] @@ -47,7 +52,7 @@ pub struct Identity { address: Address, c25519: [u8; C25519_PUBLIC_KEY_SIZE], ed25519: [u8; ED25519_PUBLIC_KEY_SIZE], - v1: Option<(P521PublicKey, P521PublicKey, [u8; P521_ECDSA_SIGNATURE_SIZE], [u8; SHA512_HASH_SIZE])>, + v1: Option<(P521PublicKey, P521PublicKey, [u8; P521_ECDSA_SIGNATURE_SIZE], [u8; SHA384_HASH_SIZE])>, secrets: Option, } @@ -130,22 +135,15 @@ impl Identity { let ed25519 = Ed25519KeyPair::generate(false); let p521_ecdh = P521KeyPair::generate(false).unwrap(); let p521_ecdsa = P521KeyPair::generate(false).unwrap(); - let c25519_pub_bytes = c25519.public_bytes(); let ed25519_pub_bytes = ed25519.public_bytes(); - - let mut signing_buf = [0_u8; C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE]; - signing_buf[0..C25519_PUBLIC_KEY_SIZE].copy_from_slice(&c25519_pub_bytes); - signing_buf[C25519_PUBLIC_KEY_SIZE..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)].copy_from_slice(&ed25519_pub_bytes); - signing_buf[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdh.public_key_bytes()); - signing_buf[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice(p521_ecdsa.public_key_bytes()); - + let sign_buf: [u8; C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE] = concat_arrays!(c25519_pub_bytes, ed25519_pub_bytes, *p521_ecdh.public_key_bytes(), *p521_ecdsa.public_key_bytes()); loop { // ECDSA is a randomized signature algorithm, so each signature will be different. - let sig = p521_ecdsa.sign(&signing_buf).unwrap(); + let sig = p521_ecdsa.sign(&sign_buf).unwrap(); let bh = balloon::hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&sig, V1_BALLOON_SALT); if bh[0] < 7 { - let addr = Address::from_bytes(&bh[59..64]).unwrap(); + let addr = Address::from_bytes(&bh[43..48]).unwrap(); if addr.is_valid() { let p521_ecdh_pub = p521_ecdh.public_key().clone(); let p521_ecdsa_pub = p521_ecdsa.public_key().clone(); @@ -166,6 +164,7 @@ impl Identity { } /// Generate a new identity. + /// /// This is time consuming due to the one-time anti-collision proof of work required /// to generate an address corresponding with a set of identity keys. V0 identities /// take tens to hundreds of milliseconds on a typical 2020 system, while V1 identites @@ -205,6 +204,7 @@ impl Identity { } /// Locally validate this identity. + /// /// This can take a few milliseconds, especially on slower systems. V0 identities are slower /// to fully validate than V1 identities. pub fn locally_validate(&self) -> bool { @@ -232,7 +232,7 @@ impl Identity { signing_buf[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)].copy_from_slice((*p521).1.public_key_bytes()); if (*p521).1.verify(&signing_buf, &(*p521).2) { let bh = balloon::hash::<{ V1_BALLOON_SPACE_COST }, { V1_BALLOON_TIME_COST }, { V1_BALLOON_DELTA }>(&(*p521).2, V1_BALLOON_SALT); - (bh[0] < 7) && bh.eq(&(*p521).3) && Address::from_bytes(&bh[59..64]).unwrap().eq(&self.address) + (bh[0] < 7) && bh.eq(&(*p521).3) && Address::from_bytes(&bh[43..48]).unwrap().eq(&self.address) } else { false } @@ -243,6 +243,7 @@ impl Identity { } /// Execute ECDH key agreement and return SHA384(shared secret). + /// /// If both keys are type 1, key agreement is done with NIST P-521. Otherwise it's done /// with Curve25519. None is returned if there is an error such as this identity missing /// its secrets or a key being invalid. @@ -281,6 +282,7 @@ impl Identity { } /// Sign this message with this identity. + /// /// Signature is performed using ed25519 EDDSA or NIST P-521 ECDSA depending on the identity /// type. None is returned if this identity lacks secret keys or another error occurs. pub fn sign(&self, msg: &[u8]) -> Option> { @@ -404,7 +406,7 @@ impl Identity { c25519: C25519KeyPair::from_bytes(c25519_public_bytes, c25519_secret_bytes).unwrap(), ed25519: Ed25519KeyPair::from_bytes(ed25519_public_bytes, ed25519_secret_bytes).unwrap(), v1: None, - }) + }), }) } else if secrets_len == 0 { Ok(Identity { @@ -412,7 +414,7 @@ impl Identity { c25519: c25519_public_bytes.clone(), ed25519: ed25519_public_bytes.clone(), v1: None, - secrets: None + secrets: None, }) } else { std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized scret key length (type 0)")) @@ -423,7 +425,7 @@ impl Identity { let p521_ecdh_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; let p521_ecdsa_public_bytes = buf.read_bytes_fixed::<{ P521_PUBLIC_KEY_SIZE }>(cursor)?; let p521_signature = buf.read_bytes_fixed::<{ P521_ECDSA_SIGNATURE_SIZE }>(cursor)?; - let bh_digest = buf.read_bytes_fixed::<{ SHA512_HASH_SIZE }>(cursor)?; + let bh_digest = buf.read_bytes_fixed::<{ SHA384_HASH_SIZE }>(cursor)?; let secrets_len = buf.read_u8(cursor)?; if secrets_len == (C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE) as u8 { let c25519_secret_bytes = buf.read_bytes_fixed::<{ C25519_SECRET_KEY_SIZE }>(cursor)?; @@ -434,20 +436,20 @@ impl Identity { address: addr, c25519: c25519_public_bytes.clone(), ed25519: ed25519_public_bytes.clone(), - v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest. clone())), + v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest.clone())), secrets: Some(IdentitySecrets { c25519: C25519KeyPair::from_bytes(c25519_public_bytes, c25519_secret_bytes).unwrap(), ed25519: Ed25519KeyPair::from_bytes(ed25519_public_bytes, ed25519_secret_bytes).unwrap(), v1: Some((P521KeyPair::from_bytes(p521_ecdh_public_bytes, p521_ecdh_secret_bytes).unwrap(), P521KeyPair::from_bytes(p521_ecdsa_public_bytes, p521_ecdsa_secret_bytes).unwrap())), - }) + }), }) } else if secrets_len == 0 { Ok(Identity { address: addr, c25519: c25519_public_bytes.clone(), ed25519: ed25519_public_bytes.clone(), - v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest. clone())), - secrets: None + v1: Some((P521PublicKey::from_bytes(p521_ecdh_public_bytes).unwrap(), P521PublicKey::from_bytes(p521_ecdsa_public_bytes).unwrap(), p521_signature.clone(), bh_digest.clone())), + secrets: None, }) } else { std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid secret key length (type 1)")) @@ -459,14 +461,13 @@ impl Identity { /// Get this identity in byte array format. pub fn marshal_to_bytes(&self, include_private: bool) -> Vec { - let mut buf: Buffer<2048> = Buffer::new(); + let mut buf: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); self.marshal(&mut buf, include_private).expect("overflow"); buf.as_bytes().to_vec() } /// Unmarshal an identity from a byte slice. - /// On success the identity and the number of bytes actually read from the slice are - /// returned. + /// On success the identity and the number of bytes actually read from the slice are returned. pub fn unmarshal_from_bytes(bytes: &[u8]) -> std::io::Result<(Identity, usize)> { let mut cursor: usize = 0; let id = Self::unmarshal(&Buffer::<2048>::from_bytes(bytes)?, &mut cursor)?; @@ -477,15 +478,10 @@ impl Identity { pub fn to_secret_string(&self) -> String { self.secrets.as_ref().map_or_else(|| self.to_string(), |secrets| { secrets.v1.as_ref().map_or_else(|| { - format!("{}:{}{}", self.to_string(), crate::util::hex::to_string(secrets.c25519.secret_bytes().as_ref()), crate::util::hex::to_string(secrets.ed25519.secret_bytes().as_ref())) + format!("{}:{}{}", self.to_string(), crate::util::hex::to_string(secrets.c25519.public_bytes().as_ref()), crate::util::hex::to_string(secrets.ed25519.secret_bytes().as_ref())) }, |p521_secret| { - let mut secret_key_blob: Vec = Vec::new(); - secret_key_blob.reserve(C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE); - let _ = secret_key_blob.write_all(&secrets.c25519.secret_bytes().as_ref()); - let _ = secret_key_blob.write_all(&secrets.ed25519.secret_bytes().as_ref()); - let _ = secret_key_blob.write_all(p521_secret.0.secret_key_bytes().as_ref()); - let _ = secret_key_blob.write_all(p521_secret.1.secret_key_bytes().as_ref()); - format!("{}:{}", self.to_string(), base64::encode_config(secret_key_blob.as_slice(), base64::URL_SAFE_NO_PAD)) + let secrets_concat: [u8; C25519_SECRET_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE + P521_SECRET_KEY_SIZE] = concat_arrays!(secrets.c25519.secret_bytes().0, secrets.ed25519.secret_bytes().0, p521_secret.0.secret_key_bytes().0, p521_secret.1.secret_key_bytes().0); + format!("{}:{}", self.to_string(), base64::encode_config(secrets_concat, base64::URL_SAFE_NO_PAD)) }) }) } @@ -496,15 +492,8 @@ impl ToString for Identity { self.v1.as_ref().map_or_else(|| { format!("{:0>10x}:0:{}{}", self.address.to_u64(), crate::util::hex::to_string(&self.c25519), crate::util::hex::to_string(&self.ed25519)) }, |p521_public| { - let mut public_key_blob: Vec = Vec::new(); - public_key_blob.reserve(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA512_HASH_SIZE); - let _ = public_key_blob.write_all(&self.c25519); - let _ = public_key_blob.write_all(&self.ed25519); - let _ = public_key_blob.write_all(p521_public.0.public_key_bytes()); - let _ = public_key_blob.write_all(p521_public.1.public_key_bytes()); - let _ = public_key_blob.write_all(&p521_public.2); - let _ = public_key_blob.write_all(&p521_public.3); - format!("{:0>10x}:1:{}", self.address.to_u64(), base64::encode_config(public_key_blob.as_slice(), base64::URL_SAFE_NO_PAD)) + let public_concat: [u8; C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA384_HASH_SIZE] = concat_arrays!(self.c25519, self.ed25519, *((*p521_public).0.public_key_bytes()), *((*p521_public).1.public_key_bytes()), (*p521_public).2, (*p521_public).3); + format!("{:0>10x}:1:{}", self.address.to_u64(), base64::encode_config(public_concat, base64::URL_SAFE_NO_PAD)) }) } } @@ -551,7 +540,7 @@ impl FromStr for Identity { let public_keys_and_sig = base64::decode_config(fields[2], base64::URL_SAFE_NO_PAD); if public_keys_and_sig.is_ok() { let public_keys_and_sig = public_keys_and_sig.unwrap(); - if public_keys_and_sig.len() == (C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA512_HASH_SIZE) { + if public_keys_and_sig.len() == V1_PUBLIC_KEYS_SIGNATURE_AND_POW_SIZE { let p521_ecdh_public = P521PublicKey::from_bytes(&public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)]); let p521_ecdsa_public = P521PublicKey::from_bytes(&public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)]); if p521_ecdh_public.is_some() && p521_ecdsa_public.is_some() { @@ -575,6 +564,7 @@ impl FromStr for Identity { return Err(InvalidFormatError); } } else { + println!("foo"); return Err(InvalidFormatError); } } else { @@ -589,7 +579,7 @@ impl FromStr for Identity { p521_ecdh_public, p521_ecdsa_public, public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)].try_into().unwrap(), - public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)..(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE + SHA512_HASH_SIZE)].try_into().unwrap() + public_keys_and_sig.as_slice()[(C25519_PUBLIC_KEY_SIZE + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_ECDSA_SIGNATURE_SIZE)..].try_into().unwrap() )), secrets, }); @@ -632,9 +622,17 @@ impl Ord for Identity { } } +impl Hash for Identity { + #[inline(always)] + fn hash(&self, state: &mut H) { + state.write_u64(self.address.to_u64()); + } +} + #[cfg(test)] mod tests { use std::str::FromStr; + use crate::vl1::identity::{Identity, Type}; #[test] @@ -697,10 +695,10 @@ mod tests { panic!("invalid signature verification succeeded"); } for good_id in [ - "9c6095d8e3:1:ZhjuMgE1EP6rfmCW5bV3WbnSNq1EthIEIpvSU_-wCxdBdzzBvc2Hz5ZHXTNie5mxAtxqzvwO6oDhd1zyLfd7xgDSbSpiBO9gGoHjFeJN_MAoH8NJuHOLvfgLO4lb4ld8s9muhx8qVn7ZFgMki0sYJfYNYoTqfwN5lVmRSiSN8zzlHQDCQmSMLwQDaKvYwtIsqvPLgDKeWX7brJrW5hF8PpVapnuP8uJr4efXer5s5_fIlCaI2K-KwjbQ0IfsQrcgC-8JRgGog7KDgEaNjiqS74bFSdpemhYEEK9xWhi7SGFDAEVHJ58LoTsJc6yJ9ISThOQQJ_6DkWDr5vY77GBqmNZI0bPuLQA8H5NgdpAed4yVVE3Q0vYjkklC8zmjfdA05W7kV5dvbOi45qh8I7li2JI47CB1FZ-3B672onKCdoaPSiQzi4GAfwGy71MStTYyiADUKGog9nKAMpmg4EOgDfU6gXCvK9VYlTRY7FIvYVv1TQfNe4EwBnLYG0XRcpl6MQwgeFUvyTQ57ACKfwVdVlVdBwXaglfYk2sFu7E63VrMi1Lh-tyPhZgs4D1GIIONt6Z8UY0IMGwfhr1uZu_2M_evxC7w-4Av6V9T5AbNCg2qjNvab6iZFPQxE8gsT0cnacq6SoT1jdP9kl-6TXqg_f095c0Lbds7xaKd9A0k4HJPAhX87pMynGCV2OM", - "23061c9924:1:Wic-UHeYc9-ri2PMLMxoaXpq3Vl7erG79ZD69HvEVjmtQ8Dtbj-kW6LY2fwANEy4EGl1Oq1z6SLI5SbowRaPJQCboc6YqOA6ZQZXPn4sulnLNoEWhclA1Fk5HMEKG1tptJ3D321CNJzb-McaIErA2l33iQLOgLWpcJSuCIapAl_tCgCH03OtJkexLZyfjhlzarWd8NOmzt6z963UMnNo3H3w9StyiNMDvCCUs2j_rP0QOLPH2mlUfw7Z1FbpezBxKmmxxQFq6zgVyDKyXPXLaLfYHJ4wj2uTBaQ_5qZGT3TQMy9ApTE-ywl9K6aM4uMXelO_dqhaoSOb4jWYRLnzrSXBcxtSlgD9xTI2rTTdiYaW8udvFtc_1sB0DwGfXx2E2W8cX2PYdhYxco5tUssuKVyRNuYr3FfnAWvhNKfooNNnuaJaFh6gZwDH1opFWlIz9AfgxeDAJ7F4Bt80O-H0Fxa4OEqyWX944FG5KiIFRP9lHlrkTa2E2cGH8lsa130fhCFreI4WU0qJFwEPHo_H39ukEzrkQhQtHWxsVg7ypokrSfD57vstxU6TCNyAA-6dPTpFZJ0gr848cj5pHPdWs0RaiohaDMYS8V4ZCALwEGoDptswgZsJauyU-GRKteKCEJ6GYBHFLDldlQtA99R70Em2RhlaW2Ic4_MFK8_-Z4ZEP4CsIgCWIwYcmSQ", - "dcc7eb3f14:1:rw1eHn1xivAX8zs2yrJLjYWM35rvPEkWGN1ln63BMwL9mVMmjN1kxv_bY3LDwzhVgc6pyL7OsXkhHQDQ2m4q5AGFv1yZLy5LghaA37WX5ayCG2MeD01C243kjwfwE4VOm7576dPtghGXX8EGhtfspHyW8enAJHL9CnBAM8ACLYwakgChp8EzynwsnnsQlW6MTQdxFSZjq0OLmBLDWvCuhcABWdT1iPP9s9ngFah1xTaEiHALsS3M0HC1-vvbmwKzT6-sWwCPqF-XEtaIMNhTzZ0d1BluXj1KJvH6K5vD8o2fvyzHelezA8br7HFdT06DE9_Gh2xaJoaFlUfOdtajEhOiwirI5wBREUjd0q96EgxLKgGDKFrYYoXAs6MbXlc11jYs9RuMu90kAH4KNIsmETPGp5Qk8U3-2I_7lw8xFx3fb70PblNoJAD1yisM70AnpwZSaTr5bWsfV7JiglCOy1F1qFm7U5X8dseZ0d0oLGX4byLDl7lnNgM3KMQd4IY3dljZevUXc58M_wH0b7VecirJr_DRhAacHkJcsnKfqOfDM8yn8ot9N2K5rlLxHKWwSK1p7EO2kwdlBLuR_DzGTJSm2jj8vmk-J6jxcQCYvHvSJTx1j0Uk9w4HIseiw2tSnww9B3dEnlesvoxb2xWLMVvNXSjDuHqqh80gAbg0Ts14bbqZXk__3MfrPxQ", - "cf0399c634:1:aSVlK4GVn9KTqf62Qx1LZpkxQL86xc7LapOyC6sbR1eTN1SKO3IJ742sfFufDa133rAUNXDVC2zNA0gRlen2SgCTCC18xqyhtsry-LLwOVSER5TSrlSL7EfDJ4Q9VFIPsj1Vo3UXrPGXur1k1K3XRStEQFdBuy0lW0Kw2fgyWVnTzwASCpOKg3Fgbv8z7XiUlPsPK1ZVbIyXVX4KFkyxnOrnSWALUHlifXRWrGAjZQhqSaU_vMrEVYSJYkFy7pLJKCQeWwFKZIZFure-5YP630qwxeY9pyrez7TalGfZFTCFPXv6BgWhQ1CfkPtJPY_1-amzRJn2IvYm3ci5OiwAcdMhSgl1VgC6dqcnz7HtuovppFTJ5FOxUYhD4YemZ376UZT7VrvKBho-mwBY2V0ZVtO_hgYg3yJbeX6wjv9HXnXsrvl5uuY04ACtZAtczPvkY0Roh2T18OdhVqytZD3QNGiirVb7GE0_SexQ2lcF0aWEsr6bS3iGhZFch1G8SlV4tag2ia-mMcD4HgFOyzlOwK5Paet4i1LfXuVG9vz-mjFoaND0here6s74NlOvizJvamwiid0k-kp4rYbsot0wGRYpNE0NtooBmp64VAFSUgGKr0OX3TRWTdiNFqBf28m8Cba-WjLSirLYZDMo83PWOHMzViHS6IIXo5mqFWJSXds9cx9gYeU3zwOZxjQ" + "55ecc4bb1d:1:EEefUe82UfSkeHGroZhgZKp_V3asFzcTct8faJOIiFk16MB6nXNEAk1xjbI9Otjtvudq49JOWR9IRSZuom0VugHW0TDg9z14_8F1L39M9y_6rxhO4oKdpcmN_0dUxOtL8t7dw4PfSS3sKh-rrwWut01hoewy6-J42nJ_hbe7q_nWFABt4BHfWp3qqwYvMosYLquwUD1BJRnF_rIOEX82YhN84eFnntQTqnMMS1M8ILxe5-A7naowp1IxsccD7WW1a3f_BQAmgZmRfWAJqaTERQ2qJtCR6cixGid4raka_YgySFcx6BDi43Okm3rwO3prLjbr_J4d97BXbINKOAEms6AAxO75pwABxMmJVO1VRnXP10Y22XWMWZiN39sDVGzXCD3uzGptr5B5dBJPTEwyK1abxbwiv30hZE9bzLNgsuX12KsHb-yvMQEYQ_NBwGgMtV0fWcc3vPadEqdO7PRofOiAft9CPTrtLsO9AI88PMNId72plYHzYkCvtnnttgHLNqJwOIoOxd0xxQHLz8BMfcTm7t9fPHl6zPOtmakAmHaSQdlMpTqrpR7NL0awixRBFauFkrpD7v0zWkjP5JpUUDK1smCxAxan7oTlkwQou_kY6Ac65-cROf24xyUit1k1IzS1OiwSmWuGplEJxUCGORBAytS9WXFV7MS7HQ", + "8b04dcccc5:1:G3esWdhJPDfE4yq9N_oilC_Der7S_iz0ytCA1uvO1RGjp_EDnqHfTO48rYhR2jZ7x3ibNyv_ySHyXvyqqmBvtABS5KdLn-fBCY2YrhH4o-3sAWffqTTMHthlFC8iIwtIh3uWDSbPAZLxRnsKQQSx5ndid31MDIdCTo4hEa-bjtXodQCoMDqOhEQHVb-abI9ljT7rOs1aWyYHI7l6lrvuR9IEV7xt2S0Z1Kdky9jnJXjBDq8H8HipLyFPc_FsURMlT5l1YgDwAFmmEAE43teNv8jZBSBYlQ4fokG-2OLXBtuKQBZ6Sd8Els3YEgXhn2TJXQIK0lPH5lKnEjH5IaDJ8uAxvKrs4ABYmd4OYRCHohHDYOzlzoRFTT-57SsWSfVdtGioRFVwTcB8sAUIKumWCpVsD18zaFXDNwn4nfkvhvBoKlbCGYiERgErKF7_t0YF5nXy2V5LdPPLPVq1KsVR2kMmQyILxCl5PWyKv9dgdos_69MmTSuCA28CZ6lcJJ8ZmCC-v1lUZSqmrwHrYzf9BX4YBBrH2ZjtoYtHzgETagH-_7Tll04Ug9KFUlQgerDMWhhPiMsILg4JDpGM0XHvPMqL8TU4KIiNGet9dga3ONkbrZaUpn-dEJ_3yL1D6BDbgoLex8fW3ejm5SOkNhtqH0QPSFJDKyyLBNzMxQ", + "f1544578e8:1:fOBzH-NWHagNRi9qXKCPifYuQoACy4jzB89Nhntxcgtz3ovvrmMSa7hPrSrZlX7iIfDefO2IVZSKKswDsOoRigBp-ZqIPLg_2MbBaFfYoK6K01s7eDvE1VuTLen0phB-hq6OAefSvdg4qlZL8Ti9h0oFTZP_-6L4vtKulo32aShVYwDHsO9qa0Ven_Ha6BO4Ef6UyZW2fWIXRx43QclV7I9Frzdeoi5i_x78DN_O7CIbWt3s2LLIfcxP-UXgpdSZEXKBggECzikEtf9mx5Wmaz5D_C6erXD9AT5eijkD7xBFW1cFYQwiyHwJYJgGF76g4WnSz5Fs_3klR8iOeHDAPaRNAxxfjgAanMzOPVNEa20wtB40kp5o1xYebr312KoT19p7A-7KvmR42_Wseyai2q0tbm0h7ugUk9oOOAmS91g1jiGL_PFdQgHNmGH93GyhSozgkaTAkmCPXVMBWFBkFLDgf5dF7r5pRzxBou4OaWmmxBHGON1BBKwNiojOrAPrtOmvD1d7FLEV3ADrMA6B4nBAco0_LeC43GBIXqMMEJKm7xH-JPDg3HsKOgQUYSIyTquGNT3egZTvb9bTh421XvQJnQ6kazSrsTF9tgS2Fxryllj-QcfbWpyN_Cohrlf3GPMXTQsalFlSmnmAhcWt2YFrHjKdhvbxVEV46A", + "ebcc1dfe97:1:6l1EtROT-jp-bSse6vajfbi3EDaWJWjBt97Fp5KETjQNhsla79wLFbaSR5ccjqt9tT5lqlpdqLyoeaI8HIP2XgEQbUMmk5GgLNCwLSyTVkAui9HO01lghRxQll7AhptPOD5CjYQC8qnhSfNaUkLkzqjeEjgclDx7oMclrVDkWTvyegBK6Q_rUkfseWRRfe6zA7wwrOHQ2jeXOY8QbeyAqCRWtXKr16zisNhyuYbeP00kzUAkZvrWey4mvzp7ruJgARsBVAF5FRcq8JRqkF5E99MNe-T4mSPbQZKF40saK4N2p_zzp3vr-Wq8IkevPFn_5gbk8cwINC30Bz_qz45xRAhLX6emigA8EUWzeAW4091wU2wQoXrzmhil9b3weBSEfeLvlycnmwfGMDWo8jaW29aqz5Ix6U7V3hu_kiA4FgzJEIxUYu3L4wBsPI2zYUMZqXBpcPvh8GGzIzgOGLRoi_3FhNtGEDTn8ajCMWUmfpIMiwOw7-iWjUwEZH42Ch-0c6j4RiezD-FphACiwtDZBs5llcBPqmRo8cHJ1DwjFHQCXn5Y0mAE4WgfkSUPDE5h9uXO02ic843qUnd0m-HLVmwhxW9RDP4-FSBPCAA-3K37hyZw3mtGBfKll4NJmQb-gMBRWG_C89grkTJr-QYbekVbtHm8lG7rzB3-lw" ] { let id = Identity::from_str(good_id).unwrap(); if !id.locally_validate() { @@ -713,10 +711,10 @@ mod tests { } } for bad_id in [ - "9c6005d8e3:1:ZhjuMgE1EP6rfmCW5bV3WbnSNq1EthIEIpvSU_-wCxdBdzzBvc2Hz5ZHXTNie5mxAtxqzvwO6oDhd1zyLfd7xgDSbSpiBO9gGoHjFeJN_MAoH8NJuHOLvfgLO4lb4ld8s9muhx8qVn7ZFgMki0sYJfYNYoTqfwN5lVmRSiSN8zzlHQDCQmSMLwQDaKvYwtIsqvPLgDKeWX7brJrW5hF8PpVapnuP8uJr4efXer5s5_fIlCaI2K-KwjbQ0IfsQrcgC-8JRgGog7KDgEaNjiqS74bFSdpemhYEEK9xWhi7SGFDAEVHJ58LoTsJc6yJ9ISThOQQJ_6DkWDr5vY77GBqmNZI0bPuLQA8H5NgdpAed4yVVE3Q0vYjkklC8zmjfdA05W7kV5dvbOi45qh8I7li2JI47CB1FZ-3B672onKCdoaPSiQzi4GAfwGy71MStTYyiADUKGog9nKAMpmg4EOgDfU6gXCvK9VYlTRY7FIvYVv1TQfNe4EwBnLYG0XRcpl6MQwgeFUvyTQ57ACKfwVdVlVdBwXaglfYk2sFu7E63VrMi1Lh-tyPhZgs4D1GIIONt6Z8UY0IMGwfhr1uZu_2M_evxC7w-4Av6V9T5AbNCg2qjNvab6iZFPQxE8gsT0cnacq6SoT1jdP9kl-6TXqg_f095c0Lbds7xaKd9A0k4HJPAhX87pMynGCV2OM", - "23061c9934:1:Wic-UHeYc9-ri2PMLMxoaXpq3Vl7erG79ZD69HvEVjmtQ8Dtbj-kW6LY2fwANEy4EGl1Oq1z6SLI5SbowRaPJQCboc6YqOA6ZQZXPn4sulnLNoEWhclA1Fk5HMEKG1tptJ3D321CNJzb-McaIErA2l33iQLOgLWpcJSuCIapAl_tCgCH03OtJkexLZyfjhlzarWd8NOmzt6z963UMnNo3H3w9StyiNMDvCCUs2j_rP0QOLPH2mlUfw7Z1FbpezBxKmmxxQFq6zgVyDKyXPXLaLfYHJ4wj2uTBaQ_5qZGT3TQMy9ApTE-ywl9K6aM4uMXelO_dqhaoSOb4jWYRLnzrSXBcxtSlgD9xTI2rTTdiYaW8udvFtc_1sB0DwGfXx2E2W8cX2PYdhYxco5tUssuKVyRNuYr3FfnAWvhNKfooNNnuaJaFh6gZwDH1opFWlIz9AfgxeDAJ7F4Bt80O-H0Fxa4OEqyWX944FG5KiIFRP9lHlrkTa2E2cGH8lsa130fhCFreI4WU0qJFwEPHo_H39ukEzrkQhQtHWxsVg7ypokrSfD57vstxU6TCNyAA-6dPTpFZJ0gr848cj5pHPdWs0RaiohaDMYS8V4ZCALwEGoDptswgZsJauyU-GRKteKCEJ6GYBHFLDldlQtA99R70Em2RhlaW2Ic4_MFK8_-Z4ZEP4CsIgCWIwYcmSQ", - "dcc7eb3f14:1:rw1eHn1xivAX8zs1yrJLjYWM35rvPEkWGN1ln63BMwL9mVMmjN1kxv_bY3LDwzhVgc6pyL7OsXkhHQDQ2m4q5AGFv1yZLy5LghaA37WX5ayCG2MeD01C243kjwfwE4VOm7576dPtghGXX8EGhtfspHyW8enAJHL9CnBAM8ACLYwakgChp8EzynwsnnsQlW6MTQdxFSZjq0OLmBLDWvCuhcABWdT1iPP9s9ngFah1xTaEiHALsS3M0HC1-vvbmwKzT6-sWwCPqF-XEtaIMNhTzZ0d1BluXj1KJvH6K5vD8o2fvyzHelezA8br7HFdT06DE9_Gh2xaJoaFlUfOdtajEhOiwirI5wBREUjd0q96EgxLKgGDKFrYYoXAs6MbXlc11jYs9RuMu90kAH4KNIsmETPGp5Qk8U3-2I_7lw8xFx3fb70PblNoJAD1yisM70AnpwZSaTr5bWsfV7JiglCOy1F1qFm7U5X8dseZ0d0oLGX4byLDl7lnNgM3KMQd4IY3dljZevUXc58M_wH0b7VecirJr_DRhAacHkJcsnKfqOfDM8yn8ot9N2K5rlLxHKWwSK1p7EO2kwdlBLuR_DzGTJSm2jj8vmk-J6jxcQCYvHvSJTx1j0Uk9w4HIseiw2tSnww9B3dEnlesvoxb2xWLMVvNXSjDuHqqh80gAbg0Ts14bbqZXk__3MfrPxQ", - "cf0399c634:1:aSVlK4GVn9KTqf62Qx1LZpkxQL86xc7LapOyC5sbR1eTN1SKO3IJ742sfFufDa133rAUNXDVC2zNA0gRlen2SgCTCC18xqyhtsry-LLwOVSER5TSrlSL7EfDJ4Q9VFIPsj1Vo3UXrPGXur1k1K3XRStEQFdBuy0lW0Kw2fgyWVnTzwASCpOKg3Fgbv8z7XiUlPsPK1ZVbIyXVX4KFkyxnOrnSWALUHlifXRWrGAjZQhqSaU_vMrEVYSJYkFy7pLJKCQeWwFKZIZFure-5YP630qwxeY9pyrez7TalGfZFTCFPXv6BgWhQ1CfkPtJPY_1-amzRJn2IvYm3ci5OiwAcdMhSgl1VgC6dqcnz7HtuovppFTJ5FOxUYhD4YemZ376UZT7VrvKBho-mwBY2V0ZVtO_hgYg3yJbeX6wjv9HXnXsrvl5uuY04ACtZAtczPvkY0Roh2T18OdhVqytZD3QNGiirVb7GE0_SexQ2lcF0aWEsr6bS3iGhZFch1G8SlV4tag2ia-mMcD4HgFOyzlOwK5Paet4i1LfXuVG9vz-mjFoaND0here6s74NlOvizJvamwiid0k-kp4rYbsot0wGRYpNE0NtooBmp64VAFSUgGKr0OX3TRWTdiNFqBf28m8Cba-WjLSirLYZDMo83PWOHMzViHS6IIXo5mqFWJSXds9cx9gYeU3zwOZxjQ" + "65ecc4bb1d:1:EEefUe82UfSkeHGroZhgZKp_V3asFzcTct8faJOIiFk16MB6nXNEAk1xjbI9Otjtvudq49JOWR9IRSZuom0VugHW0TDg9z14_8F1L39M9y_6rxhO4oKdpcmN_0dUxOtL8t7dw4PfSS3sKh-rrwWut01hoewy6-J42nJ_hbe7q_nWFABt4BHfWp3qqwYvMosYLquwUD1BJRnF_rIOEX82YhN84eFnntQTqnMMS1M8ILxe5-A7naowp1IxsccD7WW1a3f_BQAmgZmRfWAJqaTERQ2qJtCR6cixGid4raka_YgySFcx6BDi43Okm3rwO3prLjbr_J4d97BXbINKOAEms6AAxO75pwABxMmJVO1VRnXP10Y22XWMWZiN39sDVGzXCD3uzGptr5B5dBJPTEwyK1abxbwiv30hZE9bzLNgsuX12KsHb-yvMQEYQ_NBwGgMtV0fWcc3vPadEqdO7PRofOiAft9CPTrtLsO9AI88PMNId72plYHzYkCvtnnttgHLNqJwOIoOxd0xxQHLz8BMfcTm7t9fPHl6zPOtmakAmHaSQdlMpTqrpR7NL0awixRBFauFkrpD7v0zWkjP5JpUUDK1smCxAxan7oTlkwQou_kY6Ac65-cROf24xyUit1k1IzS1OiwSmWuGplEJxUCGORBAytS9WXFV7MS7HQ", + "8b04dcccc5:1:G3esWdhJPDff4yq9N_oilC_Der7S_iz0ytCA1uvO1RGjp_EDnqHfTO48rYhR2jZ7x3ibNyv_ySHyXvyqqmBvtABS5KdLn-fBCY2YrhH4o-3sAWffqTTMHthlFC8iIwtIh3uWDSbPAZLxRnsKQQSx5ndid31MDIdCTo4hEa-bjtXodQCoMDqOhEQHVb-abI9ljT7rOs1aWyYHI7l6lrvuR9IEV7xt2S0Z1Kdky9jnJXjBDq8H8HipLyFPc_FsURMlT5l1YgDwAFmmEAE43teNv8jZBSBYlQ4fokG-2OLXBtuKQBZ6Sd8Els3YEgXhn2TJXQIK0lPH5lKnEjH5IaDJ8uAxvKrs4ABYmd4OYRCHohHDYOzlzoRFTT-57SsWSfVdtGioRFVwTcB8sAUIKumWCpVsD18zaFXDNwn4nfkvhvBoKlbCGYiERgErKF7_t0YF5nXy2V5LdPPLPVq1KsVR2kMmQyILxCl5PWyKv9dgdos_69MmTSuCA28CZ6lcJJ8ZmCC-v1lUZSqmrwHrYzf9BX4YBBrH2ZjtoYtHzgETagH-_7Tll04Ug9KFUlQgerDMWhhPiMsILg4JDpGM0XHvPMqL8TU4KIiNGet9dga3ONkbrZaUpn-dEJ_3yL1D6BDbgoLex8fW3ejm5SOkNhtqH0QPSFJDKyyLBNzMxQ", + "f1544578e8:1:fOBzH-NWHagNRi9qXKCPifYuQoACy4jzB89Nhntxcgtz3ovvrmMSa7hPrSrZlX7iIfDefO2IVZSKKswDsOoRigBp-ZqIPLg_2MbBaFfYoK6K01s7eDvE1VuTLen0phB-hq6OAefSvdg4qlZL8Ti9h0oFTZP_-6L4vtKulo32aShVYwDHsO9qa0Ven_Ha6BO4Ef6UyZW2fWIXRx43QclV7I9Frzdeoi5i_x78DN_O7CIbWt3s2LLIfcxP-UXgpdSZEXKBggECzikEtf9mx5Wmaz5D_C6erXD9AT5eijkD7xBFW1cFYQwiyHwJYJgGF76g4WnSz5Fs_3klR8iOeHDAPaRNAxxfjgAanMzOPVNEa20wtB40kp5o1xYebr312KoT19p7A-7KvmR42_Wseyai2q0tbm0h7ugUk9oOOAmS91g1jiGL_PFdQgHNmGH93GyhSozgkaTAkmCPXVMBWFBkFLDgf5dF7r5pRzxBou4OaWmmxBHGON1BBKwNiojOrAPrtOmvD1d7FLEV3ADrMA6B4nBAco0_LeC43GBIXqMMEJKm7xH-JPDg3HsKOgQUYSIyTquGNT3egZTvb9bTh421XvQJnQ6kazSrsTF9tgS2Fxryllj-QcfbWpyN_Cohrlf3GPMXTQsalFlSmnmAhcWt2YFrHjKdhvbx3EV46A", + "ebcc1dfe97:1:6l1EtROT-jp-bSse6vajfbi3EDaWJWjBt97Fp5KETjQNhsla79wLFbaSR5ccjqt9tT5lqlpdqLyoeaI8HIP2XgEQbUMmk5GgLNCwLSyTVkAui9HO01lghRxQll7AhptPOD5CjYQC8qnhSfNaUkLkzqjeEjgclDx7oMclrVDkWTvyegBK6Q_rUkfseWRRfe6zA7wwrOHQ2jeXOY8QbeyAqCRWtXKr16zisNhyuYbeP00kzUAkZvrWey4mvzp7ruJgARsBVAF5FRcq8JRqkF5E99MNe-T4mSPbQZKF40saK4N2p_zzp3vr-Wq8IkevPFn_5gbk8cwINC30Bz_qz45xRAhLX6emigA8EUWzeAW4091wU2wQoXrzmhil9b3weBSEfeLvlycnmwfGMDWo8jaW29aqz5Ix6U7V3hu_kiA4FgzJEIxUYu3L4wBsPI2zYUMZqXBpcPvh8GGzIzgOGLRoi_3FhNtGEDTn8ajCMWUmfpIMiwOw7-iWjUwEZH42Ch-0c6j4RiezD-FphACiwtDZBs5llcBPqmRo8cHJ1DwjFHQCXn5Y0mAE4WWfkSUPDE5h9uXO02ic843qUnd0m-HLVmwhxW9RDP4-FSBPCAA-3K37hyZw3mtGBfKll4NJmQb-gMBRWG_C89grkTJr-QYbekVbtHm8lG7rzB3-lw" ] { let id = Identity::from_str(bad_id).unwrap(); if id.locally_validate() { diff --git a/network-hypervisor/src/vl1/inetaddress.rs b/network-hypervisor/src/vl1/inetaddress.rs index 9d116a53f..9174864b1 100644 --- a/network-hypervisor/src/vl1/inetaddress.rs +++ b/network-hypervisor/src/vl1/inetaddress.rs @@ -6,7 +6,7 @@ use std::hash::{Hash, Hasher}; use std::net::{IpAddr, Ipv6Addr}; use crate::error::InvalidFormatError; -use crate::util::equal_bytes; +use crate::util::equal_ptr; use crate::vl1::buffer::Buffer; #[cfg(windows)] @@ -405,11 +405,11 @@ impl InetAddress { match buf.read_u8(cursor)? { 4 => { let b: &[u8; 6] = buf.read_bytes_fixed(cursor)?; - Ok(InetAddress::from_ip_port(&b[0..4], crate::util::integer_load_be_u16(&b[4..6]))) + Ok(InetAddress::from_ip_port(&b[0..4], crate::util::load_u16_be(&b[4..6]))) } 6 => { let b: &[u8; 18] = buf.read_bytes_fixed(cursor)?; - Ok(InetAddress::from_ip_port(&b[0..16], crate::util::integer_load_be_u16(&b[16..18]))) + Ok(InetAddress::from_ip_port(&b[0..16], crate::util::load_u16_be(&b[16..18]))) } _ => Ok(InetAddress::new()) } @@ -480,7 +480,7 @@ impl PartialEq for InetAddress { AF_INET => { self.sin.sin_port == other.sin.sin_port && self.sin.sin_addr.s_addr == other.sin.sin_addr.s_addr } AF_INET6 => { if self.sin6.sin6_port == other.sin6.sin6_port { - equal_bytes((&(self.sin6.sin6_addr) as *const in6_addr).cast(), (&(other.sin6.sin6_addr) as *const in6_addr).cast(), 16) + equal_ptr((&(self.sin6.sin6_addr) as *const in6_addr).cast(), (&(other.sin6.sin6_addr) as *const in6_addr).cast(), 16) } else { false } diff --git a/network-hypervisor/src/vl1/mod.rs b/network-hypervisor/src/vl1/mod.rs index dc5ef8c51..047090257 100644 --- a/network-hypervisor/src/vl1/mod.rs +++ b/network-hypervisor/src/vl1/mod.rs @@ -14,6 +14,7 @@ pub mod identity; pub mod inetaddress; pub mod endpoint; pub mod locator; +pub mod rootset; pub use address::Address; pub use mac::MAC; diff --git a/network-hypervisor/src/vl1/node.rs b/network-hypervisor/src/vl1/node.rs index f7f9b05f1..17442741a 100644 --- a/network-hypervisor/src/vl1/node.rs +++ b/network-hypervisor/src/vl1/node.rs @@ -59,6 +59,7 @@ pub trait VL1CallerInterface { fn load_peer(&self, address: Address) -> Option<&[u8]>; /// Save a peer's state. + /// /// The state contains the identity, so there's no need to save that separately. /// It's just supplied for the address and if the external code wants it. fn save_peer(&self, id: &Identity, peer: &[u8]); @@ -87,6 +88,7 @@ pub trait VL1CallerInterface { fn check_path(&self, id: &Identity, endpoint: &Endpoint, local_socket: Option, local_interface: Option) -> bool; /// Called to look up a path to a known node. + /// /// If a path is found, this returns a tuple of an endpoint and optional local socket and local /// interface IDs. If these are None they will be None when this is sent with wire_send. fn get_path_hints(&self, id: &Identity) -> Option<&[(&Endpoint, Option, Option)]>; @@ -101,6 +103,7 @@ pub trait VL1CallerInterface { /// Trait implemented by VL2 to handle messages after they are unwrapped by VL1. pub trait VL1PacketHandler { /// Handle a packet, returning true if the verb was recognized. + /// /// True should be returned even if the packet is not valid, since the return value is used /// to determine if this is a VL2 or VL1 packet. ERROR and OK should not be handled here but /// in handle_error() and handle_ok() instead. @@ -135,6 +138,7 @@ pub struct Node { impl Node { /// Create a new Node. + /// /// If the auto-generate identity type is not None, a new identity will be generated if /// no identity is currently stored in the data store. pub fn new(ci: &CI, auto_generate_identity_type: Option) -> Result { @@ -207,6 +211,7 @@ impl Node { } /// Run background tasks and return desired delay until next call in milliseconds. + /// /// This should only be called once at a time. It technically won't hurt anything to /// call concurrently but it will waste CPU cycles. pub fn do_background_tasks(&self, ci: &CI) -> Duration { @@ -310,6 +315,7 @@ impl Node { } /// Get the canonical Path object for a given endpoint and local socket information. + /// /// This is a canonicalizing function that returns a unique path object for every tuple /// of endpoint, local socket, and local interface. pub(crate) fn path(&self, ep: &Endpoint, local_socket: i64, local_interface: i64) -> Arc { diff --git a/network-hypervisor/src/vl1/rootset.rs b/network-hypervisor/src/vl1/rootset.rs new file mode 100644 index 000000000..f83ea1e92 --- /dev/null +++ b/network-hypervisor/src/vl1/rootset.rs @@ -0,0 +1,321 @@ +use std::collections::BTreeSet; +use std::hash::{Hash, Hasher}; +use std::io::Write; + +use concat_arrays::concat_arrays; + +use crate::crypto::c25519::{ED25519_PUBLIC_KEY_SIZE, ED25519_SECRET_KEY_SIZE, ED25519_SIGNATURE_SIZE, ed25519_verify, Ed25519KeyPair}; +use crate::crypto::hash::SHA384; +use crate::crypto::p521::{P521_ECDSA_SIGNATURE_SIZE, P521_PUBLIC_KEY_SIZE, P521_SECRET_KEY_SIZE, P521KeyPair}; +use crate::crypto::secret::Secret; +use crate::error::InvalidFormatError; +use crate::vl1::{Endpoint, Identity}; +use crate::vl1::buffer::Buffer; +use crate::vl1::constants::PACKET_SIZE_MAX; + +const ROOT_SET_TYPE_LEGACY_PLANET: u8 = 1; +const ROOT_SET_TYPE_LEGACY_MOON: u8 = 127; +const ROOT_SET_TYPE_ED25519_P521: u8 = 128; + +/// Root set type. +/// +/// Two of these are legacy from ZeroTier V1. The third is a root set signed by both +/// an Ed25519 key and a NIST P-521 key with these keys being bundled together. +#[derive(Clone, PartialEq, Eq)] +pub enum Type { + LegacyPlanet(u64), + LegacyMoon(u64), + Ed25519P521RootSet([u8; 48]), +} + +impl Hash for Type { + fn hash(&self, state: &mut H) { + match self { + Self::LegacyPlanet(id) => state.write_u64(*id), + Self::LegacyMoon(id) => state.write_u64(*id), + Self::Ed25519P521RootSet(id) => state.write(id), + } + } +} + +/// Secret keys that can be used to update root sets after creation. +pub struct RootSetSecretKeys { + ed25519: Ed25519KeyPair, + p521: P521KeyPair, +} + +impl RootSetSecretKeys { + const SECRET_BYTES_LEN: usize = 1 + ED25519_PUBLIC_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_PUBLIC_KEY_SIZE + P521_SECRET_KEY_SIZE; + const PUBLIC_BYTES_LEN: usize = 1 + ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE; + + /// Generate a new set of root set secret keys. + pub fn generate() -> Self { + Self { + ed25519: Ed25519KeyPair::generate(false), + p521: P521KeyPair::generate(false).unwrap(), + } + } + + /// Reconstruct from bytes as returned by to_secret_bytes() or return None if invalid. + pub fn from_bytes(b: &[u8]) -> Option { + if b.len() == Self::SECRET_BYTES_LEN && b[0] == ROOT_SET_TYPE_ED25519_P521 { + let ed25519 = Ed25519KeyPair::from_bytes(&b[1..ED25519_PUBLIC_KEY_SIZE + 1], &b[1 + ED25519_PUBLIC_KEY_SIZE..1 + ED25519_PUBLIC_KEY_SIZE + ED25519_SECRET_KEY_SIZE]); + let p521 = P521KeyPair::from_bytes(&b[1 + ED25519_PUBLIC_KEY_SIZE + ED25519_SECRET_KEY_SIZE..1 + ED25519_PUBLIC_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_PUBLIC_KEY_SIZE], &b[1 + ED25519_PUBLIC_KEY_SIZE + ED25519_SECRET_KEY_SIZE + P521_PUBLIC_KEY_SIZE..]); + if ed25519.is_none() || p521.is_none() { + None + } else { + Some(Self { + ed25519: ed25519.unwrap(), + p521: p521.unwrap(), + }) + } + } else { + None + } + } + + /// Get both public and secret keys in byte format. + pub fn to_secret_bytes(&self) -> Secret<{ Self::SECRET_BYTES_LEN }> { + Secret(concat_arrays!([ROOT_SET_TYPE_ED25519_P521], self.ed25519.public_bytes(), self.ed25519.secret_bytes().0, *self.p521.public_key_bytes(), self.p521.secret_key_bytes().0)) + } + + /// Get only public keys in byte format. + pub fn to_public_bytes(&self) -> [u8; ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE] { + concat_arrays!(self.ed25519.public_bytes(), *self.p521.public_key_bytes()) + } +} + +/// A single root node with static endpoints where it can be reached. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Root { + /// Root node ZeroTier identity. + pub identity: Identity, + + /// Static endpoints at which this root node may be reached. + pub endpoints: BTreeSet, +} + +/// A signed bundle of root nodes. +/// +/// This is how roots are normally specified to nodes. The embedded signing key allows the +/// root set to be updated automatically. Updates can add, remove, or change the endpoints +/// of roots, allowing infrastructure updates with automatic client configuration as long +/// as at least one of the old roots is up to distribute the new ones. +#[derive(PartialEq, Eq)] +pub struct RootSet { + pub timestamp: i64, + pub name: String, + pub contact: String, + pub roots: BTreeSet, + signer: Vec, + signature: Vec, + root_set_type: Type, +} + +impl RootSet { + pub const MAX_ROOTS: usize = u8::MAX as usize; + pub const MAX_ENDPOINTS_PER_ROOT: usize = u8::MAX as usize; + + /// Sign this root set and return true on success. + /// The fields timestamp, name, contact, and roots must have been set. The signer, signature, and type will be set. + /// This can only sign new format root sets. Legacy "planet" and "moon" root sets can be used by V2 but + /// cannot be created by this code. + pub fn sign(&mut self, keys: &RootSetSecretKeys) -> bool { + self.signer = keys.to_public_bytes().to_vec(); + self.root_set_type = Type::Ed25519P521RootSet(SHA384::hash(self.signer.as_slice())); + + let mut buf: Buffer<{ PACKET_SIZE_MAX }> = Buffer::new(); + if self.marshal_internal(&mut buf, true).is_err() { + return false; + } + let ed25519 = keys.ed25519.sign(buf.as_bytes()); + let p521 = keys.p521.sign(buf.as_bytes()); + if p521.is_none() { + return false; + } + let p521 = p521.unwrap(); + + self.signature.clear(); + let _ = self.signature.write_all(&ed25519); + let _ = self.signature.write_all(&p521); + + true + } + + fn marshal_internal(&self, buf: &mut Buffer, for_signing: bool) -> std::io::Result<()> { + if self.roots.len() > u8::MAX as usize { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "maximum roots per root set: 255")); + } + + let name = self.name.as_bytes(); + let contact = self.contact.as_bytes(); + if name.len() > u8::MAX as usize { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "maximum roots per root set: 255")); + } + if contact.len() > u8::MAX as usize { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "maximum roots per root set: 255")); + } + + if for_signing { + buf.append_u64(0x7f7f7f7f7f7f7f7f)?; + } + + match &self.root_set_type { + Type::LegacyPlanet(id) | Type::LegacyMoon(id) => { + buf.append_u8(if matches!(self.root_set_type, Type::LegacyPlanet(_)) { + ROOT_SET_TYPE_LEGACY_PLANET + } else { + ROOT_SET_TYPE_LEGACY_MOON + })?; + buf.append_u64(*id)?; + buf.append_u64(self.timestamp as u64)?; + if self.signer.len() != 64 { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "legacy signer can only be 64 bytes")); + } + buf.append_bytes(self.signer.as_slice())?; + if !for_signing { + if self.signature.len() != 96 { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "legacy signature can only be 96 bytes")); + } + buf.append_bytes(self.signature.as_slice())?; + } + } + + Type::Ed25519P521RootSet(_) => { + buf.append_u8(ROOT_SET_TYPE_ED25519_P521)?; + buf.append_u64(self.timestamp as u64)?; + buf.append_u8(name.len() as u8)?; + buf.append_bytes(name); + buf.append_u8(contact.len() as u8)?; + buf.append_bytes(contact); + if self.signer.len() != (ED25519_PUBLIC_KEY_SIZE + P521_PUBLIC_KEY_SIZE) { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "signer can only be 164 bytes")); + } + buf.append_u8(self.signer.len() as u8)?; + buf.append_bytes(self.signer.as_slice())?; + if !for_signing { + if self.signature.len() != (ED25519_SIGNATURE_SIZE + P521_ECDSA_SIGNATURE_SIZE) { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "signature can only be 192 bytes")); + } + buf.append_u8(self.signature.len() as u8)?; + buf.append_bytes(self.signature.as_slice())?; + } + } + } + + buf.append_u8(self.roots.len() as u8)?; + for root in self.roots.iter() { + root.identity.marshal(buf, false)?; + if root.endpoints.len() > u8::MAX as usize { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "maximum endpoints per root: 255")); + } + buf.append_u8(root.endpoints.len() as u8)?; + for ep in root.endpoints.iter() { + ep.marshal(buf)?; + } + } + + if matches!(self.root_set_type, Type::LegacyMoon(_)) { + buf.append_u8(0)?; + } + + if for_signing { + buf.append_u64(0x7f7f7f7f7f7f7f7f)?; + } + + Ok(()) + } + + #[inline(always)] + pub fn marshal(&self, buf: &mut Buffer) -> std::io::Result<()> { + self.marshal_internal(buf, false) + } + + pub fn unmarshal(buf: &Buffer, cursor: &mut usize) -> std::io::Result { + let read_roots = |buf: &Buffer, cursor: &mut usize| -> std::io::Result> { + let mut roots = BTreeSet::::new(); + let root_count = buf.read_u8(cursor)? as usize; + for _ in 0..root_count { + let identity = Identity::unmarshal(buf, cursor)?; + let mut endpoints = BTreeSet::::new(); + let endpoint_count = buf.read_u8(cursor)? as usize; + for _ in 0..endpoint_count { + endpoints.insert(Endpoint::unmarshal(buf, cursor)?); + } + roots.insert(Root { + identity, + endpoints + }); + } + Ok(roots) + }; + + let type_id = buf.read_u8(cursor)?; + match type_id { + ROOT_SET_TYPE_LEGACY_PLANET | ROOT_SET_TYPE_LEGACY_MOON => { + let root_set_type = if type_id == ROOT_SET_TYPE_LEGACY_PLANET { + Type::LegacyPlanet(buf.read_u64(cursor)?) + } else { + Type::LegacyMoon(buf.read_u64(cursor)?) + }; + let timestamp = buf.read_u64(cursor)?; + let signer = buf.read_bytes(64, cursor)?.to_vec(); + let signature = buf.read_bytes(96, cursor)?.to_vec(); + let roots = read_roots(buf, cursor)?; + if type_id == ROOT_SET_TYPE_LEGACY_MOON { + *cursor += buf.read_u8(cursor)? as usize; + } + Ok(Self { + timestamp: timestamp as i64, + name: String::new(), + contact: String::new(), + roots, + signer, + signature, + root_set_type, + }) + } + + ROOT_SET_TYPE_ED25519_P521 => { + let timestamp = buf.read_u64(cursor)?; + let name = String::from_utf8_lossy(buf.read_bytes(buf.read_u8(cursor)? as usize, cursor)?).to_string(); + let contact = String::from_utf8_lossy(buf.read_bytes(buf.read_u8(cursor)? as usize, cursor)?).to_string(); + let signer = buf.read_bytes(buf.read_u8(cursor)? as usize, cursor)?.to_vec(); + let signature = buf.read_bytes(buf.read_u8(cursor)? as usize, cursor)?.to_vec(); + let root_set_type = Type::Ed25519P521RootSet(SHA384::hash(signer.as_slice())); + Ok(Self { + timestamp: timestamp as i64, + name, + contact, + roots: read_roots(buf, cursor)?, + signer, + signature, + root_set_type, + }) + } + + _ => { + Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "unrecognized type")) + } + } + } + + /// Get this root set's globally unique ID. + /// + /// For new root set format this is a hash of its public keys. For old style planet/moon + /// this is a user assigned 64-bit ID. The latter is deprecated but still supported. + pub fn id(&self) -> Vec { + match self.root_set_type { + Type::LegacyPlanet(id) => id.to_be_bytes().to_vec(), + Type::LegacyMoon(id) => id.to_be_bytes().to_vec(), + Type::Ed25519P521RootSet(id) => id.to_vec(), + } + } +} + +impl Hash for RootSet { + fn hash(&self, state: &mut H) { + state.write_u64(self.timestamp as u64); + self.root_set_type.hash(state); + } +}