From c9c81b9b0c387c77d3b4f79b9d11f19d5498377d Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:49:06 +0200 Subject: [PATCH 1/3] fix: case-insensitive .dash suffix and UTXO double-spend prevention 1. DPNS name normalization: strip the `.dash` suffix case-insensitively before querying Platform, so `.DASH`, `.Dash`, etc. all resolve. 2. UTXO double-spend prevention via optimistic validation: after signing the transaction (optimistically, without locks), re-acquire the write lock and verify that every selected UTXO is still in the spendable set before marking them spent. If a concurrent caller already claimed an outpoint, return an error instead of broadcasting a transaction the network would reject. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/wallet/core/wallet.rs | 50 ++++++++++++++++--- .../rs-sdk/src/platform/dpns_usernames/mod.rs | 2 +- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/rs-platform-wallet/src/wallet/core/wallet.rs b/packages/rs-platform-wallet/src/wallet/core/wallet.rs index 386242a7368..b89eb9661ea 100644 --- a/packages/rs-platform-wallet/src/wallet/core/wallet.rs +++ b/packages/rs-platform-wallet/src/wallet/core/wallet.rs @@ -4,9 +4,9 @@ use std::sync::Arc; use super::balance::WalletBalance; +use dashcore::Address as DashAddress; use dashcore::secp256k1::{Message, Secp256k1}; use dashcore::sighash::SighashCache; -use dashcore::Address as DashAddress; use dashcore::{OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; use key_wallet::Utxo; use tokio::sync::RwLock; @@ -316,6 +316,38 @@ impl CoreWallet { self.sign_transaction_inputs(&secp, &mut tx, &selected_utxos) .await?; + // 5b. Validate-and-mark under write lock: verify that the UTXOs we + // selected (under a read lock earlier) haven't been claimed by a + // concurrent caller. If they have, fail fast with a clear error + // instead of broadcasting a transaction the network will reject. + { + use crate::wallet::platform_wallet_traits::WalletTransactionChecker; + use key_wallet::transaction_checking::TransactionContext; + let mut state = self.state.write().await; + + // Re-check that our selected outpoints are still spendable. + let current_spendable: std::collections::BTreeSet = state + .managed_state + .wallet_info() + .get_spendable_utxos() + .iter() + .map(|u| u.outpoint) + .collect(); + + for (outpoint, _, _) in &selected_utxos { + if !current_spendable.contains(outpoint) { + return Err(PlatformWalletError::TransactionBuild( + "Selected UTXOs are no longer available (concurrent transaction). Please retry.".to_string(), + )); + } + } + + // All UTXOs still available — mark them as spent atomically. + state + .check_core_transaction(&tx, TransactionContext::Mempool, true, true) + .await; + } + // 6. Broadcast. self.broadcast_transaction(&tx).await?; @@ -479,12 +511,16 @@ impl CoreWallet { // Derive private keys and sign. for (i, (input, sighash)) in tx.input.iter_mut().zip(sighashes).enumerate() { let path = &derivation_paths[i]; - let extended_key = info.managed_state.wallet().derive_extended_private_key(path).map_err(|e| { - PlatformWalletError::TransactionBuild(format!( - "Failed to derive key for input {}: {}", - i, e - )) - })?; + let extended_key = info + .managed_state + .wallet() + .derive_extended_private_key(path) + .map_err(|e| { + PlatformWalletError::TransactionBuild(format!( + "Failed to derive key for input {}: {}", + i, e + )) + })?; let input_private_key = extended_key.to_priv(); let message = Message::from_digest(sighash.into()); diff --git a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs index 58c1b4a9792..97b09bad7e5 100644 --- a/packages/rs-sdk/src/platform/dpns_usernames/mod.rs +++ b/packages/rs-sdk/src/platform/dpns_usernames/mod.rs @@ -427,7 +427,7 @@ impl Sdk { let label = if let Some(dot_pos) = name.rfind('.') { let (label_part, suffix) = name.split_at(dot_pos); // Only strip the suffix if it's exactly ".dash" - if suffix == ".dash" { + if suffix.eq_ignore_ascii_case(".dash") { label_part } else { // If it's not ".dash", treat the whole thing as the label From fe8f26387b12bc3eb5960fa02bc1d44a4379b945 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:26:41 +0200 Subject: [PATCH 2/3] fix(deps): convert rust-dashcore path deps to git branch deps Path dependencies on ../rust-dashcore break when this repo is used as a git dependency (e.g. from dash-evo-tool CI). Switch to git deps pointing to feat/blockchain-identities-account-type branch. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a835e3ba485..8f290e3e864 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,13 +47,13 @@ members = [ ] [workspace.dependencies] -dashcore = { path = "../rust-dashcore/dash" } -dash-spv = { path = "../rust-dashcore/dash-spv" } -dash-spv-ffi = { path = "../rust-dashcore/dash-spv-ffi" } -key-wallet = { path = "../rust-dashcore/key-wallet" } -key-wallet-ffi = { path = "../rust-dashcore/key-wallet-ffi" } -key-wallet-manager = { path = "../rust-dashcore/key-wallet-manager" } -dashcore-rpc = { path = "../rust-dashcore/rpc-client" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +dash-spv = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +dash-spv-ffi = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +key-wallet = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } # Optimize heavy crypto crates even in dev/test builds so that # Halo 2 proof generation and verification run at near-release speed. From d971326b00b43608623046dc1aa35ad72d8fd6e4 Mon Sep 17 00:00:00 2001 From: Lukasz Klimek <842586+lklimek@users.noreply.github.com> Date: Thu, 9 Apr 2026 16:50:54 +0200 Subject: [PATCH 3/3] Revert "fix(deps): convert rust-dashcore path deps to git branch deps" This reverts commit fe8f26387b12bc3eb5960fa02bc1d44a4379b945. --- Cargo.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f290e3e864..a835e3ba485 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,13 +47,13 @@ members = [ ] [workspace.dependencies] -dashcore = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -dash-spv = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -dash-spv-ffi = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -key-wallet = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", branch = "feat/blockchain-identities-account-type" } +dashcore = { path = "../rust-dashcore/dash" } +dash-spv = { path = "../rust-dashcore/dash-spv" } +dash-spv-ffi = { path = "../rust-dashcore/dash-spv-ffi" } +key-wallet = { path = "../rust-dashcore/key-wallet" } +key-wallet-ffi = { path = "../rust-dashcore/key-wallet-ffi" } +key-wallet-manager = { path = "../rust-dashcore/key-wallet-manager" } +dashcore-rpc = { path = "../rust-dashcore/rpc-client" } # Optimize heavy crypto crates even in dev/test builds so that # Halo 2 proof generation and verification run at near-release speed.