diff --git a/Cargo.lock b/Cargo.lock index 845cb75272..f68e8f7c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7129,6 +7129,7 @@ dependencies = [ "csv", "cupid", "futures", + "hex", "hkdf", "human-repr", "humantime-serde", diff --git a/nym-api/nym-api-requests/src/models/described/type_translation.rs b/nym-api/nym-api-requests/src/models/described/type_translation.rs index 94746950d3..efdd558ac3 100644 --- a/nym-api/nym-api-requests/src/models/described/type_translation.rs +++ b/nym-api/nym-api-requests/src/models/described/type_translation.rs @@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::IpAddr; use strum_macros::{Display, EnumString}; +use thiserror::Error; use utoipa::ToSchema; // to whoever is thinking of modifying this struct. @@ -211,17 +212,57 @@ pub struct LewesProtocolDetailsV1 { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. - pub kem_keys: HashMap>>, + /// note: digests are hex encoded + pub kem_keys: HashMap>, /// Digests of the signing keys available to this node alongside hashing algorithms used /// for their computation. - pub signing_keys: HashMap>>, + /// note: digests are hex encoded + pub signing_keys: HashMap>, +} + +impl LewesProtocolDetailsV1 { + fn decode_digests( + digests: &HashMap, + ) -> Result>, MalformedLPData> { + let mut kem_digests = HashMap::new(); + for (hash_function, digest) in digests { + let digest = hex::decode(digest).map_err(|source| MalformedLPData::MalformedHash { + value: digest.to_string(), + source, + })?; + kem_digests.insert((*hash_function).try_into()?, digest); + } + Ok(kem_digests) + } + + pub fn kem_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut kem_keys = HashMap::new(); + for (kem, digests) in &self.kem_keys { + let kem_digests = Self::decode_digests(digests)?; + kem_keys.insert((*kem).try_into()?, kem_digests); + } + Ok(kem_keys) + } + + pub fn signing_keys( + &self, + ) -> Result>>, MalformedLPData> { + let mut signing_keys = HashMap::new(); + for (signature_scheme, digests) in &self.signing_keys { + let kem_digests = Self::decode_digests(digests)?; + signing_keys.insert((*signature_scheme).try_into()?, kem_digests); + } + Ok(signing_keys) + } } /// Convert map of digests from `nym_node_requests` types into `nym-api-requests` types fn translate_digests( - digests: HashMap>, -) -> HashMap> { + digests: HashMap, +) -> HashMap { digests .into_iter() .map(|(hash_fn, digest)| (hash_fn.into(), digest)) @@ -244,6 +285,7 @@ fn translate_digests( ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPKEM { MlKem768, XWing, @@ -267,6 +309,7 @@ pub enum LPKEM { ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPHashFunction { Blake3, Shake128, @@ -290,6 +333,7 @@ pub enum LPHashFunction { ToSchema, )] #[strum(serialize_all = "lowercase")] +#[non_exhaustive] pub enum LPSignatureScheme { Ed25519, } @@ -469,32 +513,59 @@ impl From } } -impl From for KEM { - fn from(value: LPKEM) -> Self { +#[derive(Debug, Error)] +pub enum MalformedLPData { + #[error("{value} does not correspond to any valid LP KEM")] + UnknownLpKEM { value: LPKEM }, + + #[error("{value} does not correspond to any valid LP Signature Scheme")] + UnknownLpSignatureScheme { value: LPSignatureScheme }, + + #[error("{value} does not correspond to any valid LP Hash Function")] + UnknownLpHashFunction { value: LPHashFunction }, + + #[error("{value} is not a valid hex encoded hash: {source}")] + MalformedHash { + value: String, + source: hex::FromHexError, + }, +} + +impl TryFrom for KEM { + type Error = MalformedLPData; + fn try_from(value: LPKEM) -> Result { match value { - LPKEM::MlKem768 => KEM::MlKem768, - LPKEM::XWing => KEM::XWing, - LPKEM::X25519 => KEM::X25519, - LPKEM::McEliece => KEM::McEliece, + LPKEM::MlKem768 => Ok(KEM::MlKem768), + LPKEM::XWing => Ok(KEM::XWing), + LPKEM::X25519 => Ok(KEM::X25519), + LPKEM::McEliece => Ok(KEM::McEliece), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpKEM { value: other }), } } } -impl From for HashFunction { - fn from(value: LPHashFunction) -> Self { +impl TryFrom for HashFunction { + type Error = MalformedLPData; + fn try_from(value: LPHashFunction) -> Result { match value { - LPHashFunction::Blake3 => HashFunction::Blake3, - LPHashFunction::Shake128 => HashFunction::Shake128, - LPHashFunction::Shake256 => HashFunction::Shake256, - LPHashFunction::Sha256 => HashFunction::SHA256, + LPHashFunction::Blake3 => Ok(HashFunction::Blake3), + LPHashFunction::Shake128 => Ok(HashFunction::Shake128), + LPHashFunction::Shake256 => Ok(HashFunction::Shake256), + LPHashFunction::Sha256 => Ok(HashFunction::SHA256), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpHashFunction { value: other }), } } } -impl From for SignatureScheme { - fn from(value: LPSignatureScheme) -> Self { +impl TryFrom for SignatureScheme { + type Error = MalformedLPData; + fn try_from(value: LPSignatureScheme) -> Result { match value { - LPSignatureScheme::Ed25519 => SignatureScheme::Ed25519, + LPSignatureScheme::Ed25519 => Ok(SignatureScheme::Ed25519), + // TODO: for backwards compatibility once variants within the LP crate change + // other => Err(MalformedLPData::UnknownLpSignatureScheme { value: other }), } } } diff --git a/nym-api/nym-api-requests/src/models/described/v2.rs b/nym-api/nym-api-requests/src/models/described/v2.rs index bd68aa5271..8be3710eed 100644 --- a/nym-api/nym-api-requests/src/models/described/v2.rs +++ b/nym-api/nym-api-requests/src/models/described/v2.rs @@ -262,10 +262,16 @@ pub fn mock_nym_node_description(seed: u64) -> NymNodeDescriptionV2 { let mut kem_hashes = std::collections::HashMap::new(); let mut signing_keys_hashes = std::collections::HashMap::new(); - kem_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + kem_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); kem_hashes_wrapper.insert(LPKEM::X25519, kem_hashes); - signing_keys_hashes.insert(LPHashFunction::Sha256, vec![(seed % 256) as u8; 32]); + signing_keys_hashes.insert( + LPHashFunction::Sha256, + hex::encode([(seed % 256) as u8; 32]), + ); signing_keys_hashes_wrapper.insert(LPSignatureScheme::Ed25519, signing_keys_hashes); NymNodeDescriptionV2 { diff --git a/nym-gateway-probe/src/common/nodes.rs b/nym-gateway-probe/src/common/nodes.rs index 9716a923fd..6950978b14 100644 --- a/nym-gateway-probe/src/common/nodes.rs +++ b/nym-gateway-probe/src/common/nodes.rs @@ -132,32 +132,8 @@ impl DirectoryNode { ) { (Some(lp_data), Some(noise_key)) => Some(TestedNodeLpDetails { address: SocketAddr::new(ip_address, lp_data.control_port), - expected_kem_key_hashes: lp_data - .kem_keys - .into_iter() - .map(|(kem, digests)| { - ( - kem.into(), - digests - .into_iter() - .map(|(hash_fn, digest)| (hash_fn.into(), digest)) - .collect(), - ) - }) - .collect(), - expected_signing_key_hashes: lp_data - .signing_keys - .into_iter() - .map(|(scheme, digests)| { - ( - scheme.into(), - digests - .into_iter() - .map(|(hash_fn, digest)| (hash_fn.into(), digest)) - .collect(), - ) - }) - .collect(), + expected_kem_key_hashes: lp_data.kem_keys()?, + expected_signing_key_hashes: lp_data.signing_keys()?, x25519: noise_key.x25519_pubkey, }), _ => None, diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 0002bc60f4..55105c54bf 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -28,6 +28,7 @@ console-subscriber = { workspace = true, optional = true } csv = { workspace = true } clap = { workspace = true, features = ["cargo", "env"] } futures = { workspace = true } +hex = { workspace = true } humantime-serde = { workspace = true } human-repr = { workspace = true } ipnetwork = { workspace = true } diff --git a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs index ac38f316b1..5dffa99541 100644 --- a/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/lewes_protocol/models.rs @@ -21,11 +21,13 @@ pub struct LewesProtocol { /// Digests of the KEM keys available to this node alongside hashing algorithms used /// for their computation. - pub kem_keys: HashMap>>, + /// note: digests are hex encoded + pub kem_keys: HashMap>, /// Digests of the signing keys available to this node alongside hashing algorithms used /// for their computation. - pub signing_keys: HashMap>>, + /// note: digests are hex encoded + pub signing_keys: HashMap>, } impl LewesProtocol { @@ -33,8 +35,8 @@ impl LewesProtocol { enabled: bool, control_port: u16, data_port: u16, - kem_keys: HashMap>>, - signing_keys: HashMap>>, + kem_keys: HashMap>, + signing_keys: HashMap>, ) -> Self { LewesProtocol { enabled, diff --git a/nym-node/src/node/mod.rs b/nym-node/src/node/mod.rs index 01fb355825..abf246c453 100644 --- a/nym-node/src/node/mod.rs +++ b/nym-node/src/node/mod.rs @@ -779,7 +779,7 @@ impl NymNode { Ok(()) } - fn compute_kem_key_hashes(&self) -> HashMap>> { + fn compute_kem_key_hashes(&self) -> HashMap> { let kem = LPKEM::X25519; let kem_key_bytes = self.entry_gateway.psq_kem_key.public_key().as_bytes(); @@ -787,7 +787,7 @@ impl NymNode { // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` let digests = produce_key_digests(kem_key_bytes.as_ref()) .into_iter() - .map(|(f, d)| (f.into(), d)) + .map(|(f, digest)| (f.into(), hex::encode(&digest))) .collect(); let mut hashes = HashMap::new(); @@ -797,7 +797,7 @@ impl NymNode { fn compute_signing_key_hashes( &self, - ) -> HashMap>> { + ) -> HashMap> { let scheme = LPSignatureScheme::Ed25519; let kem_key_bytes = self.ed25519_identity_keys.public_key().as_bytes(); @@ -805,7 +805,7 @@ impl NymNode { // convert from `nym_kkt_ciphersuite` types into `nym_nodes_requests` let digests = produce_key_digests(kem_key_bytes.as_ref()) .into_iter() - .map(|(f, d)| (f.into(), d)) + .map(|(f, digest)| (f.into(), hex::encode(&digest))) .collect(); let mut hashes = HashMap::new();