fix: prevent cumulative change-address index leak in dry-run tx builds#72
Merged
ovitrif merged 1 commit intofix/dsym-debug-symbolsfrom Mar 4, 2026
Merged
Conversation
This was referenced Mar 4, 2026
ovitrif
approved these changes
Mar 4, 2026
Collaborator
ovitrif
left a comment
There was a problem hiding this comment.
LGTM.
I will rebuild bindings. Probably can revert to rc.31, since my original release needs to be recreated, as it missed a point.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TxBuilder::finish()being called for fee estimation / dry-run purposes without cancelling the resulting PSBT. Each uncancelledfinish()permanently burns a change address index.AggregateWallet::cancel_dry_run_tx()to unmark change addresses on the primary wallet without persisting, and call it on all dry-run and error-after-finish()paths.Detail
BDK's
TxBuilder::finish()has two side-effects on the internal (change) keychain: it reveals a new derivation index and marks it as "used". When the PSBT is only used for fee estimation (never signed/broadcast), the marked address is permanently consumed. Repeated fee estimations steadily burn through the change address keyspace — a problem for wallet recovery (gap-limit), resource efficiency, and address hygiene.cancel_dry_run_txcalls BDK'scancel_txon the primary wallet to reverse the "used" marking so the nextfinish()reuses the same index. The reveal itself persists harmlessly in the staged changeset.Call sites fixed
build_transaction_psbt— AllRetainingReserve temp txcancel_txmoved before the?oncalculate_feeso it fires unconditionallybuild_transaction_psbt— reserve checkscancel_dry_run_txbefore returningcalculate_transaction_feecancel_dry_run_txfires unconditionally before fee result is unwrappedselect_confirmed_utxoscancel_dry_run_txfires after UTXO collection, before returnSafety
No change-address reuse between broadcast transactions is possible:
Mutex<AggregateWallet>serializes all wallet accesscancel_dry_run_txis never called on the broadcast path (send_to_address)finish()re-marks the address as "used" and persists itTest plan
test_finish_without_cancel_leaks_change_index— demonstrates the bugtest_cancel_dry_run_prevents_cumulative_index_leak— proves the fix across 5 iterationstest_cancel_after_failed_intermediate_prevents_leak— error-path coveragetest_dry_run_cancel_then_real_tx_reuses_change_address— real tx after dry-runcargo build/cargo fmt/cargo clippyclean