fix(chain): sanitize and bound-check derivation indices near BIP32_MAX_INDEX - #2265
fix(chain): sanitize and bound-check derivation indices near BIP32_MAX_INDEX#2265busayo-OD wants to merge 3 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #2265 +/- ##
==========================================
+ Coverage 78.71% 79.08% +0.37%
==========================================
Files 31 31
Lines 5966 6062 +96
Branches 282 282
==========================================
+ Hits 4696 4794 +98
Misses 1194 1194
+ Partials 76 74 -2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
evanlinjin
left a comment
There was a problem hiding this comment.
Thanks for the PR.
Given you have already started the work, we should probably also test:
- Given the derivation index is saturated,
next_unused_spkshould returnNoneandreveal_next_spkshould returnNone. - What happens if we use an index greater than
BIP32_MAX_INDEXforreveal_to_target. - What happens if we use
BIP32_MAX_INDEXforreveal_to_target. - Any method that takes an index value should be tested with
BIP32_MAX_INDEXand> BIP32_MAX_INDEX.
KeychainTxOutIndex::apply_changeset accepts ChangeSet::last_revealed values above BIP32_MAX_INDEX, violating an invariant relied on by the indexer. Clamp each last_revealed value to BIP32_MAX_INDEX before storing it, keeping changeset application infallible and monotone.
replenish_inner_index clamped stop_index (an exclusive bound) to BIP32_MAX_INDEX, excluding the boundary itself and causing a panic when a keychain's index was revealed exactly up to BIP32_MAX_INDEX. Clamp to BIP32_MAX_INDEX.saturating_add(1) instead, so the boundary value is included as documented.
lookahead_to_target added 1 to target_index without an overflow check, panicking when called with target_index == u32::MAX. Use checked_add/checked_sub so this is a no-op instead of a panic.
b4935c0 to
a04c57a
Compare
Thanks for the review. I’ve added the requested boundary tests. Two additional issues surfaced while adding them and have been fixed too. For saturation, the current behavior is to return the last revealed SPK with an empty |
| } | ||
| } | ||
| for (did, index) in changeset.last_revealed { | ||
| let index = index.min(BIP32_MAX_INDEX); |
There was a problem hiding this comment.
I propose adding debug_assert!(index < BIP32_MAX_INDEX) above this line so that overflows can be caught during development (it shouldn't really happen).
Fixes bdk_wallet/issues/60
Description
KeychainTxOutIndex::apply_changesetacceptedChangeSet::last_revealedvalues with no upper bound, allowing an index greater thanBIP32_MAX_INDEX(2^31 - 1) to be stored, violating an invariant relied on bynext_index().The changes in this PR address boundary issues around derivation indices:
apply_changeset: clampslast_revealedtoBIP32_MAX_INDEXbefore storing it, instead of storing the raw value.replenish_inner_index: fixes an off-by-one in thestop_indexcalculation that caused a panic ("we just inserted it") when a keychain's derivation index was revealed exactly up toBIP32_MAX_INDEX. The exclusive upper bound was clamped toBIP32_MAX_INDEXinstead ofBIP32_MAX_INDEX + 1, silently excluding the boundary value itself.lookahead_to_target: fixes an integer overflow panic ("attempt to add with overflow") when called withtarget_index == u32::MAX, by replacing uncheckedtarget_index + 1withchecked_add/checked_sub.Notes to the reviewers
This revisits the earlier implementation attempt (#1792) and implements the clamp-in-
apply_changesetapproach from that review discussion.The second and third fixes were found while adding the boundary tests requested in review of an earlier version of this PR.
Also added a test documenting current saturation behavior:
reveal_next_spk/next_unused_spkreturn the last revealed script with an emptyChangeSet, notNone, once the index is saturated. This matchesreveal_next_spk's existing doc comment.Went through the other index-taking methods (
spk_at_index,is_used,mark_used,unmark_used) as well. These are plain lookups with no boundary-sensitive logic, so no dedicated tests were added there.Changelog notice
last_revealedtoBIP32_MAX_INDEXinKeychainTxOutIndex::apply_changesetto prevent it accepting an out-of-range derivation index.replenish_inner_indexthat could panic when revealing up toBIP32_MAX_INDEX.lookahead_to_targetthat could panic when called withtarget_index == u32::MAX.Checklists
All Submissions
just pbefore committingBugfixes