diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index 4a9a132a8..aade637a3 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -92,6 +92,11 @@ typedef enum { Platform Payment address (DIP-17) - Path: m/9'/5'/17'/account'/key_class'/index */ PLATFORM_PAYMENT = 13, + /* + Platform Address Funding (DIP-17) - Path: m/9'/5'/17'/0'/2'/index + For asset lock funding keys (fixed account=0', key_class=2') + */ + PLATFORM_ADDRESS_FUNDING = 14, } FFIAccountType; /* @@ -637,6 +642,10 @@ typedef struct { Whether provider platform keys account exists */ bool has_provider_platform_keys; + /* + Whether platform address funding account exists (DIP-17) + */ + bool has_platform_address_funding; } FFIManagedAccountCollectionSummary; /* @@ -1927,6 +1936,18 @@ bool derivation_identity_authentication_path(FFINetwork network, FFIError *error) ; +/* + Derive platform address funding path (m/9'/5'/17'/0'/2'/index) + DIP-17: Used for asset lock funding keys + */ + +bool derivation_platform_address_funding_path(FFINetwork network, + unsigned int index, + char *path_out, + size_t path_max_len, + FFIError *error) +; + /* Derive private key for a specific path from seed @@ -2954,6 +2975,30 @@ void *managed_account_collection_get_provider_platform_keys(const FFIManagedAcco bool managed_account_collection_has_provider_platform_keys(const FFIManagedAccountCollection *collection) ; +/* + Get the platform address funding account if it exists in managed collection + DIP-17: Path: m/9'/coin_type'/17'/0'/2'/index (for asset lock funding) + + # Safety + + - `collection` must be a valid pointer to an FFIManagedAccountCollection + - The returned pointer must be freed with `managed_account_free` when no longer needed + */ + +FFIManagedAccount *managed_account_collection_get_platform_address_funding(const FFIManagedAccountCollection *collection) +; + +/* + Check if platform address funding account exists in managed collection + + # Safety + + - `collection` must be a valid pointer to an FFIManagedAccountCollection + */ + +bool managed_account_collection_has_platform_address_funding(const FFIManagedAccountCollection *collection) +; + /* Get the total number of accounts in the managed collection diff --git a/key-wallet-ffi/src/address_pool.rs b/key-wallet-ffi/src/address_pool.rs index b255efac7..e6fb9853c 100644 --- a/key-wallet-ffi/src/address_pool.rs +++ b/key-wallet-ffi/src/address_pool.rs @@ -64,6 +64,7 @@ fn get_managed_account_by_type<'a>( // Platform Payment accounts are not currently persisted in ManagedAccountCollection None } + AccountType::PlatformAddressFunding => collection.platform_address_funding.as_ref(), } } @@ -113,6 +114,7 @@ fn get_managed_account_by_type_mut<'a>( // Platform Payment accounts are not currently persisted in ManagedAccountCollection None } + AccountType::PlatformAddressFunding => collection.platform_address_funding.as_mut(), } } diff --git a/key-wallet-ffi/src/derivation.rs b/key-wallet-ffi/src/derivation.rs index 5b8b35b8c..9aaf4459e 100644 --- a/key-wallet-ffi/src/derivation.rs +++ b/key-wallet-ffi/src/derivation.rs @@ -30,6 +30,8 @@ pub enum FFIDerivationPathType { PathBlockchainIdentityCreditInvitationFunding = 13, PathProviderPlatformNodeKeys = 14, PathCoinJoin = 15, + PathPlatformPayment = 16, + PathPlatformAddressFunding = 17, PathRoot = 255, } @@ -424,6 +426,62 @@ pub extern "C" fn derivation_identity_authentication_path( true } +/// Derive platform address funding path (m/9'/5'/17'/0'/2'/index) +/// DIP-17: Used for asset lock funding keys +#[no_mangle] +pub extern "C" fn derivation_platform_address_funding_path( + network: FFINetwork, + index: c_uint, + path_out: *mut c_char, + path_max_len: usize, + error: *mut FFIError, +) -> bool { + if path_out.is_null() { + FFIError::set_error( + error, + FFIErrorCode::InvalidInput, + "Path output buffer is null".to_string(), + ); + return false; + } + + let network_rust: key_wallet::Network = network.into(); + + use key_wallet::bip32::DerivationPath; + let derivation = DerivationPath::platform_address_funding_path(network_rust, index); + + let path_str = format!("{}", derivation); + + let c_string = match CString::new(path_str) { + Ok(s) => s, + Err(_) => { + FFIError::set_error( + error, + FFIErrorCode::AllocationFailed, + "Failed to create C string".to_string(), + ); + return false; + } + }; + + let bytes = c_string.as_bytes_with_nul(); + if bytes.len() > path_max_len { + FFIError::set_error( + error, + FFIErrorCode::InvalidInput, + format!("Path too long: {} > {}", bytes.len(), path_max_len), + ); + return false; + } + + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), path_out.cast::(), bytes.len()); + } + + FFIError::set_success(error); + true +} + /// Derive private key for a specific path from seed /// /// # Safety diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 54c9bbbef..9ecb5f876 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -167,6 +167,9 @@ pub unsafe extern "C" fn managed_wallet_get_account( AccountType::PlatformPayment { .. } => None, + AccountType::PlatformAddressFunding => { + managed_collection.platform_address_funding.as_ref() + } }; match managed_account { @@ -474,6 +477,7 @@ pub unsafe extern "C" fn managed_account_get_account_type( AccountType::PlatformPayment { .. } => FFIAccountType::PlatformPayment, + AccountType::PlatformAddressFunding => FFIAccountType::PlatformAddressFunding, } } @@ -951,6 +955,10 @@ pub unsafe extern "C" fn managed_account_get_address_pool( addresses, .. } => addresses, + ManagedAccountType::PlatformAddressFunding { + addresses, + .. + } => addresses, }; let ffi_pool = FFIAddressPool { diff --git a/key-wallet-ffi/src/managed_account_collection.rs b/key-wallet-ffi/src/managed_account_collection.rs index 60913d516..c6dabf8c8 100644 --- a/key-wallet-ffi/src/managed_account_collection.rs +++ b/key-wallet-ffi/src/managed_account_collection.rs @@ -75,6 +75,9 @@ pub struct FFIManagedAccountCollectionSummary { #[cfg(feature = "eddsa")] /// Whether provider platform keys account exists pub has_provider_platform_keys: bool, + + /// Whether platform address funding account exists (DIP-17) + pub has_platform_address_funding: bool, } /// Get managed account collection for a specific network from wallet manager @@ -725,6 +728,50 @@ pub unsafe extern "C" fn managed_account_collection_has_provider_platform_keys( } } +// Platform Address Funding account functions + +/// Get the platform address funding account if it exists in managed collection +/// DIP-17: Path: m/9'/coin_type'/17'/0'/2'/index (for asset lock funding) +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +/// - The returned pointer must be freed with `managed_account_free` when no longer needed +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_get_platform_address_funding( + collection: *const FFIManagedAccountCollection, +) -> *mut FFIManagedAccount { + if collection.is_null() { + return ptr::null_mut(); + } + + let collection = &*collection; + match &collection.collection.platform_address_funding { + Some(account) => { + let ffi_account = FFIManagedAccount::new(account); + Box::into_raw(Box::new(ffi_account)) + } + None => ptr::null_mut(), + } +} + +/// Check if platform address funding account exists in managed collection +/// +/// # Safety +/// +/// - `collection` must be a valid pointer to an FFIManagedAccountCollection +#[no_mangle] +pub unsafe extern "C" fn managed_account_collection_has_platform_address_funding( + collection: *const FFIManagedAccountCollection, +) -> bool { + if collection.is_null() { + return false; + } + + let collection = &*collection; + collection.collection.platform_address_funding.is_some() +} + // Utility functions /// Get the total number of accounts in the managed collection @@ -906,6 +953,10 @@ pub unsafe extern "C" fn managed_account_collection_summary( summary_parts.push("• Provider Platform Keys Account (EdDSA)".to_string()); } + if collection.collection.platform_address_funding.is_some() { + summary_parts.push("• Platform Address Funding Account (DIP-17)".to_string()); + } + // If there are no accounts at all if summary_parts.len() == 1 { summary_parts.push("No accounts configured".to_string()); @@ -1015,6 +1066,7 @@ pub unsafe extern "C" fn managed_account_collection_summary_data( has_provider_operator_keys: collection.collection.provider_operator_keys.is_some(), #[cfg(feature = "eddsa")] has_provider_platform_keys: collection.collection.provider_platform_keys.is_some(), + has_platform_address_funding: collection.collection.platform_address_funding.is_some(), }; Box::into_raw(Box::new(summary)) diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index 8586bd993..fad2a9153 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -473,6 +473,24 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } + AccountTypeMatch::PlatformAddressFunding { + involved_addresses, + } => { + // PlatformAddressFunding is used for asset lock transactions (funding) + let ffi_match = FFIAccountMatch { + account_type: 14, // PlatformAddressFunding + account_index: 0, + registration_index: 0, + received: account_match.received, + sent: account_match.sent, + external_addresses_count: involved_addresses.len() as c_uint, + internal_addresses_count: 0, + has_external_addresses: !involved_addresses.is_empty(), + has_internal_addresses: false, + }; + ffi_accounts.push(ffi_match); + continue; + } } } diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index 241702752..337b2cf33 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -190,6 +190,9 @@ pub enum FFIAccountType { DashpayExternalAccount = 12, /// Platform Payment address (DIP-17) - Path: m/9'/5'/17'/account'/key_class'/index PlatformPayment = 13, + /// Platform Address Funding (DIP-17) - Path: m/9'/5'/17'/0'/2'/index + /// For asset lock funding keys (fixed account=0', key_class=2') + PlatformAddressFunding = 14, } impl FFIAccountType { @@ -259,6 +262,9 @@ impl FFIAccountType { Platform Payment account creation must use a different API path." ); } + FFIAccountType::PlatformAddressFunding => { + key_wallet::AccountType::PlatformAddressFunding + } } } @@ -347,6 +353,9 @@ impl FFIAccountType { account, key_class, } => (FFIAccountType::PlatformPayment, *account, Some(*key_class)), + key_wallet::AccountType::PlatformAddressFunding => { + (FFIAccountType::PlatformAddressFunding, 0, None) + } } } } diff --git a/key-wallet/src/account/account_collection.rs b/key-wallet/src/account/account_collection.rs index eb9dbaaad..801d10a70 100644 --- a/key-wallet/src/account/account_collection.rs +++ b/key-wallet/src/account/account_collection.rs @@ -74,6 +74,9 @@ pub struct AccountCollection { pub dashpay_external_accounts: BTreeMap, /// Platform Payment accounts (DIP-17) pub platform_payment_accounts: BTreeMap, + /// Platform Address Funding account (DIP-17, singleton) + /// Path: m/9'/coin_type'/17'/0'/2'/index (for asset lock funding) + pub platform_address_funding: Option, } impl AccountCollection { @@ -96,6 +99,7 @@ impl AccountCollection { dashpay_receival_accounts: BTreeMap::new(), dashpay_external_accounts: BTreeMap::new(), platform_payment_accounts: BTreeMap::new(), + platform_address_funding: None, } } @@ -181,6 +185,9 @@ impl AccountCollection { }; self.platform_payment_accounts.insert(key, account); } + AccountType::PlatformAddressFunding => { + self.platform_address_funding = Some(account); + } } Ok(()) } @@ -274,6 +281,7 @@ impl AccountCollection { }; self.platform_payment_accounts.contains_key(&key) } + AccountType::PlatformAddressFunding => self.platform_address_funding.is_some(), } } @@ -337,6 +345,7 @@ impl AccountCollection { }; self.platform_payment_accounts.get(&key) } + AccountType::PlatformAddressFunding => self.platform_address_funding.as_ref(), } } @@ -400,6 +409,7 @@ impl AccountCollection { }; self.platform_payment_accounts.get_mut(&key) } + AccountType::PlatformAddressFunding => self.platform_address_funding.as_mut(), } } @@ -440,6 +450,10 @@ impl AccountCollection { accounts.extend(self.dashpay_external_accounts.values()); accounts.extend(self.platform_payment_accounts.values()); + if let Some(account) = &self.platform_address_funding { + accounts.push(account); + } + accounts } @@ -480,6 +494,10 @@ impl AccountCollection { accounts.extend(self.dashpay_external_accounts.values_mut()); accounts.extend(self.platform_payment_accounts.values_mut()); + if let Some(account) = &mut self.platform_address_funding { + accounts.push(account); + } + accounts } @@ -566,7 +584,8 @@ impl AccountCollection { && self.identity_topup_not_bound.is_none() && self.identity_invitation.is_none() && self.provider_voting_keys.is_none() - && self.provider_owner_keys.is_none(); + && self.provider_owner_keys.is_none() + && self.platform_address_funding.is_none(); #[cfg(feature = "bls")] { @@ -600,6 +619,7 @@ impl AccountCollection { { self.provider_platform_keys = None; } + self.platform_address_funding = None; } } diff --git a/key-wallet/src/account/account_type.rs b/key-wallet/src/account/account_type.rs index 5b317a9ab..718ff55a6 100644 --- a/key-wallet/src/account/account_type.rs +++ b/key-wallet/src/account/account_type.rs @@ -92,6 +92,10 @@ pub enum AccountType { /// Key class (hardened) - default 0', 1' reserved for change-like segregation key_class: u32, }, + /// Platform Address Funding account (DIP-17) + /// Path: m/9'/coin_type'/17'/0'/2'/index + /// This is a specialized account for asset lock funding with fixed account=0' and key_class=2' + PlatformAddressFunding, } impl From for AccountTypeToCheck { @@ -128,6 +132,7 @@ impl From for AccountTypeToCheck { AccountType::PlatformPayment { .. } => AccountTypeToCheck::PlatformPayment, + AccountType::PlatformAddressFunding => AccountTypeToCheck::PlatformAddressFunding, } } } @@ -156,7 +161,7 @@ impl AccountType { account, .. } => Some(*account), - // Identity and provider types don't have account indices + // Identity, provider, and special platform funding types don't have account indices Self::IdentityRegistration | Self::IdentityTopUp { .. @@ -166,7 +171,8 @@ impl AccountType { | Self::ProviderVotingKeys | Self::ProviderOwnerKeys | Self::ProviderOperatorKeys - | Self::ProviderPlatformKeys => None, + | Self::ProviderPlatformKeys + | Self::PlatformAddressFunding => None, } } @@ -227,6 +233,7 @@ impl AccountType { Self::PlatformPayment { .. } => DerivationPathReference::PlatformPayment, + Self::PlatformAddressFunding => DerivationPathReference::PlatformAddressFunding, } } @@ -435,6 +442,20 @@ impl AccountType { ); Ok(path) } + Self::PlatformAddressFunding => { + // DIP-17: m/9'/coin_type'/17'/0'/2' (base path without index) + // account=0' and key_class=2' (funding) are fixed + // The leaf index is non-hardened and appended during address generation + match network { + Network::Dash => { + Ok(DerivationPath::from(crate::dip9::PLATFORM_ADDRESS_FUNDING_PATH_MAINNET)) + } + Network::Testnet | Network::Devnet | Network::Regtest => { + Ok(DerivationPath::from(crate::dip9::PLATFORM_ADDRESS_FUNDING_PATH_TESTNET)) + } + _ => Err(crate::error::Error::InvalidNetwork), + } + } } } } diff --git a/key-wallet/src/bip32.rs b/key-wallet/src/bip32.rs index 5bd805b7e..fc247f15a 100644 --- a/key-wallet/src/bip32.rs +++ b/key-wallet/src/bip32.rs @@ -1116,6 +1116,31 @@ impl DerivationPath { root_derivation_path } + /// DIP-17: Platform Address Funding path (m/9'/coin_type'/17'/0'/2'/index) + /// This is used for asset lock funding keys. + /// + /// # Arguments + /// * `network` - The network (Dash mainnet uses coin_type 5', testnet/devnet/regtest use 1') + /// * `index` - The address index (non-hardened) + /// + /// # Returns + /// Full derivation path: m/9'/coin_type'/17'/0'/2'/index + pub fn platform_address_funding_path(network: Network, index: u32) -> Self { + use crate::dip9::{ + PLATFORM_ADDRESS_FUNDING_PATH_MAINNET, PLATFORM_ADDRESS_FUNDING_PATH_TESTNET, + }; + + let mut root_derivation_path: DerivationPath = match network { + Network::Dash => PLATFORM_ADDRESS_FUNDING_PATH_MAINNET, + _ => PLATFORM_ADDRESS_FUNDING_PATH_TESTNET, + } + .into(); + root_derivation_path.0.extend(&[ChildNumber::Normal { + index, + }]); + root_derivation_path + } + pub fn derive_priv_ecdsa_for_master_seed( &self, seed: &[u8], @@ -2636,6 +2661,17 @@ mod tests { assert_eq!(path.to_string(), "m/9'/1'/5'/0'/1'/2'/3'"); } + #[test] + fn test_platform_address_funding_path() { + // Mainnet funding key at index 0: m/9'/5'/17'/0'/2'/0 + let path = DerivationPath::platform_address_funding_path(Network::Dash, 0); + assert_eq!(path.to_string(), "m/9'/5'/17'/0'/2'/0"); + + // Testnet funding key at index 5: m/9'/1'/17'/0'/2'/5 + let path = DerivationPath::platform_address_funding_path(Network::Testnet, 5); + assert_eq!(path.to_string(), "m/9'/1'/17'/0'/2'/5"); + } + #[test] fn test_derive_priv_ecdsa_for_master_seed() { let path = DerivationPath::bip_44_account(Network::Dash, 0); diff --git a/key-wallet/src/dip9.rs b/key-wallet/src/dip9.rs index 76f6ca0e8..00ea0ea7d 100644 --- a/key-wallet/src/dip9.rs +++ b/key-wallet/src/dip9.rs @@ -28,6 +28,8 @@ pub enum DerivationPathReference { ProviderPlatformNodeKeys = 14, CoinJoin = 15, PlatformPayment = 16, + /// DIP-17: Platform Address Funding (m/9'/coin_type'/17'/0'/2'/index) + PlatformAddressFunding = 17, Root = 255, } @@ -134,6 +136,14 @@ pub const FEATURE_PURPOSE_IDENTITIES_SUBFEATURE_INVITATIONS: u32 = 3; pub const FEATURE_PURPOSE_DASHPAY: u32 = 15; /// DIP-17: Platform Payment Addresses feature index pub const FEATURE_PURPOSE_PLATFORM_PAYMENT: u32 = 17; + +/// DIP-17: Platform Payment key classes +/// Key class for external/receiving addresses +pub const PLATFORM_KEY_CLASS_PAYMENT: u32 = 0; +/// Key class for internal/change (reserved) +pub const PLATFORM_KEY_CLASS_INTERNAL: u32 = 1; +/// Key class for asset lock funding addresses +pub const PLATFORM_KEY_CLASS_FUNDING: u32 = 2; pub const DASH_BIP44_PATH_MAINNET: IndexConstPath<2> = IndexConstPath { indexes: [ ChildNumber::Hardened { @@ -417,3 +427,53 @@ pub const PLATFORM_PAYMENT_ROOT_PATH_TESTNET: IndexConstPath<3> = IndexConstPath reference: DerivationPathReference::PlatformPayment, path_type: DerivationPathType::CLEAR_FUNDS, }; + +// DIP-17: Platform Address Funding Paths +// Path: m/9'/coin_type'/17'/0'/2'/index +// These are specifically for asset lock funding keys (key_class = 2) + +/// Platform Address Funding path for mainnet: m/9'/5'/17'/0'/2' +pub const PLATFORM_ADDRESS_FUNDING_PATH_MAINNET: IndexConstPath<5> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_PLATFORM_PAYMENT, + }, + ChildNumber::Hardened { + index: 0, // account index fixed at 0' + }, + ChildNumber::Hardened { + index: PLATFORM_KEY_CLASS_FUNDING, // key class 2' for funding + }, + ], + reference: DerivationPathReference::PlatformAddressFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; + +/// Platform Address Funding path for testnet: m/9'/1'/17'/0'/2' +pub const PLATFORM_ADDRESS_FUNDING_PATH_TESTNET: IndexConstPath<5> = IndexConstPath { + indexes: [ + ChildNumber::Hardened { + index: FEATURE_PURPOSE, + }, + ChildNumber::Hardened { + index: DASH_TESTNET_COIN_TYPE, + }, + ChildNumber::Hardened { + index: FEATURE_PURPOSE_PLATFORM_PAYMENT, + }, + ChildNumber::Hardened { + index: 0, // account index fixed at 0' + }, + ChildNumber::Hardened { + index: PLATFORM_KEY_CLASS_FUNDING, // key class 2' for funding + }, + ], + reference: DerivationPathReference::PlatformAddressFunding, + path_type: DerivationPathType::CREDIT_FUNDING, +}; diff --git a/key-wallet/src/managed_account/managed_account_collection.rs b/key-wallet/src/managed_account/managed_account_collection.rs index 2a1baabf4..c9f1e2dea 100644 --- a/key-wallet/src/managed_account/managed_account_collection.rs +++ b/key-wallet/src/managed_account/managed_account_collection.rs @@ -52,6 +52,9 @@ pub struct ManagedAccountCollection { pub dashpay_external_accounts: BTreeMap, /// Platform Payment accounts (DIP-17) pub platform_payment_accounts: BTreeMap, + /// Platform Address Funding account (DIP-17, singleton) + /// Path: m/9'/coin_type'/17'/0'/2'/index (for asset lock funding) + pub platform_address_funding: Option, } impl ManagedAccountCollection { @@ -72,6 +75,7 @@ impl ManagedAccountCollection { dashpay_receival_accounts: BTreeMap::new(), dashpay_external_accounts: BTreeMap::new(), platform_payment_accounts: BTreeMap::new(), + platform_address_funding: None, } } @@ -158,6 +162,9 @@ impl ManagedAccountCollection { }; self.platform_payment_accounts.contains_key(&key) } + ManagedAccountType::PlatformAddressFunding { + .. + } => self.platform_address_funding.is_some(), } } @@ -262,6 +269,11 @@ impl ManagedAccountCollection { }; self.platform_payment_accounts.insert(key, account); } + ManagedAccountType::PlatformAddressFunding { + .. + } => { + self.platform_address_funding = Some(account); + } } } @@ -365,6 +377,13 @@ impl ManagedAccountCollection { } } + // Convert Platform Address Funding account + if let Some(account) = &account_collection.platform_address_funding { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.platform_address_funding = Some(managed_account); + } + } + managed_collection } @@ -623,6 +642,19 @@ impl ManagedAccountCollection { addresses, } } + AccountType::PlatformAddressFunding => { + // DIP-17: Platform Address Funding (asset lock funding) + let addresses = AddressPool::new( + base_path, + AddressPoolType::Absent, + DIP17_GAP_LIMIT, + network, + key_source, + )?; + ManagedAccountType::PlatformAddressFunding { + addresses, + } + } }; Ok(ManagedAccount::new(managed_type, network, is_watch_only)) @@ -768,6 +800,9 @@ impl ManagedAccountCollection { // Platform Payment addresses are not used in Core chain transactions (DIP-17) None } + AccountTypeMatch::PlatformAddressFunding { + .. + } => self.platform_address_funding.as_ref(), } } @@ -869,6 +904,14 @@ impl ManagedAccountCollection { accounts.extend(self.dashpay_receival_accounts.values()); accounts.extend(self.dashpay_external_accounts.values()); + // Add Platform Payment accounts + accounts.extend(self.platform_payment_accounts.values()); + + // Add Platform Address Funding account + if let Some(account) = &self.platform_address_funding { + accounts.push(account); + } + accounts } @@ -920,6 +963,14 @@ impl ManagedAccountCollection { accounts.extend(self.dashpay_receival_accounts.values_mut()); accounts.extend(self.dashpay_external_accounts.values_mut()); + // Add Platform Payment accounts + accounts.extend(self.platform_payment_accounts.values_mut()); + + // Add Platform Address Funding account + if let Some(account) = &mut self.platform_address_funding { + accounts.push(account); + } + accounts } @@ -962,6 +1013,8 @@ impl ManagedAccountCollection { && self.provider_platform_keys.is_none() && self.dashpay_receival_accounts.is_empty() && self.dashpay_external_accounts.is_empty() + && self.platform_payment_accounts.is_empty() + && self.platform_address_funding.is_none() } /// Clear all accounts @@ -979,5 +1032,7 @@ impl ManagedAccountCollection { self.provider_platform_keys = None; self.dashpay_receival_accounts.clear(); self.dashpay_external_accounts.clear(); + self.platform_payment_accounts.clear(); + self.platform_address_funding = None; } } diff --git a/key-wallet/src/managed_account/managed_account_type.rs b/key-wallet/src/managed_account/managed_account_type.rs index fa83e293f..c5c226011 100644 --- a/key-wallet/src/managed_account/managed_account_type.rs +++ b/key-wallet/src/managed_account/managed_account_type.rs @@ -115,6 +115,13 @@ pub enum ManagedAccountType { /// Platform payment address pool (single pool, non-hardened leaf index) addresses: AddressPool, }, + /// Platform Address Funding account (DIP-17) + /// Path: m/9'/coin_type'/17'/0'/2'/index + /// This is a specialized account for asset lock funding with fixed account=0' and key_class=2' + PlatformAddressFunding { + /// Platform address funding address pool (single pool, non-hardened leaf index) + addresses: AddressPool, + }, } impl ManagedAccountType { @@ -167,6 +174,9 @@ impl ManagedAccountType { account, .. } => Some(*account), + Self::PlatformAddressFunding { + .. + } => None, } } @@ -245,6 +255,10 @@ impl ManagedAccountType { | Self::PlatformPayment { addresses, .. + } + | Self::PlatformAddressFunding { + addresses, + .. } => vec![addresses], } } @@ -308,6 +322,10 @@ impl ManagedAccountType { | Self::PlatformPayment { addresses, .. + } + | Self::PlatformAddressFunding { + addresses, + .. } => vec![addresses], } } @@ -432,6 +450,9 @@ impl ManagedAccountType { account: *account, key_class: *key_class, }, + Self::PlatformAddressFunding { + .. + } => AccountType::PlatformAddressFunding, } } @@ -697,6 +718,23 @@ impl ManagedAccountType { addresses: pool, }) } + AccountType::PlatformAddressFunding => { + // DIP-17: m/9'/coin_type'/17'/0'/2'/index (asset lock funding) + // The leaf index is non-hardened + let path = account_type + .derivation_path(network) + .unwrap_or_else(|_| DerivationPath::master()); + let pool = AddressPool::new( + path, + crate::managed_account::address_pool::AddressPoolType::Absent, + DIP17_GAP_LIMIT, + network, + key_source, + )?; + Ok(Self::PlatformAddressFunding { + addresses: pool, + }) + } } } } diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index 14be9a322..f54daf2af 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -248,6 +248,10 @@ impl ManagedAccount { | ManagedAccountType::PlatformPayment { addresses, .. + } + | ManagedAccountType::PlatformAddressFunding { + addresses, + .. } => { addresses.unused_addresses().first().and_then(|addr| addresses.address_index(addr)) } @@ -510,6 +514,10 @@ impl ManagedAccount { | ManagedAccountType::PlatformPayment { addresses, .. + } + | ManagedAccountType::PlatformAddressFunding { + addresses, + .. } => { // Create appropriate key source based on whether xpub is provided let key_source = match account_xpub { @@ -599,6 +607,10 @@ impl ManagedAccount { | ManagedAccountType::PlatformPayment { addresses, .. + } + | ManagedAccountType::PlatformAddressFunding { + addresses, + .. } => { // Create appropriate key source based on whether xpub is provided let key_source = match account_xpub { @@ -828,6 +840,10 @@ impl ManagedAccount { | ManagedAccountType::PlatformPayment { addresses, .. + } + | ManagedAccountType::PlatformAddressFunding { + addresses, + .. } => Some(addresses.gap_limit), } } diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index 78799db31..06bf64e80 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -114,6 +114,11 @@ pub enum AccountTypeMatch { key_class: u32, involved_addresses: Vec, }, + /// Platform Address Funding account (DIP-17) + /// For asset lock funding keys + PlatformAddressFunding { + involved_addresses: Vec, + }, } impl AccountTypeMatch { @@ -174,6 +179,10 @@ impl AccountTypeMatch { | AccountTypeMatch::PlatformPayment { involved_addresses, .. + } + | AccountTypeMatch::PlatformAddressFunding { + involved_addresses, + .. } => involved_addresses.clone(), } } @@ -258,6 +267,9 @@ impl AccountTypeMatch { AccountTypeMatch::PlatformPayment { .. } => AccountTypeToCheck::PlatformPayment, + AccountTypeMatch::PlatformAddressFunding { + .. + } => AccountTypeToCheck::PlatformAddressFunding, } } } @@ -396,6 +408,12 @@ impl ManagedAccountCollection { // any Core chain transaction by design. Vec::new() } + AccountTypeToCheck::PlatformAddressFunding => self + .platform_address_funding + .as_ref() + .and_then(|account| account.check_asset_lock_transaction_for_match(tx, None)) + .into_iter() + .collect(), } } @@ -660,6 +678,11 @@ impl ManagedAccount { key_class: *key_class, involved_addresses: involved_other_addresses, }, + ManagedAccountType::PlatformAddressFunding { + .. + } => AccountTypeMatch::PlatformAddressFunding { + involved_addresses: involved_other_addresses, + }, }; Some(AccountMatch { diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index 36fde7583..edbbff9db 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -115,6 +115,7 @@ impl TransactionRouter { AccountTypeToCheck::IdentityTopUp, AccountTypeToCheck::IdentityTopUpNotBound, AccountTypeToCheck::IdentityInvitation, + AccountTypeToCheck::PlatformAddressFunding, ], TransactionType::AssetUnlock => { vec![AccountTypeToCheck::StandardBIP44, AccountTypeToCheck::StandardBIP32] @@ -181,6 +182,9 @@ pub enum AccountTypeToCheck { /// Platform Payment accounts (DIP-17). /// Note: These are NOT checked for Core chain transactions as they operate on Dash Platform. PlatformPayment, + /// Platform Address Funding accounts (DIP-17). + /// Note: These ARE checked for Core chain transactions as they are used for asset lock funding. + PlatformAddressFunding, } impl From for AccountTypeToCheck { @@ -233,6 +237,9 @@ impl From for AccountTypeToCheck { ManagedAccountType::PlatformPayment { .. } => AccountTypeToCheck::PlatformPayment, + ManagedAccountType::PlatformAddressFunding { + .. + } => AccountTypeToCheck::PlatformAddressFunding, } } } @@ -287,6 +294,9 @@ impl From<&ManagedAccountType> for AccountTypeToCheck { ManagedAccountType::PlatformPayment { .. } => AccountTypeToCheck::PlatformPayment, + ManagedAccountType::PlatformAddressFunding { + .. + } => AccountTypeToCheck::PlatformAddressFunding, } } } diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index df1db5ece..e56d04751 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -147,6 +147,9 @@ impl WalletTransactionChecker for ManagedWalletInfo { // This branch should never be reached by design (per DIP-17). None } + AccountTypeMatch::PlatformAddressFunding { + .. + } => self.accounts.platform_address_funding.as_mut(), }; if let Some(account) = account { diff --git a/key-wallet/src/wallet/helper.rs b/key-wallet/src/wallet/helper.rs index 53821d176..b8b8c4117 100644 --- a/key-wallet/src/wallet/helper.rs +++ b/key-wallet/src/wallet/helper.rs @@ -843,6 +843,9 @@ impl Wallet { // and xpubs are not retrieved via this helper None } + crate::transaction_checking::transaction_router::AccountTypeToCheck::PlatformAddressFunding => { + coll.platform_address_funding.as_ref().map(|a| a.account_xpub) + } } } }