From 0d8f9594ec53ebebed6525eb3d488f8301af1edd Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sun, 10 May 2026 03:07:39 -0500 Subject: [PATCH] fix(ffi): validate account seed lengths --- key-wallet-ffi/src/account_derivation.rs | 30 +++++++- .../src/account_derivation_tests.rs | 73 ++++++++++++++++++- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/key-wallet-ffi/src/account_derivation.rs b/key-wallet-ffi/src/account_derivation.rs index cabc9d35a..9191fd91c 100644 --- a/key-wallet-ffi/src/account_derivation.rs +++ b/key-wallet-ffi/src/account_derivation.rs @@ -16,6 +16,26 @@ use std::ptr; // No extra FFI enum for chain selection; account semantics decide path. +fn validate_bls_seed_len(seed_len: usize, error: *mut FFIError) -> bool { + if !(16..=64).contains(&seed_len) { + unsafe { + (*error).set(FFIErrorCode::InvalidInput, "Seed length must be between 16 and 64 bytes"); + } + return false; + } + true +} + +fn validate_eddsa_seed_len(seed_len: usize, error: *mut FFIError) -> bool { + if seed_len < 16 { + unsafe { + (*error).set(FFIErrorCode::InvalidInput, "Seed length must be at least 16 bytes"); + } + return false; + } + true +} + /// Derive an extended private key from an account at a given index, using the provided master xpriv. /// /// Returns an opaque FFIExtendedPrivKey pointer that must be freed with `extended_private_key_free`. @@ -65,7 +85,7 @@ pub unsafe extern "C" fn account_derive_extended_private_key_at( /// /// # Safety /// - `account` must be a valid, non-null pointer to an `FFIBLSAccount` (only when `bls` feature is enabled). -/// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). +/// - `seed` must point to a readable buffer of length `seed_len` (16..=64 bytes expected). /// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "bls")] @@ -79,8 +99,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_seed( ) -> *mut c_char { let account = deref_ptr!(account, error); check_ptr!(seed, error); - if seed_len == 0 || seed_len > 64 { - (*error).set(FFIErrorCode::InvalidInput, "Seed length must be between 1 and 64 bytes"); + if !validate_bls_seed_len(seed_len, error) { return ptr::null_mut(); } let seed_slice = std::slice::from_raw_parts(seed, seed_len); @@ -147,7 +166,7 @@ pub unsafe extern "C" fn bls_account_derive_private_key_from_mnemonic( /// /// # Safety /// - `account` must be a valid, non-null pointer to an `FFIEdDSAAccount` (only when `eddsa` feature is enabled). -/// - `seed` must point to a readable buffer of length `seed_len` (1..=64 bytes expected). +/// - `seed` must point to a readable buffer of length `seed_len` (at least 16 bytes expected). /// - `error` must be a valid pointer to an FFIError. /// - Returned string must be freed with `string_free`. #[cfg(feature = "eddsa")] @@ -161,6 +180,9 @@ pub unsafe extern "C" fn eddsa_account_derive_private_key_from_seed( ) -> *mut c_char { let account = deref_ptr!(account, error); check_ptr!(seed, error); + if !validate_eddsa_seed_len(seed_len, error) { + return ptr::null_mut(); + } let seed_slice = std::slice::from_raw_parts(seed, seed_len); let sk = unwrap_or_return!( account.inner().derive_from_seed_private_key_at(seed_slice, index), diff --git a/key-wallet-ffi/src/account_derivation_tests.rs b/key-wallet-ffi/src/account_derivation_tests.rs index 688560d3a..211596492 100644 --- a/key-wallet-ffi/src/account_derivation_tests.rs +++ b/key-wallet-ffi/src/account_derivation_tests.rs @@ -10,9 +10,10 @@ mod tests { use crate::types::FFIAccountKind; use crate::wallet; use dash_network::ffi::FFINetwork; + #[cfg(feature = "bls")] + use key_wallet::account::{AccountType, BLSAccount}; - const MNEMONIC: &str = - "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; + const MNEMONIC: &str = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; #[test] fn test_account_derive_private_key_at_receive_index() { @@ -246,4 +247,72 @@ mod tests { wallet::wallet_free(wallet); } } + + #[cfg(feature = "bls")] + #[test] + fn test_bls_seed_helper_rejects_invalid_seed_lengths() { + let mut error = FFIError::default(); + let account = BLSAccount::from_seed( + None, + AccountType::ProviderOperatorKeys, + [1u8; 32], + dashcore::Network::Testnet, + ) + .unwrap(); + let ffi_account = crate::account::FFIBLSAccount::new(&account); + let short_seed = [0u8; 15]; + let long_seed = [0u8; 65]; + + let too_short = unsafe { + bls_account_derive_private_key_from_seed( + &ffi_account, + short_seed.as_ptr(), + short_seed.len(), + 0, + &mut error, + ) + }; + assert!(too_short.is_null()); + assert_eq!(error.code, FFIErrorCode::InvalidInput); + + error = FFIError::default(); + let too_long = unsafe { + bls_account_derive_private_key_from_seed( + &ffi_account, + long_seed.as_ptr(), + long_seed.len(), + 0, + &mut error, + ) + }; + assert!(too_long.is_null()); + assert_eq!(error.code, FFIErrorCode::InvalidInput); + } + + #[cfg(feature = "eddsa")] + #[test] + fn test_eddsa_seed_helper_rejects_invalid_seed_lengths() { + let mut error = FFIError::default(); + let account = key_wallet::account::EdDSAAccount::from_seed( + None, + FFIAccountKind::ProviderPlatformKeys.to_account_type(0), + [1u8; 32], + FFINetwork::Testnet.into(), + ) + .unwrap(); + let ffi_account = crate::account::FFIEdDSAAccount::new(&account); + let short_seed = [0u8; 15]; + + let too_short = unsafe { + eddsa_account_derive_private_key_from_seed( + &ffi_account, + short_seed.as_ptr(), + short_seed.len(), + 0, + &mut error, + ) + }; + assert!(too_short.is_null()); + assert_eq!(error.code, FFIErrorCode::InvalidInput); + } }