Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions dash-spv-ffi/tests/test_platform_integration_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,10 +357,11 @@ fn test_error_string_lifecycle() {
fn test_handle_lifecycle() {
unsafe {
// Test null handle operations
let null_handle = ptr::null_mut();
let null_client: *mut FFIDashSpvClient = ptr::null_mut();
let null_handle: *mut CoreSDKHandle = ptr::null_mut();

// Getting core handle from null client
let handle = ffi_dash_spv_get_core_handle(null_handle);
let handle = ffi_dash_spv_get_core_handle(null_client);
assert!(handle.is_null());

// Releasing null handle should be safe
Expand Down
6 changes: 6 additions & 0 deletions dash-spv-ffi/tests/unit/test_type_conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ mod tests {
current_filter_tip: None,
masternode_engine: None,
last_masternode_diff_height: None,
sync_base_height: 0,
synced_from_checkpoint: false,
};

let ffi_state = FFIChainState::from(state);
Expand Down Expand Up @@ -213,6 +215,10 @@ mod tests {
pending_filter_requests: 0,
filter_request_timeouts: u64::MAX,
filter_requests_retried: u64::MAX,
connected_peers: 0,
total_peers: 0,
header_height: 0,
filter_height: 0,
};

let ffi_stats = FFISpvStats::from(stats);
Expand Down
1 change: 1 addition & 0 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cargo-fuzz = true
[dependencies]
honggfuzz = { version = "0.5", default-features = false }
dashcore = { path = "../dash", features = [ "serde" ] }
key-wallet = { path = "../key-wallet" }

serde = { version = "1.0.219", features = [ "derive" ] }
serde_json = "1.0"
Expand Down
10 changes: 5 additions & 5 deletions fuzz/fuzz_targets/dash/deserialize_psbt.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use honggfuzz::fuzz;

fn do_test(data: &[u8]) {
let psbt: Result<dashcore::psbt::PartiallySignedTransaction, _> =
dashcore::psbt::Psbt::deserialize(data);
let psbt: Result<key_wallet::psbt::PartiallySignedTransaction, _> =
key_wallet::psbt::Psbt::deserialize(data);
match psbt {
Err(_) => {}
Ok(psbt) => {
let ser = dashcore::psbt::Psbt::serialize(&psbt);
let deser = dashcore::psbt::Psbt::deserialize(&ser).unwrap();
let ser = key_wallet::psbt::Psbt::serialize(&psbt);
let deser = key_wallet::psbt::Psbt::deserialize(&ser).unwrap();
// Since the fuzz data could order psbt fields differently, we compare to our deser/ser instead of data
assert_eq!(ser, dashcore::psbt::Psbt::serialize(&deser));
assert_eq!(ser, key_wallet::psbt::Psbt::serialize(&deser));
}
}
}
Expand Down
157 changes: 87 additions & 70 deletions key-wallet-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use std::str::FromStr;
use std::sync::Arc;

use key_wallet::{
self as kw, address as kw_address, derivation::HDWallet as KwHDWallet, mnemonic as kw_mnemonic,
DerivationPath as KwDerivationPath, ExtendedPrivKey, ExtendedPubKey, Network as KwNetwork,
self as kw, derivation::HDWallet as KwHDWallet, mnemonic as kw_mnemonic, Address as KwAddress,
AddressType as KwAddressType, DerivationPath as KwDerivationPath, ExtendedPrivKey,
ExtendedPubKey, Network as KwNetwork,
};
use secp256k1::{PublicKey, Secp256k1};

Expand Down Expand Up @@ -75,20 +76,21 @@ pub enum AddressType {
P2SH,
}

impl From<kw_address::AddressType> for AddressType {
fn from(t: kw_address::AddressType) -> Self {
impl From<KwAddressType> for AddressType {
fn from(t: KwAddressType) -> Self {
match t {
kw_address::AddressType::P2PKH => AddressType::P2PKH,
kw_address::AddressType::P2SH => AddressType::P2SH,
KwAddressType::P2pkh => AddressType::P2PKH,
KwAddressType::P2sh => AddressType::P2SH,
_ => AddressType::P2PKH, // Default to P2PKH for unknown types
}
}
}

impl From<AddressType> for kw_address::AddressType {
impl From<AddressType> for KwAddressType {
fn from(t: AddressType) -> Self {
match t {
AddressType::P2PKH => kw_address::AddressType::P2PKH,
AddressType::P2SH => kw_address::AddressType::P2SH,
AddressType::P2PKH => KwAddressType::P2pkh,
AddressType::P2SH => KwAddressType::P2sh,
}
}
}
Expand Down Expand Up @@ -189,6 +191,15 @@ impl From<kw::Error> for KeyWalletError {
kw::Error::KeyError(msg) => KeyWalletError::KeyError {
message: msg,
},
kw::Error::CoinJoinNotEnabled => KeyWalletError::KeyError {
message: "CoinJoin not enabled".into(),
},
kw::Error::Serialization(msg) => KeyWalletError::KeyError {
message: format!("Serialization error: {}", msg),
},
kw::Error::InvalidParameter(msg) => KeyWalletError::KeyError {
message: format!("Invalid parameter: {}", msg),
},
}
}
}
Expand All @@ -201,6 +212,14 @@ impl From<kw::bip32::Error> for KeyWalletError {
}
}

impl From<kw::dashcore::address::Error> for KeyWalletError {
fn from(e: kw::dashcore::address::Error) -> Self {
KeyWalletError::AddressError {
message: e.to_string(),
}
}
}

// Validate mnemonic function
pub fn validate_mnemonic(phrase: String, language: Language) -> Result<bool, KeyWalletError> {
Ok(kw::Mnemonic::validate(&phrase, language.into()))
Expand Down Expand Up @@ -439,53 +458,33 @@ impl ExtPubKey {

// Address wrapper
pub struct Address {
inner: kw_address::Address,
inner: KwAddress,
}

impl Address {
pub fn from_string(address: String, network: Network) -> Result<Self, KeyWalletError> {
let inner = kw_address::Address::from_str(&address).map_err(|e| KeyWalletError::from(e))?;
let unchecked_addr = KwAddress::from_str(&address).map_err(|e| KeyWalletError::from(e))?;

// Validate that the parsed network matches the expected network
// Note: Testnet, Devnet, and Regtest all share the same address prefixes (140/19)
// so we need to be flexible when comparing these networks
let parsed_network: KwNetwork = inner.network;
// Convert to expected network and require it
let expected_network: KwNetwork = network.into();

let networks_compatible = match (parsed_network, expected_network) {
// Exact matches are always OK
(n1, n2) if n1 == n2 => true,
// Testnet addresses can be used on devnet/regtest and vice versa
(KwNetwork::Testnet, KwNetwork::Devnet)
| (KwNetwork::Testnet, KwNetwork::Regtest)
| (KwNetwork::Devnet, KwNetwork::Testnet)
| (KwNetwork::Devnet, KwNetwork::Regtest)
| (KwNetwork::Regtest, KwNetwork::Testnet)
| (KwNetwork::Regtest, KwNetwork::Devnet) => true,
// All other combinations are incompatible
_ => false,
};

if !networks_compatible {
return Err(KeyWalletError::AddressError {
message: format!(
"Address is for network {:?}, expected {:?}",
inner.network, network
),
});
}
let inner = unchecked_addr.require_network(expected_network).map_err(|e| {
KeyWalletError::AddressError {
message: format!("Address network validation failed: {}", e),
}
})?;

Ok(Self {
inner,
})
}

pub fn from_public_key(public_key: Vec<u8>, network: Network) -> Result<Self, KeyWalletError> {
let pubkey =
let secp_pubkey =
PublicKey::from_slice(&public_key).map_err(|e| KeyWalletError::Secp256k1Error {
message: e.to_string(),
})?;
let inner = kw_address::Address::p2pkh(&pubkey, network.into());
let dashcore_pubkey = kw::dashcore::PublicKey::new(secp_pubkey);
let inner = KwAddress::p2pkh(&dashcore_pubkey, network.into());
Ok(Self {
inner,
})
Expand All @@ -496,11 +495,11 @@ impl Address {
}

pub fn get_type(&self) -> AddressType {
self.inner.address_type.into()
self.inner.address_type().unwrap_or(KwAddressType::P2pkh).into()
}

pub fn get_network(&self) -> Network {
match self.inner.network {
match *self.inner.network() {
KwNetwork::Dash => Network::Dash,
KwNetwork::Testnet => Network::Testnet,
KwNetwork::Regtest => Network::Regtest,
Expand All @@ -510,19 +509,19 @@ impl Address {
}

pub fn get_script_pubkey(&self) -> Vec<u8> {
self.inner.script_pubkey()
self.inner.script_pubkey().into()
}
}

// Address generator wrapper
pub struct AddressGenerator {
inner: kw_address::AddressGenerator,
network: Network,
}

impl AddressGenerator {
pub fn new(network: Network) -> Self {
Self {
inner: kw_address::AddressGenerator::new(network.into()),
network,
}
}

Expand All @@ -538,15 +537,44 @@ impl AddressGenerator {
message: e.to_string(),
})?;

// Generate addresses for a single index
let addrs = self
.inner
.generate_range(&xpub, external, index, 1)
.map_err(|e| KeyWalletError::from(e))?;
let secp = Secp256k1::new();

let addr = addrs.into_iter().next().ok_or_else(|| KeyWalletError::KeyError {
message: "Failed to generate address".into(),
})?;
// Derive child key: 0 for external (receiving), 1 for internal (change)
let chain_code = if external {
0
} else {
1
};
let child_chain = xpub
.ckd_pub(
&secp,
kw::ChildNumber::from_normal_idx(chain_code).map_err(|e| {
KeyWalletError::InvalidDerivationPath {
message: e.to_string(),
}
})?,
)
.map_err(|e| KeyWalletError::KeyError {
message: e.to_string(),
})?;

// Derive specific index
let child = child_chain
.ckd_pub(
&secp,
kw::ChildNumber::from_normal_idx(index).map_err(|e| {
KeyWalletError::InvalidDerivationPath {
message: e.to_string(),
}
})?,
)
.map_err(|e| KeyWalletError::KeyError {
message: e.to_string(),
})?;

// Generate P2PKH address from the public key
let dashcore_pubkey = kw::dashcore::PublicKey::new(child.public_key);
let addr = KwAddress::p2pkh(&dashcore_pubkey, self.network.into());

Ok(Arc::new(Address {
inner: addr,
Expand All @@ -560,25 +588,14 @@ impl AddressGenerator {
start: u32,
count: u32,
) -> Result<Vec<Arc<Address>>, KeyWalletError> {
// Parse the extended public key from string
let xpub =
ExtendedPubKey::from_str(&account_xpub.xpub).map_err(|e| KeyWalletError::KeyError {
message: e.to_string(),
})?;
let mut addresses = Vec::new();

let addrs = self
.inner
.generate_range(&xpub, external, start, count)
.map_err(|e| KeyWalletError::from(e))?;
for i in 0..count {
let addr = self.generate(account_xpub.clone(), external, start + i)?;
addresses.push(addr);
}

Ok(addrs
.into_iter()
.map(|addr| {
Arc::new(Address {
inner: addr,
})
})
.collect())
Ok(addresses)
}
}

Expand Down
Loading