Skip to content

Commit f032d8f

Browse files
feat(key-wallet): add DIP-13 identity authentication accounts (ECDSA + BLS)
Add two new `AccountType` variants for DIP-13 sub-feature 0' (per-identity signing keys the user employs to sign Dash Platform state transitions): - `IdentityAuthenticationEcdsa { identity_index }` — key_type 0', backed by a regular `Account` (secp256k1). - `IdentityAuthenticationBls { identity_index }` — key_type 1', backed by `BLSAccount`, gated on `#[cfg(feature = "bls")]`. Both account types use the DIP-13 derivation path `m/9'/coin_type'/5'/0'/key_type'/identity_index'` with hardened children for individual keys (`.../identity_index'/key_index'`). Address pools use `AbsentHardened` since DIP-13 mandates hardened leaves. ### Wiring - `AccountCollection` gains `identity_authentication_ecdsa: BTreeMap<u32, Account>` and (under `bls`) `identity_authentication_bls: BTreeMap<u32, BLSAccount>`, keyed by `identity_index`. All collection methods (`new`, `insert`, `insert_bls_account`, `contains_account_type`, `account_of_type[_mut]`, `bls_account_of_type[_mut]`, `all_accounts[_mut]`, `count`, `is_empty`, `clear`) are updated. - `ManagedAccountCollection`, `ManagedAccountType`, `CoreAccountTypeMatch` mirror the new variants and are routed through the usual matchers. - `AccountTypeToCheck::IdentityAuthentication{Ecdsa,Bls}` variants are added so conversions from `ManagedAccountType`/`AccountType` stay total. Identity authentication accounts are **Platform-only**: they are deliberately absent from every `TransactionType` relevance set (`TransactionRouter::get_relevant_account_types`), and the `ManagedAccountCollection::check_account_type` arms return empty results. Address matching in `ManagedCoreAccount::check_transaction_for_match` returns `None` for these variants for the same reason. - `Wallet::add_bls_account` now accepts `IdentityAuthenticationBls` in addition to `ProviderOperatorKeys`. - Two new DIP-9 `IndexConstPath<5>` constants per network (`IDENTITY_AUTHENTICATION_{ECDSA,BLS}_PATH_{MAINNET,TESTNET}`) and the matching `DerivationPathReference::BlockchainIdentityAuthentication{Ecdsa,Bls}` variants. - `asset_lock_builder::resolve_funding_account` is intentionally left untouched — identity authentication accounts do not fund asset locks. - `WalletAccountCreationOptions` is unchanged. Identity authentication accounts are per-identity and come into existence when the user registers a Platform identity, not at wallet creation. Callers insert them post-hoc via `Wallet::add_account` (ECDSA) or `Wallet::add_bls_account` (BLS). ### FFI `FFIAccountType` gains `IdentityAuthenticationEcdsa = 16` and `IdentityAuthenticationBls = 17`; `to_account_type` / `from_account_type` route the `index` parameter as `identity_index`. `FFIAccountMatch` emission for `CoreAccountTypeMatch::IdentityAuthentication*` reports the identity index in `account_index` (these variants are never produced by the L1 transaction router, but the FFI matcher stays exhaustive). ### Tests New `identity_authentication_tests` module in `account_type.rs` covers: ECDSA and BLS mainnet/testnet/regtest path derivation, `index()` / `derivation_path_reference()` / `AccountTypeToCheck` round-trip, and end-to-end insert / `contains_account_type` / `account_of_type` / `bls_account_of_type` round-trips through `AccountCollection`. BLS tests are `#[cfg(feature = "bls")]`-gated. Existing `test_wrong_account_type_for_bls` message was updated for the broadened `insert_bls_account` validation. ### Serialization compatibility Adding enum variants is forward-incompatible for `bincode::Encode`/ `Decode` — wallet blobs serialized by earlier v0.42-dev builds will fail to decode after this change. This is acceptable given the unstable 0.x API per `CLAUDE.md`. Serde uses its default (externally tagged) representation, so new readers still decode old data identically and old readers will error cleanly on new variants they cannot name. Verified: `cargo build -p key-wallet --all-features`, `cargo test -p key-wallet --lib --all-features`, `cargo clippy -p key-wallet --all-features --all-targets -- -D warnings`, `cargo fmt -p key-wallet --check`, and downstream `key-wallet-ffi` / `key-wallet-manager` builds and lib tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ee1ebd9 commit f032d8f

17 files changed

Lines changed: 1114 additions & 72 deletions

File tree

key-wallet-ffi/src/account.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,20 @@ pub unsafe extern "C" fn wallet_get_account(
8989
}
9090

9191
let wallet = &*wallet;
92-
let account_type_rust = account_type.to_account_type(account_index);
92+
let account_type_rust = match account_type.to_account_type(account_index) {
93+
Ok(t) => t,
94+
Err(mut err) => {
95+
let code = err.code;
96+
let message = if err.message.is_null() {
97+
"Invalid account type".to_string()
98+
} else {
99+
let msg = std::ffi::CStr::from_ptr(err.message).to_string_lossy().to_string();
100+
err.free_message();
101+
msg
102+
};
103+
return FFIAccountResult::error(code, message);
104+
}
105+
};
93106

94107
match wallet.inner().accounts.account_of_type(account_type_rust) {
95108
Some(account) => {

key-wallet-ffi/src/address_pool.rs

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ fn get_managed_account_by_type<'a>(
4545
collection.identity_topup_not_bound.as_ref()
4646
}
4747
AccountType::IdentityInvitation => collection.identity_invitation.as_ref(),
48+
AccountType::IdentityAuthenticationEcdsa {
49+
identity_index,
50+
} => collection.identity_authentication_ecdsa.get(identity_index),
51+
AccountType::IdentityAuthenticationBls {
52+
identity_index,
53+
} => collection.identity_authentication_bls.get(identity_index),
4854
AccountType::AssetLockAddressTopUp => collection.asset_lock_address_topup.as_ref(),
4955
AccountType::AssetLockShieldedAddressTopUp => {
5056
collection.asset_lock_shielded_address_topup.as_ref()
@@ -98,6 +104,12 @@ fn get_managed_account_by_type_mut<'a>(
98104
collection.identity_topup_not_bound.as_mut()
99105
}
100106
AccountType::IdentityInvitation => collection.identity_invitation.as_mut(),
107+
AccountType::IdentityAuthenticationEcdsa {
108+
identity_index,
109+
} => collection.identity_authentication_ecdsa.get_mut(identity_index),
110+
AccountType::IdentityAuthenticationBls {
111+
identity_index,
112+
} => collection.identity_authentication_bls.get_mut(identity_index),
101113
AccountType::AssetLockAddressTopUp => collection.asset_lock_address_topup.as_mut(),
102114
AccountType::AssetLockShieldedAddressTopUp => {
103115
collection.asset_lock_shielded_address_topup.as_mut()
@@ -298,7 +310,20 @@ pub unsafe extern "C" fn managed_wallet_get_address_pool_info(
298310
let wrapper = &*managed_wallet;
299311
let managed_wallet = wrapper.inner();
300312

301-
let account_type_rust = account_type.to_account_type(account_index);
313+
let account_type_rust = match account_type.to_account_type(account_index) {
314+
Ok(t) => t,
315+
Err(mut e) => {
316+
let msg = if e.message.is_null() {
317+
"Invalid account type".to_string()
318+
} else {
319+
let m = std::ffi::CStr::from_ptr(e.message).to_string_lossy().to_string();
320+
e.free_message();
321+
m
322+
};
323+
FFIError::set_error(error, e.code, msg);
324+
return false;
325+
}
326+
};
302327

303328
// Get the specific managed account
304329
let managed_account =
@@ -404,7 +429,20 @@ pub unsafe extern "C" fn managed_wallet_set_gap_limit(
404429

405430
let managed_wallet = (&mut *managed_wallet).inner_mut();
406431

407-
let account_type_rust = account_type.to_account_type(account_index);
432+
let account_type_rust = match account_type.to_account_type(account_index) {
433+
Ok(t) => t,
434+
Err(mut e) => {
435+
let msg = if e.message.is_null() {
436+
"Invalid account type".to_string()
437+
} else {
438+
let m = std::ffi::CStr::from_ptr(e.message).to_string_lossy().to_string();
439+
e.free_message();
440+
m
441+
};
442+
FFIError::set_error(error, e.code, msg);
443+
return false;
444+
}
445+
};
408446

409447
// Get the specific managed account
410448
let managed_account =
@@ -501,7 +539,20 @@ pub unsafe extern "C" fn managed_wallet_generate_addresses_to_index(
501539
let managed_wallet = (&mut *managed_wallet).inner_mut();
502540
let wallet = &*wallet;
503541

504-
let account_type_rust = account_type.to_account_type(account_index);
542+
let account_type_rust = match account_type.to_account_type(account_index) {
543+
Ok(t) => t,
544+
Err(mut e) => {
545+
let msg = if e.message.is_null() {
546+
"Invalid account type".to_string()
547+
} else {
548+
let m = std::ffi::CStr::from_ptr(e.message).to_string_lossy().to_string();
549+
e.free_message();
550+
m
551+
};
552+
FFIError::set_error(error, e.code, msg);
553+
return false;
554+
}
555+
};
505556

506557
let account_type_to_check = match account_type_rust.try_into() {
507558
Ok(check_type) => check_type,
@@ -746,6 +797,22 @@ pub unsafe extern "C" fn managed_wallet_mark_address_used(
746797
}
747798
}
748799
}
800+
if !found {
801+
for account in collection.identity_authentication_ecdsa.values_mut() {
802+
if account.mark_address_used(&address) {
803+
found = true;
804+
break;
805+
}
806+
}
807+
}
808+
if !found {
809+
for account in collection.identity_authentication_bls.values_mut() {
810+
if account.mark_address_used(&address) {
811+
found = true;
812+
break;
813+
}
814+
}
815+
}
749816
if !found {
750817
if let Some(account) = &mut collection.asset_lock_address_topup {
751818
if account.mark_address_used(&address) {

key-wallet-ffi/src/managed_account.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,25 @@ pub unsafe extern "C" fn managed_wallet_get_account(
218218
}
219219

220220
let managed_wallet = &*managed_wallet_ptr;
221-
let account_type_rust = account_type.to_account_type(account_index);
221+
let account_type_rust = match account_type.to_account_type(account_index) {
222+
Ok(t) => t,
223+
Err(mut e) => {
224+
let code = e.code;
225+
let message = if e.message.is_null() {
226+
"Invalid account type".to_string()
227+
} else {
228+
let m = std::ffi::CStr::from_ptr(e.message).to_string_lossy().to_string();
229+
e.free_message();
230+
m
231+
};
232+
// `wallet_manager_get_managed_wallet_info` allocated `managed_wallet_ptr`
233+
// above; the success path frees it via `managed_wallet_info_free` at the
234+
// bottom of this function. Do the same here before bailing so the
235+
// managed-wallet handle isn't leaked on invalid-account-type errors.
236+
crate::managed_wallet::managed_wallet_info_free(managed_wallet_ptr);
237+
return FFIManagedCoreAccountResult::error(code, message);
238+
}
239+
};
222240

223241
let result = {
224242
use key_wallet::account::StandardAccountType;
@@ -247,6 +265,12 @@ pub unsafe extern "C" fn managed_wallet_get_account(
247265
managed_collection.identity_topup_not_bound.as_ref()
248266
}
249267
AccountType::IdentityInvitation => managed_collection.identity_invitation.as_ref(),
268+
AccountType::IdentityAuthenticationEcdsa {
269+
identity_index,
270+
} => managed_collection.identity_authentication_ecdsa.get(&identity_index),
271+
AccountType::IdentityAuthenticationBls {
272+
identity_index,
273+
} => managed_collection.identity_authentication_bls.get(&identity_index),
250274
AccountType::AssetLockAddressTopUp => {
251275
managed_collection.asset_lock_address_topup.as_ref()
252276
}
@@ -564,6 +588,12 @@ pub unsafe extern "C" fn managed_core_account_get_account_type(
564588
FFIAccountType::IdentityTopUpNotBoundToIdentity
565589
}
566590
AccountType::IdentityInvitation => FFIAccountType::IdentityInvitation,
591+
AccountType::IdentityAuthenticationEcdsa {
592+
..
593+
} => FFIAccountType::IdentityAuthenticationEcdsa,
594+
AccountType::IdentityAuthenticationBls {
595+
..
596+
} => FFIAccountType::IdentityAuthenticationBls,
567597
AccountType::AssetLockAddressTopUp => FFIAccountType::AssetLockAddressTopUp,
568598
AccountType::AssetLockShieldedAddressTopUp => FFIAccountType::AssetLockShieldedAddressTopUp,
569599
AccountType::ProviderVotingKeys => FFIAccountType::ProviderVotingKeys,
@@ -1167,6 +1197,14 @@ pub unsafe extern "C" fn managed_core_account_get_address_pool(
11671197
addresses,
11681198
..
11691199
} => addresses,
1200+
ManagedAccountType::IdentityAuthenticationEcdsa {
1201+
addresses,
1202+
..
1203+
} => addresses,
1204+
ManagedAccountType::IdentityAuthenticationBls {
1205+
addresses,
1206+
..
1207+
} => addresses,
11701208
};
11711209

11721210
let ffi_pool = FFIAddressPool {

0 commit comments

Comments
 (0)