Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions key-wallet/src/tests/special_transaction_matching_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,97 @@ async fn provider_registration_with_operator_public_key_matches_provider_operato
assert_matched_account_type(&result, AccountTypeToCheck::ProviderOperatorKeys);
}

/// After a ProRegTx uses an operator key, gap-limit maintenance must extend
/// the operator pool with freshly derived BLS keys — exactly like the ECDSA
/// owner/voting pools. Regression test for the operator pool being skipped
/// because the wallet exposes no ECDSA xpub for BLS accounts (the checker
/// bailed before `maintain_gap_limit`).
#[cfg(feature = "bls")]
#[tokio::test]
async fn provider_registration_extends_operator_key_gap_limit() {
use crate::managed_account::address_pool::PublicKeyType;
use crate::managed_account::managed_account_type::ManagedAccountType;
use crate::AccountType;

let (mut wallet, mut info) = make_wallet();
let (_other_wallet, mut other_info) = make_wallet();

let owner_addr = other_info
.provider_owner_keys_managed_account_mut()
.expect("other owner")
.next_address(None, true)
.expect("derive owner");
let voting_addr = other_info
.provider_voting_keys_managed_account_mut()
.expect("other voting")
.next_address(None, true)
.expect("derive voting");
// Takes operator key 0 and marks it used, so the pool's unused window
// shrinks below the gap limit.
let operator_pk = info
.provider_operator_keys_managed_account_mut()
.expect("provider_operator_keys managed")
.next_bls_operator_key(None, true)
.expect("derive operator");

let operator_pool_state = |info: &ManagedWalletInfo| {
let account =
info.provider_operator_keys_managed_account().expect("operator managed account");
match account.managed_account_type() {
ManagedAccountType::ProviderOperatorKeys {
addresses,
} => (addresses.highest_generated, addresses.highest_used, addresses.gap_limit),
_ => unreachable!("operator account holds an operator pool"),
}
};

let (highest_generated_before, highest_used_before, gap_limit) = operator_pool_state(&info);
assert_eq!(highest_used_before, Some(0));
assert!(
highest_generated_before < Some(gap_limit),
"setup: the pre-generated window must not already satisfy the gap limit"
);

let tx = prov_reg_tx(
ProviderMasternodeType::Regular,
derive_pubkey_hash(&owner_addr),
derive_pubkey_hash(&voting_addr),
operator_pk.0.to_compressed().into(),
ScriptBuf::new(),
None,
);
let result =
info.check_core_transaction(&tx, test_block_context(), &mut wallet, true, true).await;

assert_matched_account_type(&result, AccountTypeToCheck::ProviderOperatorKeys);

// The pool must now hold `gap_limit` unused keys past the used index 0.
let (highest_generated_after, highest_used_after, _) = operator_pool_state(&info);
assert_eq!(highest_used_after, Some(0));
assert_eq!(highest_generated_after, Some(gap_limit));

// The freshly derived keys are surfaced to callers and carry BLS public
// keys matching the audited gate-free derivation entry point.
let bls_account = wallet
.accounts
.bls_account_of_type(AccountType::ProviderOperatorKeys)
.expect("operator account exists");
let new_operator_keys: Vec<_> = result
.new_addresses
.iter()
.filter(|derived| derived.account_type == AccountType::ProviderOperatorKeys)
.collect();
assert!(!new_operator_keys.is_empty(), "gap maintenance must derive new operator keys");
for derived in new_operator_keys {
let expected = bls_account
.operator_public_key_at(derived.info.index)
.expect("gate-free public derivation")
.to_bytes()
.to_vec();
assert_eq!(derived.info.public_key, Some(PublicKeyType::BLS(expected)));
}
}

#[cfg(feature = "eddsa")]
#[tokio::test]
async fn provider_registration_with_platform_node_id_matches_provider_platform_keys() {
Expand Down
9 changes: 4 additions & 5 deletions key-wallet/src/transaction_checking/wallet_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,13 @@ impl WalletTransactionChecker for ManagedWalletInfo {
account.mark_address_used(&address_info.address);
}

let Some(xpub) = wallet.extended_public_key_for_account_type(
let key_source = wallet.key_source_for_account_type(
&account_match.account_type_match.to_account_type_to_check(),
account_match.account_type_match.account_index(),
) else {
);
if matches!(key_source, KeySource::NoKeySource) {
continue;
};

let key_source = KeySource::Public(xpub);
}
let rev_before = result.new_addresses.len();
let owning_account_type = account.managed_account_type().to_account_type();
for pool in account.managed_account_type_mut().address_pools_mut() {
Expand Down
34 changes: 34 additions & 0 deletions key-wallet/src/wallet/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -616,4 +616,38 @@ impl Wallet {
}
}
}

/// Get a [`crate::KeySource`] capable of public (watch-side) derivation
/// for a specific account type.
///
/// This is the key-source counterpart of
/// [`Self::extended_public_key_for_account_type`]: ECDSA accounts yield
/// [`crate::KeySource::Public`], while the BLS provider operator account
/// yields [`crate::KeySource::BLSPublic`] (legacy-scheme non-hardened
/// derivation) so gap-limit maintenance can extend the operator key pool
/// just like the owner/voting pools.
///
/// Returns [`crate::KeySource::NoKeySource`] for account types that
/// cannot derive publicly: the Ed25519 platform node account (SLIP-0010
/// supports hardened derivation only) and Dashpay accounts (not
/// retrieved via this helper).
pub fn key_source_for_account_type(
&self,
account_type: &crate::transaction_checking::transaction_router::AccountTypeToCheck,
account_index: Option<u32>,
) -> crate::KeySource {
match account_type {
#[cfg(feature = "bls")]
crate::transaction_checking::transaction_router::AccountTypeToCheck::ProviderOperatorKeys => self
.accounts
.provider_operator_keys
.as_ref()
.map(|a| crate::KeySource::BLSPublic(a.bls_public_key.clone()))
.unwrap_or(crate::KeySource::NoKeySource),
_ => self
.extended_public_key_for_account_type(account_type, account_index)
.map(crate::KeySource::Public)
.unwrap_or(crate::KeySource::NoKeySource),
}
}
}
Loading