diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index ccedce946..57af8798f 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -100,6 +100,22 @@ typedef enum { Asset lock shielded address top-up funding (subfeature 5) */ ASSET_LOCK_SHIELDED_ADDRESS_TOP_UP = 15, + /* + Blockchain identity ECDSA keys (DIP-9) - Path: m/9'/coinType'/5'/0' + */ + BLOCKCHAIN_IDENTITIES_ECDSA = 16, + /* + Blockchain identity ECDSA Hash160 keys (DIP-9) - Path: m/9'/coinType'/5'/1' + */ + BLOCKCHAIN_IDENTITIES_ECDSA_HASH160 = 17, + /* + Blockchain identity BLS keys (DIP-9) - Path: m/9'/coinType'/5'/2' + */ + BLOCKCHAIN_IDENTITIES_BLS = 18, + /* + Blockchain identity BLS Hash160 keys (DIP-9) - Path: m/9'/coinType'/5'/3' + */ + BLOCKCHAIN_IDENTITIES_BLS_HASH160 = 19, } FFIAccountType; /* diff --git a/key-wallet-ffi/src/address_pool.rs b/key-wallet-ffi/src/address_pool.rs index ef222a85e..211fcfeda 100644 --- a/key-wallet-ffi/src/address_pool.rs +++ b/key-wallet-ffi/src/address_pool.rs @@ -49,6 +49,14 @@ fn get_managed_account_by_type<'a>( AccountType::AssetLockShieldedAddressTopUp => { collection.asset_lock_shielded_address_topup.as_ref() } + AccountType::BlockchainIdentitiesECDSA => collection.blockchain_identities_ecdsa.as_ref(), + AccountType::BlockchainIdentitiesECDSAHash160 => { + collection.blockchain_identities_ecdsa_hash160.as_ref() + } + AccountType::BlockchainIdentitiesBLS => collection.blockchain_identities_bls.as_ref(), + AccountType::BlockchainIdentitiesBLSHash160 => { + collection.blockchain_identities_bls_hash160.as_ref() + } AccountType::ProviderVotingKeys => collection.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => collection.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => collection.provider_operator_keys.as_ref(), @@ -102,6 +110,14 @@ fn get_managed_account_by_type_mut<'a>( AccountType::AssetLockShieldedAddressTopUp => { collection.asset_lock_shielded_address_topup.as_mut() } + AccountType::BlockchainIdentitiesECDSA => collection.blockchain_identities_ecdsa.as_mut(), + AccountType::BlockchainIdentitiesECDSAHash160 => { + collection.blockchain_identities_ecdsa_hash160.as_mut() + } + AccountType::BlockchainIdentitiesBLS => collection.blockchain_identities_bls.as_mut(), + AccountType::BlockchainIdentitiesBLSHash160 => { + collection.blockchain_identities_bls_hash160.as_mut() + } AccountType::ProviderVotingKeys => collection.provider_voting_keys.as_mut(), AccountType::ProviderOwnerKeys => collection.provider_owner_keys.as_mut(), AccountType::ProviderOperatorKeys => collection.provider_operator_keys.as_mut(), @@ -788,6 +804,34 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used( } } } + if !found { + if let Some(account) = &mut collection.blockchain_identities_ecdsa { + if account.mark_address_used(&address) { + found = true; + } + } + } + if !found { + if let Some(account) = &mut collection.blockchain_identities_ecdsa_hash160 { + if account.mark_address_used(&address) { + found = true; + } + } + } + if !found { + if let Some(account) = &mut collection.blockchain_identities_bls { + if account.mark_address_used(&address) { + found = true; + } + } + } + if !found { + if let Some(account) = &mut collection.blockchain_identities_bls_hash160 { + if account.mark_address_used(&address) { + found = true; + } + } + } if !found { for account in collection.dashpay_receival_accounts.values_mut() { if account.mark_address_used(&address) { diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 9d5dd42e3..e21e34415 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -250,6 +250,18 @@ pub unsafe extern "C" fn managed_wallet_get_account( AccountType::AssetLockShieldedAddressTopUp => { managed_collection.asset_lock_shielded_address_topup.as_ref() } + AccountType::BlockchainIdentitiesECDSA => { + managed_collection.blockchain_identities_ecdsa.as_ref() + } + AccountType::BlockchainIdentitiesECDSAHash160 => { + managed_collection.blockchain_identities_ecdsa_hash160.as_ref() + } + AccountType::BlockchainIdentitiesBLS => { + managed_collection.blockchain_identities_bls.as_ref() + } + AccountType::BlockchainIdentitiesBLSHash160 => { + managed_collection.blockchain_identities_bls_hash160.as_ref() + } AccountType::ProviderVotingKeys => managed_collection.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => managed_collection.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => managed_collection.provider_operator_keys.as_ref(), @@ -563,6 +575,14 @@ pub unsafe extern "C" fn managed_core_account_get_account_type( AccountType::IdentityInvitation => FFIAccountType::IdentityInvitation, AccountType::AssetLockAddressTopUp => FFIAccountType::AssetLockAddressTopUp, AccountType::AssetLockShieldedAddressTopUp => FFIAccountType::AssetLockShieldedAddressTopUp, + AccountType::BlockchainIdentitiesECDSA => FFIAccountType::BlockchainIdentitiesECDSA, + AccountType::BlockchainIdentitiesECDSAHash160 => { + FFIAccountType::BlockchainIdentitiesECDSAHash160 + } + AccountType::BlockchainIdentitiesBLS => FFIAccountType::BlockchainIdentitiesBLS, + AccountType::BlockchainIdentitiesBLSHash160 => { + FFIAccountType::BlockchainIdentitiesBLSHash160 + } AccountType::ProviderVotingKeys => FFIAccountType::ProviderVotingKeys, AccountType::ProviderOwnerKeys => FFIAccountType::ProviderOwnerKeys, AccountType::ProviderOperatorKeys => FFIAccountType::ProviderOperatorKeys, @@ -1039,6 +1059,18 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool( ManagedAccountType::AssetLockShieldedAddressTopUp { addresses, } => addresses, + ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + } => addresses, + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + } => addresses, + ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + } => addresses, + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + } => addresses, ManagedAccountType::ProviderVotingKeys { addresses, } => addresses, diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index c25890a8a..930281e28 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -384,6 +384,74 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( ffi_accounts.push(ffi_match); continue; } + CoreAccountTypeMatch::BlockchainIdentitiesECDSA { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 16, // BlockchainIdentitiesECDSA + 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; + } + CoreAccountTypeMatch::BlockchainIdentitiesECDSAHash160 { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 17, // BlockchainIdentitiesECDSAHash160 + 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; + } + CoreAccountTypeMatch::BlockchainIdentitiesBLS { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 18, // BlockchainIdentitiesBLS + 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; + } + CoreAccountTypeMatch::BlockchainIdentitiesBLSHash160 { + involved_addresses, + } => { + let ffi_match = FFIAccountMatch { + account_type: 19, // BlockchainIdentitiesBLSHash160 + 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; + } CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } => { diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index cdaff1730..c355a4fc8 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -195,6 +195,14 @@ pub enum FFIAccountType { AssetLockAddressTopUp = 14, /// Asset lock shielded address top-up funding (subfeature 5) AssetLockShieldedAddressTopUp = 15, + /// Blockchain identity ECDSA keys (DIP-9) - Path: m/9'/coinType'/5'/0' + BlockchainIdentitiesECDSA = 16, + /// Blockchain identity ECDSA Hash160 keys (DIP-9) - Path: m/9'/coinType'/5'/1' + BlockchainIdentitiesECDSAHash160 = 17, + /// Blockchain identity BLS keys (DIP-9) - Path: m/9'/coinType'/5'/2' + BlockchainIdentitiesBLS = 18, + /// Blockchain identity BLS Hash160 keys (DIP-9) - Path: m/9'/coinType'/5'/3' + BlockchainIdentitiesBLSHash160 = 19, } impl FFIAccountType { @@ -229,6 +237,18 @@ impl FFIAccountType { FFIAccountType::AssetLockShieldedAddressTopUp => { key_wallet::AccountType::AssetLockShieldedAddressTopUp } + FFIAccountType::BlockchainIdentitiesECDSA => { + key_wallet::AccountType::BlockchainIdentitiesECDSA + } + FFIAccountType::BlockchainIdentitiesECDSAHash160 => { + key_wallet::AccountType::BlockchainIdentitiesECDSAHash160 + } + FFIAccountType::BlockchainIdentitiesBLS => { + key_wallet::AccountType::BlockchainIdentitiesBLS + } + FFIAccountType::BlockchainIdentitiesBLSHash160 => { + key_wallet::AccountType::BlockchainIdentitiesBLSHash160 + } FFIAccountType::ProviderVotingKeys => key_wallet::AccountType::ProviderVotingKeys, FFIAccountType::ProviderOwnerKeys => key_wallet::AccountType::ProviderOwnerKeys, FFIAccountType::ProviderOperatorKeys => key_wallet::AccountType::ProviderOperatorKeys, @@ -314,6 +334,18 @@ impl FFIAccountType { key_wallet::AccountType::AssetLockShieldedAddressTopUp => { (FFIAccountType::AssetLockShieldedAddressTopUp, 0, None) } + key_wallet::AccountType::BlockchainIdentitiesECDSA => { + (FFIAccountType::BlockchainIdentitiesECDSA, 0, None) + } + key_wallet::AccountType::BlockchainIdentitiesECDSAHash160 => { + (FFIAccountType::BlockchainIdentitiesECDSAHash160, 0, None) + } + key_wallet::AccountType::BlockchainIdentitiesBLS => { + (FFIAccountType::BlockchainIdentitiesBLS, 0, None) + } + key_wallet::AccountType::BlockchainIdentitiesBLSHash160 => { + (FFIAccountType::BlockchainIdentitiesBLSHash160, 0, None) + } key_wallet::AccountType::ProviderVotingKeys => { (FFIAccountType::ProviderVotingKeys, 0, None) } diff --git a/key-wallet/src/account/account_collection.rs b/key-wallet/src/account/account_collection.rs index 5a2a2c566..724d3c054 100644 --- a/key-wallet/src/account/account_collection.rs +++ b/key-wallet/src/account/account_collection.rs @@ -62,6 +62,14 @@ pub struct AccountCollection { pub asset_lock_address_topup: Option, /// Asset lock shielded address top-up account (optional) pub asset_lock_shielded_address_topup: Option, + /// Blockchain identities ECDSA account + pub blockchain_identities_ecdsa: Option, + /// Blockchain identities ECDSA_HASH160 account + pub blockchain_identities_ecdsa_hash160: Option, + /// Blockchain identities BLS account + pub blockchain_identities_bls: Option, + /// Blockchain identities BLS_HASH160 account + pub blockchain_identities_bls_hash160: Option, /// Provider voting keys (optional) pub provider_voting_keys: Option, /// Provider owner keys (optional) @@ -93,6 +101,10 @@ impl AccountCollection { identity_invitation: None, asset_lock_address_topup: None, asset_lock_shielded_address_topup: None, + blockchain_identities_ecdsa: None, + blockchain_identities_ecdsa_hash160: None, + blockchain_identities_bls: None, + blockchain_identities_bls_hash160: None, provider_voting_keys: None, provider_owner_keys: None, #[cfg(feature = "bls")] @@ -147,6 +159,18 @@ impl AccountCollection { AccountType::AssetLockShieldedAddressTopUp => { self.asset_lock_shielded_address_topup = Some(account); } + AccountType::BlockchainIdentitiesECDSA => { + self.blockchain_identities_ecdsa = Some(account); + } + AccountType::BlockchainIdentitiesECDSAHash160 => { + self.blockchain_identities_ecdsa_hash160 = Some(account); + } + AccountType::BlockchainIdentitiesBLS => { + self.blockchain_identities_bls = Some(account); + } + AccountType::BlockchainIdentitiesBLSHash160 => { + self.blockchain_identities_bls_hash160 = Some(account); + } AccountType::ProviderVotingKeys => { self.provider_voting_keys = Some(account); } @@ -246,6 +270,14 @@ impl AccountCollection { AccountType::AssetLockShieldedAddressTopUp => { self.asset_lock_shielded_address_topup.is_some() } + AccountType::BlockchainIdentitiesECDSA => self.blockchain_identities_ecdsa.is_some(), + AccountType::BlockchainIdentitiesECDSAHash160 => { + self.blockchain_identities_ecdsa_hash160.is_some() + } + AccountType::BlockchainIdentitiesBLS => self.blockchain_identities_bls.is_some(), + AccountType::BlockchainIdentitiesBLSHash160 => { + self.blockchain_identities_bls_hash160.is_some() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.is_some(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.is_some(), #[cfg(feature = "bls")] @@ -319,6 +351,14 @@ impl AccountCollection { AccountType::AssetLockShieldedAddressTopUp => { self.asset_lock_shielded_address_topup.as_ref() } + AccountType::BlockchainIdentitiesECDSA => self.blockchain_identities_ecdsa.as_ref(), + AccountType::BlockchainIdentitiesECDSAHash160 => { + self.blockchain_identities_ecdsa_hash160.as_ref() + } + AccountType::BlockchainIdentitiesBLS => self.blockchain_identities_bls.as_ref(), + AccountType::BlockchainIdentitiesBLSHash160 => { + self.blockchain_identities_bls_hash160.as_ref() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.as_ref(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.as_ref(), AccountType::ProviderOperatorKeys => None, // BLSAccount, use bls_account_of_type @@ -386,6 +426,14 @@ impl AccountCollection { AccountType::AssetLockShieldedAddressTopUp => { self.asset_lock_shielded_address_topup.as_mut() } + AccountType::BlockchainIdentitiesECDSA => self.blockchain_identities_ecdsa.as_mut(), + AccountType::BlockchainIdentitiesECDSAHash160 => { + self.blockchain_identities_ecdsa_hash160.as_mut() + } + AccountType::BlockchainIdentitiesBLS => self.blockchain_identities_bls.as_mut(), + AccountType::BlockchainIdentitiesBLSHash160 => { + self.blockchain_identities_bls_hash160.as_mut() + } AccountType::ProviderVotingKeys => self.provider_voting_keys.as_mut(), AccountType::ProviderOwnerKeys => self.provider_owner_keys.as_mut(), AccountType::ProviderOperatorKeys => None, // BLSAccount, use bls_account_of_type_mut @@ -457,6 +505,19 @@ impl AccountCollection { accounts.push(account); } + if let Some(account) = &self.blockchain_identities_ecdsa { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_ecdsa_hash160 { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_bls { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_bls_hash160 { + accounts.push(account); + } + if let Some(account) = &self.provider_voting_keys { accounts.push(account); } @@ -505,6 +566,19 @@ impl AccountCollection { accounts.push(account); } + if let Some(account) = &mut self.blockchain_identities_ecdsa { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_ecdsa_hash160 { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_bls { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_bls_hash160 { + accounts.push(account); + } + if let Some(account) = &mut self.provider_voting_keys { accounts.push(account); } @@ -607,6 +681,10 @@ impl AccountCollection { && self.identity_invitation.is_none() && self.asset_lock_address_topup.is_none() && self.asset_lock_shielded_address_topup.is_none() + && self.blockchain_identities_ecdsa.is_none() + && self.blockchain_identities_ecdsa_hash160.is_none() + && self.blockchain_identities_bls.is_none() + && self.blockchain_identities_bls_hash160.is_none() && self.provider_voting_keys.is_none() && self.provider_owner_keys.is_none(); @@ -634,6 +712,10 @@ impl AccountCollection { self.identity_invitation = None; self.asset_lock_address_topup = None; self.asset_lock_shielded_address_topup = None; + self.blockchain_identities_ecdsa = None; + self.blockchain_identities_ecdsa_hash160 = None; + self.blockchain_identities_bls = None; + self.blockchain_identities_bls_hash160 = None; self.provider_voting_keys = None; self.provider_owner_keys = None; #[cfg(feature = "bls")] diff --git a/key-wallet/src/account/account_type.rs b/key-wallet/src/account/account_type.rs index ba1830e2d..68674d2fa 100644 --- a/key-wallet/src/account/account_type.rs +++ b/key-wallet/src/account/account_type.rs @@ -59,6 +59,14 @@ pub enum AccountType { /// Asset lock shielded address top-up funding (subfeature 5) /// Path: m/9'/coinType'/5'/5'/index' AssetLockShieldedAddressTopUp, + /// m/9'/coinType'/5'/0' -- ECDSA identity authentication keys + BlockchainIdentitiesECDSA, + /// m/9'/coinType'/5'/1' -- ECDSA_HASH160 identity authentication keys + BlockchainIdentitiesECDSAHash160, + /// m/9'/coinType'/5'/2' -- BLS identity authentication keys + BlockchainIdentitiesBLS, + /// m/9'/coinType'/5'/3' -- BLS_HASH160 identity authentication keys + BlockchainIdentitiesBLSHash160, /// Provider voting keys (DIP-3) /// Path: `m/9'/5'/3'/1'/[key_index]` ProviderVotingKeys, @@ -129,6 +137,16 @@ impl TryFrom for AccountTypeToCheck { AccountType::AssetLockShieldedAddressTopUp => { Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp) } + AccountType::BlockchainIdentitiesECDSA => { + Ok(AccountTypeToCheck::BlockchainIdentitiesECDSA) + } + AccountType::BlockchainIdentitiesECDSAHash160 => { + Ok(AccountTypeToCheck::BlockchainIdentitiesECDSAHash160) + } + AccountType::BlockchainIdentitiesBLS => Ok(AccountTypeToCheck::BlockchainIdentitiesBLS), + AccountType::BlockchainIdentitiesBLSHash160 => { + Ok(AccountTypeToCheck::BlockchainIdentitiesBLSHash160) + } AccountType::ProviderVotingKeys => Ok(AccountTypeToCheck::ProviderVotingKeys), AccountType::ProviderOwnerKeys => Ok(AccountTypeToCheck::ProviderOwnerKeys), AccountType::ProviderOperatorKeys => Ok(AccountTypeToCheck::ProviderOperatorKeys), @@ -182,6 +200,10 @@ impl AccountType { | Self::IdentityInvitation | Self::AssetLockAddressTopUp | Self::AssetLockShieldedAddressTopUp + | Self::BlockchainIdentitiesECDSA + | Self::BlockchainIdentitiesECDSAHash160 + | Self::BlockchainIdentitiesBLS + | Self::BlockchainIdentitiesBLSHash160 | Self::ProviderVotingKeys | Self::ProviderOwnerKeys | Self::ProviderOperatorKeys @@ -231,6 +253,10 @@ impl AccountType { Self::AssetLockShieldedAddressTopUp { .. } => DerivationPathReference::BlockchainAssetLockShieldedAddressTopupFunding, + Self::BlockchainIdentitiesECDSA + | Self::BlockchainIdentitiesECDSAHash160 + | Self::BlockchainIdentitiesBLS + | Self::BlockchainIdentitiesBLSHash160 => DerivationPathReference::BlockchainIdentities, Self::ProviderVotingKeys { .. } => DerivationPathReference::ProviderVotingKeys, @@ -378,6 +404,26 @@ impl AccountType { _ => Err(crate::error::Error::InvalidNetwork), } } + Self::BlockchainIdentitiesECDSA + | Self::BlockchainIdentitiesECDSAHash160 + | Self::BlockchainIdentitiesBLS + | Self::BlockchainIdentitiesBLSHash160 => { + let subfeature = match self { + Self::BlockchainIdentitiesECDSA => 0, + Self::BlockchainIdentitiesECDSAHash160 => 1, + Self::BlockchainIdentitiesBLS => 2, + Self::BlockchainIdentitiesBLSHash160 => 3, + _ => unreachable!(), + }; + Ok(DerivationPath::from(vec![ + ChildNumber::from_hardened_idx(9).map_err(crate::error::Error::Bip32)?, + ChildNumber::from_hardened_idx(coin_type) + .map_err(crate::error::Error::Bip32)?, + ChildNumber::from_hardened_idx(5).map_err(crate::error::Error::Bip32)?, + ChildNumber::from_hardened_idx(subfeature) + .map_err(crate::error::Error::Bip32)?, + ])) + } Self::ProviderVotingKeys => { // DIP-3: m/9'/5'/3'/1' (base path, actual key index added when deriving) Ok(DerivationPath::from(vec![ diff --git a/key-wallet/src/account/mod.rs b/key-wallet/src/account/mod.rs index 704a2cd3a..8b854c3b5 100644 --- a/key-wallet/src/account/mod.rs +++ b/key-wallet/src/account/mod.rs @@ -549,4 +549,55 @@ mod tests { let change3 = account.derive_change_address(3).unwrap(); assert_eq!(internal3, change3); } + + #[test] + fn test_blockchain_identities_derivation_path_testnet() { + use crate::dip9::DerivationPathReference; + + let variants: [(AccountType, u32); 4] = [ + (AccountType::BlockchainIdentitiesECDSA, 0), + (AccountType::BlockchainIdentitiesECDSAHash160, 1), + (AccountType::BlockchainIdentitiesBLS, 2), + (AccountType::BlockchainIdentitiesBLSHash160, 3), + ]; + + for (account_type, expected_subfeature) in &variants { + assert_eq!( + account_type.derivation_path_reference(), + DerivationPathReference::BlockchainIdentities + ); + assert_eq!(account_type.index(), None); + assert_eq!(account_type.registration_index(), None); + + let path = account_type.derivation_path(Network::Testnet).unwrap(); + let children: Vec<_> = path.into_iter().collect(); + assert_eq!(children.len(), 4); + // m/9'/1'/5'/subfeature' + assert_eq!(*children[0], ChildNumber::from_hardened_idx(9).unwrap()); + assert_eq!(*children[1], ChildNumber::from_hardened_idx(1).unwrap()); + assert_eq!(*children[2], ChildNumber::from_hardened_idx(5).unwrap()); + assert_eq!(*children[3], ChildNumber::from_hardened_idx(*expected_subfeature).unwrap()); + } + } + + #[test] + fn test_blockchain_identities_derivation_path_mainnet() { + let variants: [(AccountType, u32); 4] = [ + (AccountType::BlockchainIdentitiesECDSA, 0), + (AccountType::BlockchainIdentitiesECDSAHash160, 1), + (AccountType::BlockchainIdentitiesBLS, 2), + (AccountType::BlockchainIdentitiesBLSHash160, 3), + ]; + + for (account_type, expected_subfeature) in &variants { + let path = account_type.derivation_path(Network::Mainnet).unwrap(); + let children: Vec<_> = path.into_iter().collect(); + assert_eq!(children.len(), 4); + // m/9'/5'/5'/subfeature' + assert_eq!(*children[0], ChildNumber::from_hardened_idx(9).unwrap()); + assert_eq!(*children[1], ChildNumber::from_hardened_idx(5).unwrap()); + assert_eq!(*children[2], ChildNumber::from_hardened_idx(5).unwrap()); + assert_eq!(*children[3], ChildNumber::from_hardened_idx(*expected_subfeature).unwrap()); + } + } } diff --git a/key-wallet/src/managed_account/managed_account_collection.rs b/key-wallet/src/managed_account/managed_account_collection.rs index 5fc272a03..ea69987fb 100644 --- a/key-wallet/src/managed_account/managed_account_collection.rs +++ b/key-wallet/src/managed_account/managed_account_collection.rs @@ -56,6 +56,18 @@ macro_rules! get_by_account_type_match_impl { CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { .. } => $self.asset_lock_shielded_address_topup.$as_opt(), + CoreAccountTypeMatch::BlockchainIdentitiesECDSA { + .. + } => $self.blockchain_identities_ecdsa.$as_opt(), + CoreAccountTypeMatch::BlockchainIdentitiesECDSAHash160 { + .. + } => $self.blockchain_identities_ecdsa_hash160.$as_opt(), + CoreAccountTypeMatch::BlockchainIdentitiesBLS { + .. + } => $self.blockchain_identities_bls.$as_opt(), + CoreAccountTypeMatch::BlockchainIdentitiesBLSHash160 { + .. + } => $self.blockchain_identities_bls_hash160.$as_opt(), CoreAccountTypeMatch::ProviderVotingKeys { .. } => $self.provider_voting_keys.$as_opt(), @@ -130,6 +142,14 @@ pub struct ManagedAccountCollection { pub asset_lock_address_topup: Option, /// Asset lock shielded address top-up account (optional) pub asset_lock_shielded_address_topup: Option, + /// Blockchain identities ECDSA account + pub blockchain_identities_ecdsa: Option, + /// Blockchain identities ECDSA_HASH160 account + pub blockchain_identities_ecdsa_hash160: Option, + /// Blockchain identities BLS account + pub blockchain_identities_bls: Option, + /// Blockchain identities BLS_HASH160 account + pub blockchain_identities_bls_hash160: Option, /// Provider voting keys (optional) pub provider_voting_keys: Option, /// Provider owner keys (optional) @@ -160,6 +180,10 @@ impl ManagedAccountCollection { identity_invitation: None, asset_lock_address_topup: None, asset_lock_shielded_address_topup: None, + blockchain_identities_ecdsa: None, + blockchain_identities_ecdsa_hash160: None, + blockchain_identities_bls: None, + blockchain_identities_bls_hash160: None, provider_voting_keys: None, provider_owner_keys: None, provider_operator_keys: None, @@ -210,6 +234,18 @@ impl ManagedAccountCollection { ManagedAccountType::AssetLockShieldedAddressTopUp { .. } => self.asset_lock_shielded_address_topup.is_some(), + ManagedAccountType::BlockchainIdentitiesECDSA { + .. + } => self.blockchain_identities_ecdsa.is_some(), + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + .. + } => self.blockchain_identities_ecdsa_hash160.is_some(), + ManagedAccountType::BlockchainIdentitiesBLS { + .. + } => self.blockchain_identities_bls.is_some(), + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + .. + } => self.blockchain_identities_bls_hash160.is_some(), ManagedAccountType::ProviderVotingKeys { .. } => self.provider_voting_keys.is_some(), @@ -319,6 +355,26 @@ impl ManagedAccountCollection { } => { self.asset_lock_shielded_address_topup = Some(account); } + ManagedAccountType::BlockchainIdentitiesECDSA { + .. + } => { + self.blockchain_identities_ecdsa = Some(account); + } + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + .. + } => { + self.blockchain_identities_ecdsa_hash160 = Some(account); + } + ManagedAccountType::BlockchainIdentitiesBLS { + .. + } => { + self.blockchain_identities_bls = Some(account); + } + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + .. + } => { + self.blockchain_identities_bls_hash160 = Some(account); + } ManagedAccountType::ProviderVotingKeys { .. } => { @@ -450,6 +506,27 @@ impl ManagedAccountCollection { } } + if let Some(account) = &account_collection.blockchain_identities_ecdsa { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.blockchain_identities_ecdsa = Some(managed_account); + } + } + if let Some(account) = &account_collection.blockchain_identities_ecdsa_hash160 { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.blockchain_identities_ecdsa_hash160 = Some(managed_account); + } + } + if let Some(account) = &account_collection.blockchain_identities_bls { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.blockchain_identities_bls = Some(managed_account); + } + } + if let Some(account) = &account_collection.blockchain_identities_bls_hash160 { + if let Ok(managed_account) = Self::create_managed_account_from_account(account) { + managed_collection.blockchain_identities_bls_hash160 = Some(managed_account); + } + } + if let Some(account) = &account_collection.provider_voting_keys { if let Ok(managed_account) = Self::create_managed_account_from_account(account) { managed_collection.provider_voting_keys = Some(managed_account); @@ -689,6 +766,41 @@ impl ManagedAccountCollection { addresses, } } + AccountType::BlockchainIdentitiesECDSA + | AccountType::BlockchainIdentitiesECDSAHash160 + | AccountType::BlockchainIdentitiesBLS + | AccountType::BlockchainIdentitiesBLSHash160 => { + let addresses = AddressPool::new( + base_path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + match account_type { + AccountType::BlockchainIdentitiesECDSA => { + ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + } + } + AccountType::BlockchainIdentitiesECDSAHash160 => { + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + } + } + AccountType::BlockchainIdentitiesBLS => { + ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + } + } + AccountType::BlockchainIdentitiesBLSHash160 => { + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + } + } + _ => unreachable!(), + } + } AccountType::ProviderVotingKeys => { let addresses = AddressPool::new( base_path, @@ -970,6 +1082,19 @@ impl ManagedAccountCollection { accounts.push(account); } + if let Some(account) = &self.blockchain_identities_ecdsa { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_ecdsa_hash160 { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_bls { + accounts.push(account); + } + if let Some(account) = &self.blockchain_identities_bls_hash160 { + accounts.push(account); + } + if let Some(account) = &self.provider_voting_keys { accounts.push(account); } @@ -1029,6 +1154,19 @@ impl ManagedAccountCollection { accounts.push(account); } + if let Some(account) = &mut self.blockchain_identities_ecdsa { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_ecdsa_hash160 { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_bls { + accounts.push(account); + } + if let Some(account) = &mut self.blockchain_identities_bls_hash160 { + accounts.push(account); + } + if let Some(account) = &mut self.provider_voting_keys { accounts.push(account); } @@ -1087,6 +1225,10 @@ impl ManagedAccountCollection { && self.identity_invitation.is_none() && self.asset_lock_address_topup.is_none() && self.asset_lock_shielded_address_topup.is_none() + && self.blockchain_identities_ecdsa.is_none() + && self.blockchain_identities_ecdsa_hash160.is_none() + && self.blockchain_identities_bls.is_none() + && self.blockchain_identities_bls_hash160.is_none() && self.provider_voting_keys.is_none() && self.provider_owner_keys.is_none() && self.provider_operator_keys.is_none() @@ -1107,6 +1249,10 @@ impl ManagedAccountCollection { self.identity_invitation = None; self.asset_lock_address_topup = None; self.asset_lock_shielded_address_topup = None; + self.blockchain_identities_ecdsa = None; + self.blockchain_identities_ecdsa_hash160 = None; + self.blockchain_identities_bls = None; + self.blockchain_identities_bls_hash160 = None; self.provider_voting_keys = None; self.provider_owner_keys = None; self.provider_operator_keys = None; diff --git a/key-wallet/src/managed_account/managed_account_type.rs b/key-wallet/src/managed_account/managed_account_type.rs index d86c6812f..2305ce832 100644 --- a/key-wallet/src/managed_account/managed_account_type.rs +++ b/key-wallet/src/managed_account/managed_account_type.rs @@ -70,6 +70,26 @@ pub enum ManagedAccountType { /// Asset lock shielded address top-up address pool addresses: AddressPool, }, + /// m/9'/coinType'/5'/0' -- ECDSA identity authentication keys + BlockchainIdentitiesECDSA { + /// Address pool + addresses: AddressPool, + }, + /// m/9'/coinType'/5'/1' -- ECDSA_HASH160 identity authentication keys + BlockchainIdentitiesECDSAHash160 { + /// Address pool + addresses: AddressPool, + }, + /// m/9'/coinType'/5'/2' -- BLS identity authentication keys + BlockchainIdentitiesBLS { + /// Address pool + addresses: AddressPool, + }, + /// m/9'/coinType'/5'/3' -- BLS_HASH160 identity authentication keys + BlockchainIdentitiesBLSHash160 { + /// Address pool + addresses: AddressPool, + }, /// Provider voting keys (DIP-3) /// Path: `m/9'/5'/3'/1'/[key_index]` ProviderVotingKeys { @@ -161,6 +181,18 @@ impl ManagedAccountType { | Self::AssetLockShieldedAddressTopUp { .. } + | Self::BlockchainIdentitiesECDSA { + .. + } + | Self::BlockchainIdentitiesECDSAHash160 { + .. + } + | Self::BlockchainIdentitiesBLS { + .. + } + | Self::BlockchainIdentitiesBLSHash160 { + .. + } | Self::ProviderVotingKeys { .. } @@ -242,6 +274,22 @@ impl ManagedAccountType { addresses, .. } + | Self::BlockchainIdentitiesECDSA { + addresses, + .. + } + | Self::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | Self::BlockchainIdentitiesBLS { + addresses, + .. + } + | Self::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | Self::ProviderVotingKeys { addresses, .. @@ -313,6 +361,22 @@ impl ManagedAccountType { addresses, .. } + | Self::BlockchainIdentitiesECDSA { + addresses, + .. + } + | Self::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | Self::BlockchainIdentitiesBLS { + addresses, + .. + } + | Self::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | Self::ProviderVotingKeys { addresses, .. @@ -432,6 +496,18 @@ impl ManagedAccountType { Self::AssetLockShieldedAddressTopUp { .. } => AccountType::AssetLockShieldedAddressTopUp, + Self::BlockchainIdentitiesECDSA { + .. + } => AccountType::BlockchainIdentitiesECDSA, + Self::BlockchainIdentitiesECDSAHash160 { + .. + } => AccountType::BlockchainIdentitiesECDSAHash160, + Self::BlockchainIdentitiesBLS { + .. + } => AccountType::BlockchainIdentitiesBLS, + Self::BlockchainIdentitiesBLSHash160 { + .. + } => AccountType::BlockchainIdentitiesBLSHash160, Self::ProviderVotingKeys { .. } => AccountType::ProviderVotingKeys, @@ -639,6 +715,41 @@ impl ManagedAccountType { addresses: pool, }) } + AccountType::BlockchainIdentitiesECDSA + | AccountType::BlockchainIdentitiesECDSAHash160 + | AccountType::BlockchainIdentitiesBLS + | AccountType::BlockchainIdentitiesBLSHash160 => { + let path = account_type + .derivation_path(network) + .unwrap_or_else(|_| DerivationPath::master()); + let pool = AddressPool::new( + path, + AddressPoolType::Absent, + DEFAULT_SPECIAL_GAP_LIMIT, + network, + key_source, + )?; + + Ok(match account_type { + AccountType::BlockchainIdentitiesECDSA => Self::BlockchainIdentitiesECDSA { + addresses: pool, + }, + AccountType::BlockchainIdentitiesECDSAHash160 => { + Self::BlockchainIdentitiesECDSAHash160 { + addresses: pool, + } + } + AccountType::BlockchainIdentitiesBLS => Self::BlockchainIdentitiesBLS { + addresses: pool, + }, + AccountType::BlockchainIdentitiesBLSHash160 => { + Self::BlockchainIdentitiesBLSHash160 { + addresses: pool, + } + } + _ => unreachable!(), + }) + } AccountType::ProviderVotingKeys => { let path = account_type .derivation_path(network) diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index 82ba2b803..d3110f12b 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -243,6 +243,22 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -670,6 +686,22 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -767,6 +799,22 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. @@ -1004,6 +1052,22 @@ impl ManagedCoreAccount { addresses, .. } + | ManagedAccountType::BlockchainIdentitiesECDSA { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLS { + addresses, + .. + } + | ManagedAccountType::BlockchainIdentitiesBLSHash160 { + addresses, + .. + } | ManagedAccountType::ProviderVotingKeys { addresses, .. diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index 15089cef9..1ba8d8bdf 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -92,6 +92,22 @@ pub enum CoreAccountTypeMatch { AssetLockShieldedAddressTopUp { involved_addresses: Vec, }, + /// Blockchain identities ECDSA account + BlockchainIdentitiesECDSA { + involved_addresses: Vec, + }, + /// Blockchain identities ECDSA_HASH160 account + BlockchainIdentitiesECDSAHash160 { + involved_addresses: Vec, + }, + /// Blockchain identities BLS account + BlockchainIdentitiesBLS { + involved_addresses: Vec, + }, + /// Blockchain identities BLS_HASH160 account + BlockchainIdentitiesBLSHash160 { + involved_addresses: Vec, + }, /// Provider voting keys account (no index) ProviderVotingKeys { involved_addresses: Vec, @@ -161,6 +177,18 @@ impl CoreAccountTypeMatch { | CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { involved_addresses, } + | CoreAccountTypeMatch::BlockchainIdentitiesECDSA { + involved_addresses, + } + | CoreAccountTypeMatch::BlockchainIdentitiesECDSAHash160 { + involved_addresses, + } + | CoreAccountTypeMatch::BlockchainIdentitiesBLS { + involved_addresses, + } + | CoreAccountTypeMatch::BlockchainIdentitiesBLSHash160 { + involved_addresses, + } | CoreAccountTypeMatch::ProviderVotingKeys { involved_addresses, } @@ -245,6 +273,18 @@ impl CoreAccountTypeMatch { CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { .. } => AccountTypeToCheck::AssetLockShieldedAddressTopUp, + CoreAccountTypeMatch::BlockchainIdentitiesECDSA { + .. + } => AccountTypeToCheck::BlockchainIdentitiesECDSA, + CoreAccountTypeMatch::BlockchainIdentitiesECDSAHash160 { + .. + } => AccountTypeToCheck::BlockchainIdentitiesECDSAHash160, + CoreAccountTypeMatch::BlockchainIdentitiesBLS { + .. + } => AccountTypeToCheck::BlockchainIdentitiesBLS, + CoreAccountTypeMatch::BlockchainIdentitiesBLSHash160 { + .. + } => AccountTypeToCheck::BlockchainIdentitiesBLSHash160, CoreAccountTypeMatch::ProviderVotingKeys { .. } => AccountTypeToCheck::ProviderVotingKeys, @@ -361,6 +401,30 @@ impl ManagedAccountCollection { .and_then(|account| account.check_asset_lock_transaction_for_match(tx, None)) .into_iter() .collect(), + AccountTypeToCheck::BlockchainIdentitiesECDSA => self + .blockchain_identities_ecdsa + .as_ref() + .and_then(|account| account.check_transaction_for_match(tx, None)) + .into_iter() + .collect(), + AccountTypeToCheck::BlockchainIdentitiesECDSAHash160 => self + .blockchain_identities_ecdsa_hash160 + .as_ref() + .and_then(|account| account.check_transaction_for_match(tx, None)) + .into_iter() + .collect(), + AccountTypeToCheck::BlockchainIdentitiesBLS => self + .blockchain_identities_bls + .as_ref() + .and_then(|account| account.check_transaction_for_match(tx, None)) + .into_iter() + .collect(), + AccountTypeToCheck::BlockchainIdentitiesBLSHash160 => self + .blockchain_identities_bls_hash160 + .as_ref() + .and_then(|account| account.check_transaction_for_match(tx, None)) + .into_iter() + .collect(), AccountTypeToCheck::ProviderVotingKeys => self .provider_voting_keys .as_ref() @@ -640,6 +704,26 @@ impl ManagedCoreAccount { } => CoreAccountTypeMatch::AssetLockShieldedAddressTopUp { involved_addresses: involved_other_addresses, }, + ManagedAccountType::BlockchainIdentitiesECDSA { + .. + } => CoreAccountTypeMatch::BlockchainIdentitiesECDSA { + involved_addresses: involved_other_addresses, + }, + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + .. + } => CoreAccountTypeMatch::BlockchainIdentitiesECDSAHash160 { + involved_addresses: involved_other_addresses, + }, + ManagedAccountType::BlockchainIdentitiesBLS { + .. + } => CoreAccountTypeMatch::BlockchainIdentitiesBLS { + involved_addresses: involved_other_addresses, + }, + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + .. + } => CoreAccountTypeMatch::BlockchainIdentitiesBLSHash160 { + involved_addresses: involved_other_addresses, + }, ManagedAccountType::ProviderVotingKeys { .. } => CoreAccountTypeMatch::ProviderVotingKeys { @@ -1019,6 +1103,31 @@ impl ManagedCoreAccount { } } + // Check blockchain identities accounts + if let Some(account) = &collection.blockchain_identities_ecdsa { + if account.contains_address(address) { + return Some((AccountTypeToCheck::BlockchainIdentitiesECDSA, None)); + } + } + + if let Some(account) = &collection.blockchain_identities_ecdsa_hash160 { + if account.contains_address(address) { + return Some((AccountTypeToCheck::BlockchainIdentitiesECDSAHash160, None)); + } + } + + if let Some(account) = &collection.blockchain_identities_bls { + if account.contains_address(address) { + return Some((AccountTypeToCheck::BlockchainIdentitiesBLS, None)); + } + } + + if let Some(account) = &collection.blockchain_identities_bls_hash160 { + if account.contains_address(address) { + return Some((AccountTypeToCheck::BlockchainIdentitiesBLSHash160, None)); + } + } + // Check provider accounts if let Some(account) = &collection.provider_voting_keys { if account.contains_address(address) { diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index fdaa556d7..37a0ce0e8 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -75,6 +75,10 @@ impl TransactionRouter { vec![ AccountTypeToCheck::StandardBIP44, AccountTypeToCheck::StandardBIP32, + AccountTypeToCheck::BlockchainIdentitiesECDSA, + AccountTypeToCheck::BlockchainIdentitiesECDSAHash160, + AccountTypeToCheck::BlockchainIdentitiesBLS, + AccountTypeToCheck::BlockchainIdentitiesBLSHash160, AccountTypeToCheck::DashpayReceivingFunds, AccountTypeToCheck::DashpayExternalAccount, ] @@ -179,6 +183,10 @@ pub enum AccountTypeToCheck { IdentityInvitation, AssetLockAddressTopUp, AssetLockShieldedAddressTopUp, + BlockchainIdentitiesECDSA, + BlockchainIdentitiesECDSAHash160, + BlockchainIdentitiesBLS, + BlockchainIdentitiesBLSHash160, ProviderVotingKeys, ProviderOwnerKeys, ProviderOperatorKeys, @@ -234,6 +242,18 @@ impl TryFrom for AccountTypeToCheck { ManagedAccountType::AssetLockShieldedAddressTopUp { .. } => Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp), + ManagedAccountType::BlockchainIdentitiesECDSA { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesECDSA), + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesECDSAHash160), + ManagedAccountType::BlockchainIdentitiesBLS { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesBLS), + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesBLSHash160), ManagedAccountType::ProviderVotingKeys { .. } => Ok(AccountTypeToCheck::ProviderVotingKeys), @@ -299,6 +319,18 @@ impl TryFrom<&ManagedAccountType> for AccountTypeToCheck { ManagedAccountType::AssetLockShieldedAddressTopUp { .. } => Ok(AccountTypeToCheck::AssetLockShieldedAddressTopUp), + ManagedAccountType::BlockchainIdentitiesECDSA { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesECDSA), + ManagedAccountType::BlockchainIdentitiesECDSAHash160 { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesECDSAHash160), + ManagedAccountType::BlockchainIdentitiesBLS { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesBLS), + ManagedAccountType::BlockchainIdentitiesBLSHash160 { + .. + } => Ok(AccountTypeToCheck::BlockchainIdentitiesBLSHash160), ManagedAccountType::ProviderVotingKeys { .. } => Ok(AccountTypeToCheck::ProviderVotingKeys), diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs index fb273ef6e..31b756a15 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs @@ -155,7 +155,8 @@ async fn test_identity_registration_account_routing() { } #[tokio::test] -async fn test_normal_payment_to_identity_address_not_detected() { +async fn test_normal_payment_to_identity_registration_shared_path_detected_via_blockchain_identities_ecdsa_hash160( +) { let network = Network::Testnet; let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::Default) @@ -210,21 +211,17 @@ async fn test_normal_payment_to_identity_address_not_detected() { ) .await; - // A normal transaction to an identity registration address should NOT be detected - // Identity addresses are only for special transactions (AssetLock) + // The identity registration address (m/9'/coinType'/5'/1') is shared with + // BlockchainIdentitiesECDSAHash160 (subfeature=1). Since BlockchainIdentities accounts are + // checked for standard transactions, this normal payment IS detected + // via BlockchainIdentitiesECDSAHash160 (but NOT via IdentityRegistration). assert!( - !result.is_relevant, - "Normal payment to identity address should not be detected as relevant. Got is_relevant={}", - result.is_relevant - ); - - assert_eq!( - result.total_received, 0, - "Should not have received any funds from normal payment to identity address. Got {} duffs", - result.total_received + result.is_relevant, + "Normal payment to identity-path address should be detected via BlockchainIdentitiesECDSAHash160" ); - // Verify that identity registration account is not in the affected accounts + // Verify that identity registration account is NOT in the affected accounts + // (IdentityRegistration is only checked for AssetLock transactions) assert!( !result.affected_accounts.iter().any(|acc| matches!( acc.account_type_match.to_account_type_to_check(), @@ -232,6 +229,16 @@ async fn test_normal_payment_to_identity_address_not_detected() { )), "Identity registration account should not be affected by normal payment" ); + + // Specifically BlockchainIdentitiesECDSAHash160 (subfeature=1) should be in the affected + // accounts, as it shares the m/9'/coinType'/5'/1' derivation path with identity registration + assert!( + result.affected_accounts.iter().any(|acc| matches!( + acc.account_type_match.to_account_type_to_check(), + AccountTypeToCheck::BlockchainIdentitiesECDSAHash160 + )), + "BlockchainIdentitiesECDSAHash160 account should be affected by normal payment to the shared m/9'/coinType'/5'/1' path" + ); } #[test] diff --git a/key-wallet/src/wallet/helper.rs b/key-wallet/src/wallet/helper.rs index 82e693d0e..8e0d4b69c 100644 --- a/key-wallet/src/wallet/helper.rs +++ b/key-wallet/src/wallet/helper.rs @@ -555,6 +555,12 @@ impl Wallet { // Asset lock shielded address top-up self.add_account(AccountType::AssetLockShieldedAddressTopUp, None)?; + // Blockchain identity accounts + self.add_account(AccountType::BlockchainIdentitiesECDSA, None)?; + self.add_account(AccountType::BlockchainIdentitiesECDSAHash160, None)?; + self.add_account(AccountType::BlockchainIdentitiesBLS, None)?; + self.add_account(AccountType::BlockchainIdentitiesBLSHash160, None)?; + // Provider keys accounts self.add_account(AccountType::ProviderVotingKeys, None)?; self.add_account(AccountType::ProviderOwnerKeys, None)?; @@ -583,6 +589,15 @@ impl Wallet { // Asset lock shielded address top-up self.add_account_with_passphrase(AccountType::AssetLockShieldedAddressTopUp, passphrase)?; + // Blockchain identity accounts + self.add_account_with_passphrase(AccountType::BlockchainIdentitiesECDSA, passphrase)?; + self.add_account_with_passphrase( + AccountType::BlockchainIdentitiesECDSAHash160, + passphrase, + )?; + self.add_account_with_passphrase(AccountType::BlockchainIdentitiesBLS, passphrase)?; + self.add_account_with_passphrase(AccountType::BlockchainIdentitiesBLSHash160, passphrase)?; + // Provider keys accounts self.add_account_with_passphrase(AccountType::ProviderVotingKeys, passphrase)?; self.add_account_with_passphrase(AccountType::ProviderOwnerKeys, passphrase)?; @@ -846,6 +861,18 @@ impl Wallet { crate::transaction_checking::transaction_router::AccountTypeToCheck::ProviderOwnerKeys => { coll.provider_owner_keys.as_ref().map(|a| a.account_xpub) } + crate::transaction_checking::transaction_router::AccountTypeToCheck::BlockchainIdentitiesECDSA => { + coll.blockchain_identities_ecdsa.as_ref().map(|a| a.account_xpub) + } + crate::transaction_checking::transaction_router::AccountTypeToCheck::BlockchainIdentitiesECDSAHash160 => { + coll.blockchain_identities_ecdsa_hash160.as_ref().map(|a| a.account_xpub) + } + crate::transaction_checking::transaction_router::AccountTypeToCheck::BlockchainIdentitiesBLS => { + coll.blockchain_identities_bls.as_ref().map(|a| a.account_xpub) + } + crate::transaction_checking::transaction_router::AccountTypeToCheck::BlockchainIdentitiesBLSHash160 => { + coll.blockchain_identities_bls_hash160.as_ref().map(|a| a.account_xpub) + } crate::transaction_checking::transaction_router::AccountTypeToCheck::ProviderOperatorKeys | crate::transaction_checking::transaction_router::AccountTypeToCheck::ProviderPlatformKeys => { // These use BLS/EdDSA keys, not regular xpubs diff --git a/key-wallet/tests/integration_test.rs b/key-wallet/tests/integration_test.rs index e3bcff52b..0795ac009 100644 --- a/key-wallet/tests/integration_test.rs +++ b/key-wallet/tests/integration_test.rs @@ -60,10 +60,11 @@ fn test_account_management() { ); assert!(result.is_ok()); - // Get accounts from wallet - Default creates 11 accounts (including PlatformPayment), plus the one we added + // Get accounts from wallet - Default creates 15 accounts (including PlatformPayment + // and 4 BlockchainIdentities variants), plus the one we added let accounts = manager.get_accounts(&wallet_id); assert!(accounts.is_ok()); - assert_eq!(accounts.unwrap().len(), 12); // 11 from Default + 1 we added + assert_eq!(accounts.unwrap().len(), 16); // 15 from Default + 1 we added } #[test]