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
3213use std:: ffi:: { CStr , CString } ;
3314use std:: os:: raw:: c_char;
3415
3516use key_wallet:: bip32:: ExtendedPrivKey ;
36- use platform_wallet:: { CoreAddressPrivateKey , PlatformWallet , PlatformWalletError } ;
17+ use platform_wallet:: {
18+ CoreAddressKeyError , CoreAddressPrivateKey , PlatformWallet , PlatformWalletError ,
19+ } ;
3720use zeroize:: { Zeroize , Zeroizing } ;
3821
3922use crate :: error:: * ;
4023use crate :: handle:: * ;
4124use crate :: identity_keys_from_mnemonic:: resolve_master_from_resolver;
42- use crate :: types:: Network ;
4325use crate :: { check_ptr, unwrap_option_or_return, unwrap_result_or_return} ;
4426use 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}
0 commit comments