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❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2265 +/- ##
==========================================
+ Coverage 78.71% 79.05% +0.34%
==========================================
Files 31 31
Lines 5966 6054 +88
Branches 282 284 +2
==========================================
+ Hits 4696 4786 +90
+ Misses 1194 1193 -1
+ Partials 76 75 -1
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.
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 |
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.
a04c57a to
713586e
Compare
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. The addition feeding into that clamp was also unchecked, risking overflow if lookahead was large relative to an already-high last_revealed. Use saturating_add throughout, clamping to BIP32_MAX_INDEX + 1 so the boundary value is included as documented.
lookahead_to_target treated an out-of-range target_index as a silent no-op, which is wrong — it should clamp to BIP32_MAX_INDEX and replenish up to that point, matching reveal_to_target's best-effort semantics. Use saturating_add/saturating_sub, which no longer needs checked arithmetic since replenish_inner_index's stop_index now clamps correctly on its own.
713586e to
1ca8d29
Compare
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 when called withtarget_index == u32::MAX, and changes out of range targets to clamp and replenish up toBIP32_MAX_INDEXinstead of silently doing nothing.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_targetwhen called withtarget_index == u32::MAX, and changed out of range targets to clamp toBIP32_MAX_INDEXinstead of silently doing nothing.Checklists
All Submissions
just pbefore committingBugfixes