Skip to content

Commit 53b5980

Browse files
committed
refactor(platform-wallet): choose the key source natively
`derive_core_address_private_key` and `bip44_address_private_key` take a `master_key` callback instead of an already resolved master key. `platform-wallet` now decides whether the wallet holds resident private keys, calls `master_key` only when it does not, with the wallet-manager lock released, and wipes the master key afterwards. A `master_key` failure comes back as `CoreAddressKeyError::KeySource` unchanged, so the FFI returns the resolver's own result. The address is looked up before `master_key` runs, so an address the wallet does not hold never triggers a Keychain prompt. `reveal_address_private_key` in the FFI only adapts the `MnemonicResolverHandle` into that callback and marshals the result. A removed wallet keeps the `ErrorInvalidHandle` code the capability probe returned before.
1 parent 36585fc commit 53b5980

4 files changed

Lines changed: 249 additions & 182 deletions

File tree

‎packages/rs-platform-wallet-ffi/src/address_private_key.rs‎

Lines changed: 52 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,25 @@
33
//!
44
//! Thin marshalling over
55
//! [`PlatformWallet::derive_core_address_private_key`](platform_wallet::PlatformWallet::derive_core_address_private_key):
6-
//! the whole address-lookup-and-derive decision lives on the Rust
7-
//! library side. This shim only resolves the mnemonic (for the
8-
//! external-signable / watch-only wallets the iOS app holds, whose seed
9-
//! lives in the iOS Keychain rather than the `WalletManager`) and
10-
//! marshals the resulting hex + WIF strings across the C ABI.
11-
//!
12-
//! # Key source: chosen by wallet capability
13-
//!
14-
//! Mirrors the sibling
15-
//! [`platform_wallet_preview_identity_registration_keys`](crate::platform_wallet_preview_identity_registration_keys):
16-
//! a [`MnemonicResolverHandle`] is a *capability*, consulted only when
17-
//! the in-process wallet lacks resident private keys.
18-
//!
19-
//! - **Resident-key wallet** (created from a raw seed): the resolver is
20-
//! never touched and the key is derived from the in-process wallet.
21-
//! - **External-signable / watch-only wallet** (the iOS shape): the
22-
//! mnemonic is resolved on demand (keyed by the wallet handle's own
23-
//! `wallet_id`), a master [`ExtendedPrivKey`] is built, the address's
24-
//! key is derived from it, and the master's scalar is wiped before
25-
//! returning. If the resolver handle is null for such a wallet the
26-
//! call errors.
27-
//!
28-
//! The two-phase locking rule from the identity preview applies here
29-
//! too: the wallet-manager read guard is NEVER held across the Swift
30-
//! resolver callback.
6+
//! the address lookup, the choice of key source and the derive all live
7+
//! on the Rust library side. This shim turns the [`MnemonicResolverHandle`]
8+
//! into the `master_key` callback platform-wallet calls for a wallet
9+
//! without resident private keys, such as the external-signable wallets
10+
//! the iOS app holds, and marshals the resulting hex + WIF strings across
11+
//! the C ABI. A null resolver handle fails only when the callback runs.
3112
3213
use std::ffi::{CStr, CString};
3314
use std::os::raw::c_char;
3415

3516
use key_wallet::bip32::ExtendedPrivKey;
36-
use platform_wallet::{CoreAddressPrivateKey, PlatformWallet, PlatformWalletError};
17+
use platform_wallet::{
18+
CoreAddressKeyError, CoreAddressPrivateKey, PlatformWallet, PlatformWalletError,
19+
};
3720
use zeroize::{Zeroize, Zeroizing};
3821

3922
use crate::error::*;
4023
use crate::handle::*;
4124
use crate::identity_keys_from_mnemonic::resolve_master_from_resolver;
42-
use crate::types::Network;
4325
use crate::{check_ptr, unwrap_option_or_return, unwrap_result_or_return};
4426
use rs_sdk_ffi::MnemonicResolverHandle;
4527

@@ -105,13 +87,15 @@ pub unsafe extern "C" fn platform_wallet_address_private_key(
10587
mnemonic_resolver_handle,
10688
address_cstr,
10789
out,
108-
PlatformWallet::derive_core_address_private_key,
90+
|wallet, address, master_key| wallet.derive_core_address_private_key(address, master_key),
10991
)
11092
}
11193

94+
/// The `master_key` callback [`reveal_address_private_key`] passes to `derive`.
95+
type MasterKey<'a> = &'a dyn Fn() -> Result<ExtendedPrivKey, PlatformWalletFFIResult>;
96+
11297
/// Reveals the private key `derive` finds for `address_cstr` and writes it
113-
/// into `out`, resolving the master key through the mnemonic resolver when
114-
/// the wallet holds no resident keys.
98+
/// into `out`, with the mnemonic resolver as the master key source.
11599
///
116100
/// # Safety
117101
/// As for [`platform_wallet_address_private_key`].
@@ -120,76 +104,51 @@ unsafe fn reveal_address_private_key(
120104
mnemonic_resolver_handle: *mut MnemonicResolverHandle,
121105
address_cstr: *const c_char,
122106
out: *mut AddressPrivateKeyFFI,
123-
derive: fn(
107+
derive: impl FnOnce(
124108
&PlatformWallet,
125109
&str,
126-
Option<&ExtendedPrivKey>,
127-
) -> Result<CoreAddressPrivateKey, PlatformWalletError>,
110+
MasterKey<'_>,
111+
)
112+
-> Result<CoreAddressPrivateKey, CoreAddressKeyError<PlatformWalletFFIResult>>,
128113
) -> PlatformWalletFFIResult {
129114
check_ptr!(out);
130115
unsafe { *out = AddressPrivateKeyFFI::empty() };
131116
check_ptr!(address_cstr);
132117

133118
let address_str = unwrap_result_or_return!(unsafe { CStr::from_ptr(address_cstr) }.to_str());
134119

135-
let option = PLATFORM_WALLET_STORAGE.with_item(
136-
wallet_handle,
137-
|wallet| -> Result<CoreAddressPrivateKey, PlatformWalletFFIResult> {
138-
let network: Network = wallet.network();
139-
140-
// Phase 1 — capability probe under a SHORT read guard,
141-
// dropped before any resolver interaction.
142-
let is_resident = {
143-
let wm = wallet.wallet_manager().blocking_read();
144-
match wm.get_wallet(&wallet.wallet_id()) {
145-
Some(key_wallet) => {
146-
!key_wallet.is_external_signable() && !key_wallet.is_watch_only()
147-
}
148-
None => {
149-
return Err(PlatformWalletFFIResult::err(
150-
PlatformWalletFFIResultCode::ErrorInvalidHandle,
151-
"wallet not found in wallet manager",
152-
));
153-
}
154-
}
155-
};
156-
157-
// Phase 2 — resolve the master xpriv for external-signable /
158-
// watch-only wallets. NEVER under the guard above: the
159-
// resolver synchronously re-enters Swift and reads the iOS
160-
// Keychain (which can stall on biometric unlock).
161-
let mut master_opt: Option<ExtendedPrivKey> = None;
162-
if !is_resident {
163-
if mnemonic_resolver_handle.is_null() {
164-
return Err(PlatformWalletFFIResult::err(
165-
PlatformWalletFFIResultCode::ErrorWalletOperation,
166-
"this wallet has no resident private keys (external-signable / \
167-
watch-only); a mnemonic resolver handle is required to reveal \
168-
an address private key",
169-
));
170-
}
171-
let wallet_id = wallet.wallet_id();
172-
// SAFETY: handle is non-null (checked) and the caller's
173-
// safety contract guarantees it came from
174-
// `dash_sdk_mnemonic_resolver_create`.
175-
master_opt = Some(unsafe {
176-
resolve_master_from_resolver(mnemonic_resolver_handle, &wallet_id, network)?
177-
});
120+
let option = PLATFORM_WALLET_STORAGE.with_item(wallet_handle, |wallet| {
121+
let master_key = || {
122+
if mnemonic_resolver_handle.is_null() {
123+
return Err(PlatformWalletFFIResult::err(
124+
PlatformWalletFFIResultCode::ErrorWalletOperation,
125+
"this wallet has no resident private keys (external-signable / \
126+
watch-only); a mnemonic resolver handle is required to reveal \
127+
an address private key",
128+
));
178129
}
179-
180-
// Phase 3 — library lookup + derive (re-acquires the guard
181-
// internally; the resolver, if any, has already run).
182-
let result = derive(wallet, address_str, master_opt.as_ref());
183-
184-
// Wipe the resolved master's inner scalar — `ExtendedPrivKey`
185-
// has no `Drop` / `Zeroize`. No-op on the resident path.
186-
if let Some(mut master) = master_opt {
187-
master.private_key.zeroize();
130+
// SAFETY: the handle is non-null (checked) and the caller's
131+
// safety contract guarantees it came from
132+
// `dash_sdk_mnemonic_resolver_create`.
133+
unsafe {
134+
resolve_master_from_resolver(
135+
mnemonic_resolver_handle,
136+
&wallet.wallet_id(),
137+
wallet.network(),
138+
)
188139
}
189-
190-
result.map_err(PlatformWalletFFIResult::from)
191-
},
192-
);
140+
};
141+
derive(wallet, address_str, &master_key).map_err(|error| match error {
142+
CoreAddressKeyError::KeySource(result) => result,
143+
CoreAddressKeyError::Wallet(PlatformWalletError::WalletNotFound(_)) => {
144+
PlatformWalletFFIResult::err(
145+
PlatformWalletFFIResultCode::ErrorInvalidHandle,
146+
"wallet not found in wallet manager",
147+
)
148+
}
149+
CoreAddressKeyError::Wallet(error) => error.into(),
150+
})
151+
});
193152
let result = unwrap_option_or_return!(option);
194153
let secret = unwrap_result_or_return!(result);
195154

@@ -298,7 +257,7 @@ pub unsafe extern "C" fn platform_wallet_bip44_address_private_key(
298257
mnemonic_resolver_handle,
299258
address_cstr,
300259
out,
301-
PlatformWallet::bip44_address_private_key,
260+
|wallet, address, master_key| wallet.bip44_address_private_key(address, master_key),
302261
)
303262
}
304263

@@ -335,12 +294,13 @@ mod tests {
335294
use super::*;
336295
use crate::runtime::runtime;
337296
use crate::test_fixtures::out_string;
297+
use crate::types::Network;
338298
use platform_wallet::first_receive_address_for_mnemonic;
339299
use platform_wallet::test_support::{
340300
test_platform_wallet_manager, TEST_FRENCH_MNEMONIC as FRENCH, TEST_PLATFORM_WALLET_MNEMONIC,
341301
};
342302
use std::ptr;
343-
use PlatformWalletFFIResultCode::{ErrorNullPointer, ErrorWalletOperation, Success};
303+
use PlatformWalletFFIResultCode::{ErrorInvalidHandle, ErrorNullPointer, ErrorWalletOperation};
344304

345305
#[test]
346306
fn should_report_bip44_scan_paths_and_refuse_without_a_key_source() {
@@ -400,7 +360,7 @@ mod tests {
400360
});
401361
assert!(manager.get_wallet_blocking(&wallet_id).is_none());
402362
assert!(path(&own.to_string()).is_err());
403-
assert_ne!(key(&own.to_string()), Success);
363+
assert_eq!(key(&own.to_string()), ErrorInvalidHandle);
404364
PLATFORM_WALLET_STORAGE.remove(handle);
405365
}
406366
}

‎packages/rs-platform-wallet/src/lib.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ pub use masternode::{
7373
aggregate_masternodes, ListMembership, MasternodeKeyRole, MasternodeRecord, MasternodeSource,
7474
MasternodeStatus, TrackedMasternode, TrackedMasternodeSnapshot, WalletMasternodes,
7575
};
76-
pub use wallet::core_address_key::CoreAddressPrivateKey;
76+
pub use wallet::core_address_key::{CoreAddressKeyError, CoreAddressPrivateKey};
7777
pub use wallet::identity::network::{
7878
derive_identity_auth_keypair, AutoAcceptProofSource, ContactCryptoProvider, ContactInfoOpened,
7979
ContactInfoPublishOutcome, ContactInfoSealed, PreparedDashPayPayment, SeedBindingVerification,

0 commit comments

Comments
 (0)