wallet, sqlite: Enable secure_delete by default #36796

pull achow101 wants to merge 4 commits into bitcoin:master from achow101:raii-sqlite-secure-delete changing 7 files +51 −23
  1. achow101 commented at 2:13 AM on November 7, 2026: member

    Currently, when we make deletions or updates to the database that require records to be fully deleted and to not persist in free pages afterwards, we need to explicitly call WalletDatabase::Rewrite. This behavior was introduced in #635 to ensure that unencrypted private keys are erased from the database after encrypting a wallet, and initial encryption is the only consumer of this function.

    However, initial encryption is not the only functionality that requires secure deletion. l0rinc's LLMs recently found changing the passphrase can leave the original that encryption key record in free pages. We should want to ensure that those records are also deleted, although I do not consider that the presence of such records in free space to actually be a problem. Note that this behavior appears to be present since wallet encryption was initially introduced in #352.

    Fortunately, SQLite offers a secure_delete flag that can be enabled on each connection to a database. If set, all deletes or updates will ensure that any data that was deleted or overwritten actually are overwritten with 0's. It is one of the 2 methods in SQLite's documentation for ensuring that records do not persist in free pages, the other method being to call VACUUM after deletions or updates, which is what Rewrite does.

    The downside is that secure_delete will increase CPU and disk I/O, which is why this was not set previously. However, it seems like several distros ship with secure_delete enabled by default: Debian, Ubuntu, Fedora, Arch, and NixOS all set SQLite's compile time option to default secure_delete to enabled, and these are just the ones I've looked at. This suggests to me that the performance penalty is not actually that bad. As I run Arch and often do not use depends builds, I had not noticed that secure_delete was always on and did not experience any particularly poor wallet performance that could be attributed to that behavior. In the grand scheme of things, erases and updates are fairly infrequent in the wallet.

    This PR enables secure_delete when the database is opened, and also enables it by default in the sqlite depends package. There are also a couple tests to check that all records we want deleted actually are being deleted from the database. WalletDatabase::Rewrite is removed as it it is no longer used.

  2. DrahtBot commented at 2:13 AM on November 7, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Concept ACK fametrano

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. achow101 force-pushed on Nov 7, 2026
  4. DrahtBot added the label CI failed on Nov 7, 2026
  5. DrahtBot commented at 2:23 AM on November 7, 2026: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 At least one of the CI tasks failed. <sub>Task lint: https://github.com/bitcoin/bitcoin/actions/runs/42328113449/job/128268068670</sub> <sub>LLM reason (✨ experimental): CI failed because Python lint found six unnecessary semicolons in test/functional/wallet_encryption.py.</sub>

    <details><summary>Hints</summary>

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

    </details>

  6. test, wallet: Check unencrypted private keys are securely deleted 39a9b7ad1a
  7. achow101 force-pushed on Nov 7, 2026
  8. DrahtBot removed the label CI failed on Nov 7, 2026
  9. fametrano commented at 8:03 AM on November 7, 2026: contributor

    Concept ACK

    For a wallet created at this head, no copy of the plaintext key stays in wallet.dat-journal after encryptwallet, while the wallet is loaded or after a kill -9.

    I ran encryptwallet on a fresh descriptor wallet at 96352f5200 and counted copies of its 32-byte xprv private key in its files. I linked bitcoind against Homebrew SQLite and against depends, both leaving secure_delete off by default, so only the pragma this PR adds can zero the pages. wallet.dat and the journal held 0 copies while loaded, after a kill -9, and after a restart and reload. The same run on the head of #36793, which still relies on VACUUM, left 6 copies in the journal, 1 after the restart.

    The script, builds and output are in https://gist.github.com/fametrano/245e7d7aa2a4762d2ce7c102e097b92f/7cf068b0300062264dce40a1838c98d4958504b0.

  10. wallet, sqlite, depends: Enable secure_delete af565a9198
  11. test, wallet: Check that rotated mkeys are deleted e669303414
  12. wallet, db: Remove Rewrite
    This function is no longer necessary.
    38c28393cb
  13. in depends/packages/sqlite.mk:16 in 96352f5200
      12 | @@ -13,7 +13,7 @@ $(package)_config_opts_debug += --debug
      13 |  $(package)_cppflags += -DSQLITE_DQS=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_OMIT_DEPRECATED
      14 |  $(package)_cppflags += -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_JSON -DSQLITE_LIKE_DOESNT_MATCH_BLOBS
      15 |  $(package)_cppflags += -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_AUTOINIT
      16 | -$(package)_cppflags += -DSQLITE_OMIT_LOAD_EXTENSION
      17 | +$(package)_cppflags += -DSQLITE_OMIT_LOAD_EXTENSION -DQLITE_SECURE_DELETE
    


    ValeraFinebits commented at 8:53 AM on November 7, 2026:
    $(package)_cppflags += -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_SECURE_DELETE
    

    Looks like there's a typo here.


    achow101 commented at 9:01 AM on November 7, 2026:

    Oops fixed

  14. achow101 force-pushed on Nov 7, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-11-07 14:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me