From afbdc107bf58d842d33056e96d3376741b5c8733 Mon Sep 17 00:00:00 2001 From: Simon Wicky Date: Wed, 21 May 2025 09:54:20 +0200 Subject: [PATCH] backwards compatibility for mixnodes announced keys --- nym-api/nym-api-requests/src/models.rs | 10 +- nym-node/nym-node-requests/src/api/mod.rs | 117 ++++++++++++++---- .../src/api/v1/node/models.rs | 25 +++- nym-node/src/node/http/helpers/mod.rs | 8 +- 4 files changed, 126 insertions(+), 34 deletions(-) diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 4b1df5ccfe..6d1fbf25a1 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -896,7 +896,7 @@ pub struct HostKeys { pub current_x25519_sphinx_key: SphinxKey, #[serde(default)] - pub x25519_noise: Option, + pub x25519_versioned_noise: Option, } impl From for HostKeys { @@ -904,8 +904,6 @@ impl From for HostKeys { HostKeys { ed25519: value.ed25519_identity, x25519: value.x25519_sphinx, - current_x25519_sphinx_key: value.primary_x25519_sphinx_key.into(), - pre_announced_x25519_sphinx_key: value.pre_announced_x25519_sphinx_key.map(Into::into), x25519_versioned_noise: value.x25519_versioned_noise, } } @@ -1093,7 +1091,11 @@ impl NymNodeDescription { SemiSkimmedNode { basic: skimmed_node, - x25519_noise_versioned_key: self.description.host_information.keys.x25519_noise, + x25519_noise_versioned_key: self + .description + .host_information + .keys + .x25519_versioned_noise, } } } diff --git a/nym-node/nym-node-requests/src/api/mod.rs b/nym-node/nym-node-requests/src/api/mod.rs index b1478a39ec..dab279586f 100644 --- a/nym-node/nym-node-requests/src/api/mod.rs +++ b/nym-node/nym-node-requests/src/api/mod.rs @@ -10,6 +10,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::ops::Deref; +use v1::node::models::LegacyHostInformationV3; #[cfg(feature = "client")] pub mod client; @@ -70,6 +71,15 @@ impl SignedHostInformation { // TODO: @JS: to remove downgrade support in future release(s) + let legacy_v3 = SignedData { + data: LegacyHostInformationV3::from(self.data.clone()), + signature: self.signature.clone(), + }; + + if legacy_v3.verify(&self.keys.ed25519_identity) { + return true; + } + // attempt to verify legacy signatures let legacy_v3 = SignedData { data: LegacyHostInformationV3::from(self.data.clone()), @@ -131,7 +141,7 @@ mod tests { let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); let ed22519 = ed25519::KeyPair::new(&mut rng); let x25519_sphinx = x25519::KeyPair::new(&mut rng); - let x25519_noise = VersionedNoiseKey { + let x25519_versioned_noise = VersionedNoiseKey { version: NoiseVersion::V1, x25519_pubkey: *x25519::KeyPair::new(&mut rng).public_key(), }; @@ -145,11 +155,6 @@ mod tests { keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), x25519_sphinx: *x25519_sphinx.public_key(), - primary_x25519_sphinx_key: SphinxKey { - rotation_id: current_rotation_id, - public_key: *x25519_sphinx.public_key(), - }, - pre_announced_x25519_sphinx_key: None, x25519_versioned_noise: None, }, }; @@ -164,7 +169,7 @@ mod tests { keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), x25519_sphinx: *x25519_sphinx.public_key(), - x25519_noise: Some(x25519_noise), + x25519_versioned_noise: Some(x25519_versioned_noise), }, }; @@ -314,6 +319,84 @@ mod tests { assert!(current_struct_noise.verify_host_information()) } + #[test] + fn dummy_legacy_v3_signed_host_verification() { + let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); + let ed22519 = ed25519::KeyPair::new(&mut rng); + let x25519_sphinx = x25519::KeyPair::new(&mut rng); + let x25519_noise = x25519::KeyPair::new(&mut rng); + + let legacy_info_no_noise = crate::api::v1::node::models::LegacyHostInformationV3 { + ip_address: vec!["1.1.1.1".parse().unwrap()], + hostname: Some("foomp.com".to_string()), + keys: crate::api::v1::node::models::LegacyHostKeysV3 { + ed25519_identity: *ed22519.public_key(), + x25519_sphinx: *x25519_sphinx.public_key(), + x25519_noise: None, + }, + }; + + //technically this variant should never happen + let legacy_info_noise = crate::api::v1::node::models::LegacyHostInformationV3 { + ip_address: vec!["1.1.1.1".parse().unwrap()], + hostname: Some("foomp.com".to_string()), + keys: crate::api::v1::node::models::LegacyHostKeysV3 { + ed25519_identity: *ed22519.public_key(), + x25519_sphinx: *x25519_sphinx.public_key(), + x25519_noise: Some(*x25519_noise.public_key()), + }, + }; + + let host_info_no_noise = crate::api::v1::node::models::HostInformation { + ip_address: legacy_info_no_noise.ip_address.clone(), + hostname: legacy_info_no_noise.hostname.clone(), + keys: crate::api::v1::node::models::HostKeys { + ed25519_identity: legacy_info_no_noise.keys.ed25519_identity, + x25519_sphinx: legacy_info_no_noise.keys.x25519_sphinx, + x25519_versioned_noise: None, + }, + }; + + let host_info_noise = crate::api::v1::node::models::HostInformation { + ip_address: legacy_info_noise.ip_address.clone(), + hostname: legacy_info_noise.hostname.clone(), + keys: crate::api::v1::node::models::HostKeys { + ed25519_identity: legacy_info_noise.keys.ed25519_identity, + x25519_sphinx: legacy_info_noise.keys.x25519_sphinx, + x25519_versioned_noise: Some(VersionedNoiseKey { + version: NoiseVersion::V1, + x25519_pubkey: legacy_info_noise.keys.x25519_noise.unwrap(), + }), + }, + }; + + // signature on legacy data + let signature_no_noise = SignedData::new(legacy_info_no_noise, ed22519.private_key()) + .unwrap() + .signature; + + let signature_noise = SignedData::new(legacy_info_noise, ed22519.private_key()) + .unwrap() + .signature; + + // signed blob with the 'current' structure + let current_struct_no_noise = SignedData { + data: host_info_no_noise, + signature: signature_no_noise, + }; + + let current_struct_noise = SignedData { + data: host_info_noise, + signature: signature_noise, + }; + + assert!(!current_struct_no_noise.verify(ed22519.public_key())); + assert!(current_struct_no_noise.verify_host_information()); + + assert!(!current_struct_noise.verify(ed22519.public_key())); + assert!(current_struct_noise.verify_host_information()) + } + #[test] fn dummy_legacy_v2_signed_host_verification() { let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); @@ -347,12 +430,7 @@ mod tests { hostname: legacy_info_no_noise.hostname.clone(), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info_no_noise.keys.ed25519_identity.parse().unwrap(), - x25519_sphinx: *x25519_sphinx.public_key(), - primary_x25519_sphinx_key: SphinxKey { - rotation_id: u32::MAX, - public_key: *x25519_sphinx.public_key(), - }, - pre_announced_x25519_sphinx_key: None, + x25519_sphinx: legacy_info_no_noise.keys.x25519_sphinx.parse().unwrap(), x25519_versioned_noise: None, }, }; @@ -364,7 +442,7 @@ mod tests { keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info_noise.keys.ed25519_identity.parse().unwrap(), x25519_sphinx: legacy_info_noise.keys.x25519_sphinx.parse().unwrap(), - x25519_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKey { version: NoiseVersion::V1, x25519_pubkey: legacy_info_noise.keys.x25519_noise.parse().unwrap(), }), @@ -411,7 +489,7 @@ mod tests { keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), x25519_sphinx: *x25519_sphinx.public_key(), - x25519_noise: None, + x25519_versioned_noise: None, }, }; @@ -421,7 +499,7 @@ mod tests { keys: crate::api::v1::node::models::HostKeys { ed25519_identity: *ed22519.public_key(), x25519_sphinx: *x25519_sphinx.public_key(), - x25519_noise: Some(VersionedNoiseKey { + x25519_versioned_noise: Some(VersionedNoiseKey { version: NoiseVersion::V1, x25519_pubkey: *x25519_noise.public_key(), }), @@ -462,12 +540,7 @@ mod tests { hostname: legacy_info.hostname.clone(), keys: crate::api::v1::node::models::HostKeys { ed25519_identity: legacy_info.keys.ed25519.parse().unwrap(), - x25519_sphinx: *x25519_sphinx.public_key(), - primary_x25519_sphinx_key: SphinxKey { - rotation_id: u32::MAX, - public_key: *x25519_sphinx.public_key(), - }, - pre_announced_x25519_sphinx_key: None, + x25519_sphinx: legacy_info.keys.x25519.parse().unwrap(), x25519_versioned_noise: None, }, }; diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 3ca5386408..0ae3d462cc 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -3,7 +3,9 @@ use celes::Country; use nym_crypto::asymmetric::ed25519::{self, serde_helpers::bs58_ed25519_pubkey}; -use nym_crypto::asymmetric::x25519::{self, serde_helpers::bs58_x25519_pubkey}; +use nym_crypto::asymmetric::x25519::{ + self, serde_helpers::bs58_x25519_pubkey, serde_helpers::option_bs58_x25519_pubkey, +}; use nym_noise_keys::VersionedNoiseKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -144,7 +146,22 @@ pub struct HostKeys { /// Base58-encoded x25519 public key of this node used for the noise protocol. #[serde(default)] - pub x25519_noise: Option, + pub x25519_versioned_noise: Option, +} + +#[derive(Serialize)] +pub struct LegacyHostKeysV3 { + #[serde(alias = "ed25519")] + #[serde(with = "bs58_ed25519_pubkey")] + pub ed25519_identity: ed25519::PublicKey, + + #[serde(alias = "x25519")] + #[serde(with = "bs58_x25519_pubkey")] + pub x25519_sphinx: x25519::PublicKey, + + #[serde(default)] + #[serde(with = "option_bs58_x25519_pubkey")] + pub x25519_noise: Option, } #[derive(Serialize)] @@ -164,7 +181,7 @@ impl From for LegacyHostKeysV3 { fn from(value: HostKeys) -> Self { LegacyHostKeysV3 { ed25519_identity: value.ed25519_identity, - x25519_sphinx: value.primary_x25519_sphinx_key.public_key, + x25519_sphinx: value.x25519_sphinx, x25519_noise: value.x25519_versioned_noise.map(|k| k.x25519_pubkey), } } @@ -177,7 +194,7 @@ impl From for LegacyHostKeysV2 { x25519_sphinx: value.x25519_sphinx.to_base58_string(), x25519_noise: value .x25519_noise - .map(|k| k.x25519_pubkey.to_base58_string()) + .map(|k| k.to_base58_string()) .unwrap_or_default(), } } diff --git a/nym-node/src/node/http/helpers/mod.rs b/nym-node/src/node/http/helpers/mod.rs index 04114832e1..8e98f4d803 100644 --- a/nym-node/src/node/http/helpers/mod.rs +++ b/nym-node/src/node/http/helpers/mod.rs @@ -14,13 +14,13 @@ pub mod system_info; pub(crate) fn sign_host_details( config: &Config, x22519_sphinx: &x25519::PublicKey, - x25519_noise: &VersionedNoiseKey, + x25519_versioned_noise: &VersionedNoiseKey, ed22519_identity: &ed25519::KeyPair, ) -> Result { - let x25519_noise = if config.mixnet.debug.unsafe_disable_noise { + let x25519_versioned_noise = if config.mixnet.debug.unsafe_disable_noise { None } else { - Some(*x25519_noise) + Some(*x25519_versioned_noise) }; let host_info = api_requests::v1::node::models::HostInformation { @@ -29,7 +29,7 @@ pub(crate) fn sign_host_details( keys: api_requests::v1::node::models::HostKeys { ed25519_identity: *ed22519_identity.public_key(), x25519_sphinx: *x22519_sphinx, - x25519_noise, + x25519_versioned_noise, }, };