Skip to content

Add top_up_index field to CreditOutputFunding::IdentityTopUp (DIP-9 conformance gap) #762

@lklimek

Description

@lklimek

User Story

As a Dash Platform application developer using key-wallet to fund identity top-ups, I need to specify which HD slot a top-up's credit-output key is derived from, so that:

  1. Wallet recovery from seed is deterministic — DIP-9's whole point. An SPV wallet restored from mnemonic can enumerate all asset-locks funding identity I by deterministic path scan (m/9'/coin'/5'/1'/I'/0, …/1, …/2, …) instead of relying on out-of-band metadata.
  2. Concurrent top-ups for the same identity are routable to distinct slots without races — caller-supplied topup_index gives deterministic routing regardless of thread interleave. On the critical path of e2e test AL-001 (concurrent asset-lock builds for the same wallet).
  3. Failed broadcasts can be retried idempotently — same topup_index re-derives the same credit-output key; today's sequential-cursor allocation leaks a key on every retry.
  4. Multi-party / accounting workflows can attach business meaning to indices — top-up 0 = initial seed, 1 = monthly fee, etc.

Current Behavior

On v0.42-dev HEAD 5297d61a, CreditOutputFunding at key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs:42-49 carries only identity_index: u32:

pub struct CreditOutputFunding {
    pub output: TxOut,
    pub funding_type: AssetLockFundingType,
    /// Identity index (only used for `IdentityTopUp`, ignored otherwise).
    pub identity_index: u32,
}

But the doc comment on AssetLockFundingType::IdentityTopUp (line 29 of the same file) describes the canonical derivation path with two trailing indices:

/// Identity top-up (bound to a specific identity): m/9'/coinType'/5'/1'/reg_index'/index'
IdentityTopUp,

The DIP-9 leaf path DerivationPath::identity_top_up_path(network, identity_index, top_up_index) at key-wallet/src/bip32.rs:1062 takes two u32 indices. The canonical surface accepts both; only the wiring through CreditOutputFunding to that leaf is missing.

Net effect: callers that want to fund successive top-ups for the same identity cannot dictate which HD slot each top-up consumes. The wallet's internal sequential cursor (next_private_key in managed_account_trait.rs:464) picks the next available slot regardless of caller intent.

Reproduction

Failing e2e test at dashpay/platform packages/rs-platform-wallet/tests/e2e/cases/found_006_topup_index_ignored.rs:317 (on branch feat/rs-platform-wallet-e2e, PR #3549):

  1. Register identity I via the address-funded register_from_addresses path.
  2. Pre-provision the IdentityTopUp { registration_index: 0 } HD account (wallet.add_account(...) — default initialisation does not create it).
  3. Pre-record addr[3] from the pool (default gap-limit pre-generates slots 0–4).
  4. Call top_up_identity_with_funding(I.id, FundWithWallet{...}, topup_index=0, ...) — sequential cursor consumes slot 0; both buggy and correct implementations agree here.
  5. Call top_up_identity_with_funding(I.id, FundWithWallet{...}, topup_index=3, ...) — the discriminating call.
  6. Inspect the asset-lock special-transaction payload's credit-output address.

Expected (correct routing): credit-output address == addr[3]
Actual (today): credit-output address == addr[1] — sequential cursor advanced exactly one slot regardless of topup_index=3

The assertion at found_006_topup_index_ignored.rs:317-326 fails with this mismatch; the failure is deterministic across every run.

Root Cause

  1. CreditOutputFunding.identity_index: u32 carries one of the two indices the DIP-9 derivation path requires. The second index has no field on the struct. (asset_lock_builder.rs:46-47)
  2. The doc comment on AssetLockFundingType::IdentityTopUp describes the path as m/9'/coinType'/5'/1'/reg_index'/index' — two trailing indices — but the type behind it carries only one. The doc and the type contradict each other. (asset_lock_builder.rs:29)
  3. The DIP-9 leaf identity_top_up_path(network, identity_index, top_up_index) already accepts both indices (bip32.rs:1062). What is missing is the field + wiring at the callsite (the asset-lock builder).
  4. Downstream consumers in dashpay/platform (e.g. packages/rs-platform-wallet/src/wallet/identity/network/top_up.rs:60-106) have a caller-supplied topup_index: u32 parameter that is currently prefixed _topup_index because there is no path to forward it through CreditOutputFunding. A TODO(platform-wallet) comment at line 64 of that file acknowledges the gap.

Affected Versions

  • v0.42-dev HEAD 5297d61a (today) — confirmed
  • All historic key-wallet releases (search returned 0 issues / 0 PRs touching CreditOutputFunding or top_up_index — this field has never existed)

Cross-Repository Impact

Blocks a dependent fix in dashpay/platform to expose topup_index routing through top_up_identity_with_funding. A follow-up issue will be filed against dashpay/platform referencing this one as a blocker.

No fix proposal in this issue. Adding the field is a public-API change on CreditOutputFunding; the wiring path through the asset-lock builder and BIP-32 derivation is left to the implementer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions