KeychainTxOutIndex::lookahead_to_target panics (overflow) or silently no-ops on target_index = u32::MAX

Open Beginner friendly
#2,251 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
86/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend

Research direction

Start in crates/chain/src/indexer/keychain_txout.rs at lookahead_to_target and compare its boundary handling with reveal_to_target. Add the u32::MAX regression case in crates/chain/tests/test_keychain_txout_index.rs, following the existing table-driven tests, then run the specified cargo test command. Done means the boundary case neither panics nor silently skips the requested lookahead.

Written by the indexing model from the issue text.

Description

bug

Describe the bug
KeychainTxOutIndex::lookahead_to_target computes target_index + 1 without overflow protection (crates/chain/src/indexer/keychain_txout.rs, line 543):

let temp_lookahead = (target_index + 1)
    .checked_sub(next_index)
    .filter(|&index| index > 0);

When target_index == u32::MAX:

  • In a debug build (default cargo test/cargo build, overflow-checks on): panics with "attempt to add with overflow".
  • In a release build (overflow-checks off by default): u32::MAX + 1 silently wraps to 0, so checked_sub/filter evaluate to None and the function silently does nothing — no error, no scripts derived, no indication to the caller that the call had no effect.

target_index is a raw public API parameter with no upstream validation, so it could plausibly come from untrusted input (e.g. a derivation index parsed from an external PSBT).

For comparison, the sibling method reveal_to_target handles the same boundary safely — it only ever compares indices (i > target_index) rather than doing arithmetic on the raw value. lookahead_to_target has no equivalent protection.

To Reproduce
Add this test to crates/chain/tests/test_keychain_txout_index.rs:

#[test]
fn repro_lookahead_to_target_overflow() {
    let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
    let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
    let mut index = init_txout_index(external_descriptor, internal_descriptor, 10, true);
    let _ = index.lookahead_to_target(TestKeychain::External, u32::MAX);
}

Run:

cargo test --features miniscript --test test_keychain_txout_index repro_lookahead_to_target_overflow -- --nocapture

Actual output:

thread 'repro_lookahead_to_target_overflow' panicked at crates/chain/src/indexer/keychain_txout.rs:543:34:
attempt to add with overflow

Expected behavior
No panic in debug builds, and no silent no-op in release builds. The lookahead should either be extended as far as representable (e.g. via target_index.saturating_add(1) instead of target_index + 1), or the call should visibly report that the target was unreachable — not fail silently.

Proposed fix
Use saturating_add instead of raw + — the same pattern is already used a few lines below in the same function (line 606: *_i = spk_i.saturating_add(1);), so this just makes the function internally consistent:

let temp_lookahead = target_index
    .saturating_add(1)
    .checked_sub(next_index)
    .filter(|&index| index > 0);

This preserves existing behavior for all valid inputs and makes the u32::MAX boundary case degrade gracefully instead of panicking or silently no-opping. A regression test with target_index = u32::MAX (and ideally BIP32_MAX_INDEX, the actual max valid BIP-32 derivation index) should be added alongside the fix, following the existing table-driven test style already used for lookahead_to_target in test_keychain_txout_index.rs.

Dominant language
Rust
Stars
1.1k
Forks
491
Avg merge
1d 5h
Merged PRs (30d)
1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from bitcoindevkit/bdk

All issues in bitcoindevkit/bdk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.