diff --git a/src/rpc/client/handshake_processing.rs b/src/rpc/client/handshake_processing.rs index 1230ae5..0eb8515 100644 --- a/src/rpc/client/handshake_processing.rs +++ b/src/rpc/client/handshake_processing.rs @@ -269,7 +269,7 @@ pub async fn process_handshake_response( let returned_message = encode(returned_message_bin); let returned_signed_message = encode(returned_signed_bin); - if returned_public_key_bin.len() != Wallet::PUBLIC_KEY_LENGTH { + if !Wallet::has_valid_public_key_bytes(returned_public_key_bin) { return Ok(()); } let hash = skein_256_hash_data(&returned_message); diff --git a/src/rpc/commands/wallet_register.rs b/src/rpc/commands/wallet_register.rs index c8d25f6..d3e4d19 100644 --- a/src/rpc/commands/wallet_register.rs +++ b/src/rpc/commands/wallet_register.rs @@ -83,7 +83,7 @@ pub async fn register( return RpcResponse::Binary(b"0".to_vec()); } - if public_key_bytes.len() != Wallet::PUBLIC_KEY_LENGTH { + if !Wallet::has_valid_public_key_bytes(&public_key_bytes) { return RpcResponse::Binary(b"0".to_vec()); } diff --git a/src/rpc/commands/wallet_registry_sync.rs b/src/rpc/commands/wallet_registry_sync.rs index cce98f6..44c72e5 100644 --- a/src/rpc/commands/wallet_registry_sync.rs +++ b/src/rpc/commands/wallet_registry_sync.rs @@ -17,7 +17,7 @@ pub async fn request_registry(db: &Db) -> RpcResponse { let mut response_bytes = Vec::with_capacity(wallets.len() * WALLET_REGISTRY_RECORD_BYTES); for (short_address, public_key) in wallets { if short_address.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH - || public_key.len() != Wallet::PUBLIC_KEY_LENGTH + || !Wallet::has_valid_public_key_bytes(&public_key) { continue; } diff --git a/src/rpc/server/handshake.rs b/src/rpc/server/handshake.rs index e609c42..5645a2f 100644 --- a/src/rpc/server/handshake.rs +++ b/src/rpc/server/handshake.rs @@ -373,7 +373,7 @@ pub async fn handle_handshake( } let received_public_key_bytes = match crate::decode(&received_public_key) { - Ok(bytes) if bytes.len() == Wallet::PUBLIC_KEY_LENGTH => bytes, + Ok(bytes) if Wallet::has_valid_public_key_bytes(&bytes) => bytes, _ => { drop_failed_handshake(&stream).await; return; diff --git a/src/standalone_tools/connections/handshake.rs b/src/standalone_tools/connections/handshake.rs index 6a3a37f..ba705c5 100644 --- a/src/standalone_tools/connections/handshake.rs +++ b/src/standalone_tools/connections/handshake.rs @@ -185,7 +185,7 @@ async fn perform_handshake( // Convert the returned binary fields into the formats used by wallet signature verification. let returned_message = encode(returned_message_bin); let returned_signed_message = encode(returned_signed_bin); - if returned_public_key_bin.len() != Wallet::PUBLIC_KEY_LENGTH { + if !Wallet::has_valid_public_key_bytes(returned_public_key_bin) { return Err(io::Error::new( io::ErrorKind::InvalidData, "Handshake returned an invalid public key", diff --git a/src/standalone_tools/connections/sending_request.rs b/src/standalone_tools/connections/sending_request.rs index de02fcd..964ca4b 100644 --- a/src/standalone_tools/connections/sending_request.rs +++ b/src/standalone_tools/connections/sending_request.rs @@ -431,7 +431,7 @@ async fn build_request_bytes( format!("Invalid wallet registration public key: {err}"), ) })?; - if public_key_bytes.len() != Wallet::PUBLIC_KEY_LENGTH { + if !Wallet::has_valid_public_key_bytes(&public_key_bytes) { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid wallet registration public key", diff --git a/src/wallets/create_keys.rs b/src/wallets/create_keys.rs index d8cda51..2d273f8 100644 --- a/src/wallets/create_keys.rs +++ b/src/wallets/create_keys.rs @@ -4,19 +4,24 @@ use crate::{decode, encode}; impl Wallet { pub fn generate_keypair(_network_byte: u8) -> (Vec, String) { - // Generate a new FN-DSA key pair using the wallet security parameter. - let keypair = - FnDsaKeyPair::generate(Self::FN_DSA_LOGN).expect("Failed to generate FN-DSA key pair"); + loop { + // Generate a new FN-DSA key pair using the wallet security parameter. + let keypair = FnDsaKeyPair::generate(Self::FN_DSA_LOGN) + .expect("Failed to generate FN-DSA key pair"); - // Keep the private key as raw bytes until it is hex-encoded for storage. - let secret_key_bytes = keypair.private_key().to_vec(); + let public_key_bytes = keypair.public_key().to_vec(); + if !Self::has_valid_public_key_bytes(&public_key_bytes) { + continue; + } - let public_key_bytes = keypair.public_key().to_vec(); + // Keep the private key as raw bytes until it is hex-encoded for storage. + let secret_key_bytes = keypair.private_key().to_vec(); - // Private keys are persisted as hex before wallet-image encryption. - let private_key_hex = encode(secret_key_bytes); + // Private keys are persisted as hex before wallet-image encryption. + let private_key_hex = encode(secret_key_bytes); - (public_key_bytes, private_key_hex) + return (public_key_bytes, private_key_hex); + } } pub fn regenerate_public_key(private_key_hex: &str) -> Result, String> { @@ -43,6 +48,12 @@ impl Wallet { .map_err(|e| format!("Failed to decode the provided FN-DSA private key: {e}"))?; let _ = network_byte; - Ok(keypair.public_key().to_vec()) + let public_key_bytes = keypair.public_key().to_vec(); + if !Self::has_valid_public_key_bytes(&public_key_bytes) { + return Err("FN-DSA public key does not satisfy this network's wallet key rule." + .to_string()); + } + + Ok(public_key_bytes) } } diff --git a/src/wallets/structures.rs b/src/wallets/structures.rs index a05163c..76304b2 100644 --- a/src/wallets/structures.rs +++ b/src/wallets/structures.rs @@ -15,6 +15,8 @@ pub struct Wallet { } impl Wallet { pub const FN_DSA_LOGN: u32 = 9; + pub const REQUIRED_PUBLIC_KEY_PREFIX_BYTE: u8 = 239; + pub const REQUIRED_PUBLIC_KEY_PREFIX_INDEX: usize = 1; pub const PUBLIC_KEY_LENGTH: usize = 897; pub const PRIVATE_KEY_LENGTH: usize = 1281; pub const SIGNATURE_LENGTH: usize = 666; diff --git a/src/wallets/verifications.rs b/src/wallets/verifications.rs index 723c5ec..90cca28 100644 --- a/src/wallets/verifications.rs +++ b/src/wallets/verifications.rs @@ -35,7 +35,7 @@ impl Wallet { // Decode and length-check the raw public key bytes. let public_key_bytes = match decode(public_key_hex) { - Ok(bytes) if bytes.len() == Self::PUBLIC_KEY_LENGTH => bytes, + Ok(bytes) if Self::has_valid_public_key_bytes(&bytes) => bytes, _ => return false, }; @@ -60,7 +60,7 @@ impl Wallet { _ => return false, }; - if public_key_bytes.len() != Self::PUBLIC_KEY_LENGTH { + if !Self::has_valid_public_key_bytes(public_key_bytes) { return false; } diff --git a/src/wallets/verify_address.rs b/src/wallets/verify_address.rs index 00df558..1504087 100644 --- a/src/wallets/verify_address.rs +++ b/src/wallets/verify_address.rs @@ -8,7 +8,15 @@ impl Wallet { } // FN-DSA encoded public keys carry the security parameter in the first byte. - public_key_bytes[0] == Self::FN_DSA_LOGN as u8 + if public_key_bytes[0] != Self::FN_DSA_LOGN as u8 { + return false; + } + + // The second public-key byte is chain-specific entropy. This restores + // the old key-grinding rule so deterministic seed-derived keys cannot + // be imported or registered unless they happen to match the network rule. + public_key_bytes[Self::REQUIRED_PUBLIC_KEY_PREFIX_INDEX] + == Self::REQUIRED_PUBLIC_KEY_PREFIX_BYTE } pub fn short_address_validation(short_address: &str) -> bool { diff --git a/src/wallets/wallet_bytes.rs b/src/wallets/wallet_bytes.rs index bb30250..6716e93 100644 --- a/src/wallets/wallet_bytes.rs +++ b/src/wallets/wallet_bytes.rs @@ -115,7 +115,7 @@ impl Wallet { } pub fn public_key_bytes_to_short_address_bytes(public_key_bytes: &[u8]) -> Option> { - if public_key_bytes.len() != Self::PUBLIC_KEY_LENGTH { + if !Self::has_valid_public_key_bytes(public_key_bytes) { return None; } @@ -146,11 +146,14 @@ impl Wallet { pub fn normalize_saved_public_key_bytes(public_key_hex: &str) -> Option> { let bytes = decode(public_key_hex).ok()?; - if bytes.len() == Self::PUBLIC_KEY_LENGTH { + if Self::has_valid_public_key_bytes(&bytes) { return Some(bytes); } if bytes.len() == Self::ADDRESS_BYTES_LENGTH && bytes[0] == Self::current_network_byte() { - return Some(bytes[1..].to_vec()); + let public_key_bytes = bytes[1..].to_vec(); + if Self::has_valid_public_key_bytes(&public_key_bytes) { + return Some(public_key_bytes); + } } None }