wallet: rpc: Deprecate `encryptwallet` RPC for encrypting existing wallets. #36387

pull davidgumberg wants to merge 8 commits into bitcoin:master from davidgumberg:2026-09-29-deprecate-encryptwallet changing 18 files +150 −73
  1. davidgumberg commented at 2:11 AM on September 30, 2026: contributor

    For more context, see: #36027

    Encrypting existing wallets that are unencrypted is fraught with footguns and complexity both for users and for developers.

    For example:

    • No efforts are made to ensure that remnants of unencrypted key material are not left on the user's disk, as attempting to ensure this would be difficult and dangerous, and for some SSD's infeasible.

    • Encrypting a wallet that has manually imported descriptors will cause the wallet to change the spending conditions out from under the user. (https://github.com/bitcoin/bitcoin/issues/26607)

    • Because new keys are made after encryption, restoring from a backup made before encryption may result in loss of funds.

    LLM Disclosure: Used gemma4-26B and qwen3.8-next-flash as research and drafting tools, but all code and text present here were written by me.

  2. refactor: test: Avoid encryptwallet in interface_bitcoin_cli.py
    This RPC will be deprecated in a future commit, so tests should avoid
    relying on it. This commit also adds scaffolding to init_wallet that
    will be used when refactoring other tests.
    645ea8b9b2
  3. refactor: test: rpc_psbt.py extract encrypted wallet signing
    This improves legibility of the test and avoids the use of
    `encryptwallet` which will be deprecated.
    2587fe96a8
  4. refactor: test: wallet_bumpfee.py isolate encrypted wallet tests f5f099eb58
  5. refactor: test: wallet_fundrawtransaction.py avoid encryptwallet
    A later commit will deprecate this RPC.
    0af48177dd
  6. refactor: test: wallet_listdescriptors.py extract encrypted wallet test 2c247497ff
  7. test: refactor: wallet_multiwallet.py use a new encrypted wallet d6aebe0ebc
  8. refactor: test: signrawtransaction isolate encrypted wallet test
    This is useful since a future commit will deprecate `encryptwallet`.
    df3ac8100e
  9. wallet: rpc: Deprecate `encryptwallet`
    Encrypting wallets from creation will still be supported, but there are
    footguns both for users and for developers in supporting wallets that
    are created unencrypted and then later become encrypted.
    
    The functional tests where the `deprecatedrpc` argument is added are
    ones where functionality or behavior of `encryptwallet` is being tested,
    they should be kept until the RPC command is removed.
    2cc0946985
  10. DrahtBot added the label Wallet on Sep 30, 2026
  11. DrahtBot commented at 2:11 AM on September 30, 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 pseudoramdom, w0xlt, pablomartin4btc

    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.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36488 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36488.svg"></sub> (rpc: wallet: Schedule mixed-case confTarget option deprecation by maflcko)
    • #36257 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36257.svg"></sub> (qa: assert_equals -> assert_true/assert_false by hodlinator)
    • #33954 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/33954.svg"></sub> (test: add functional test for outbound connection management by mzumsande)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  12. pseudoramdom commented at 5:00 AM on September 30, 2026: contributor

    Concept ACK, will review when I get a chance.

  13. w0xlt commented at 5:33 PM on September 30, 2026: contributor

    Concept ACK

  14. in doc/release-notes-deprecateencryptwallet.md:1 in 2cc0946985


    pablomartin4btc commented at 10:36 PM on October 20, 2026:

    nit: filename convention is release-notes-36387.md.

  15. in test/functional/test_framework/test_framework.py:418 in 2cc0946985
     418 | +                with WalletUnlock(w, self.default_wallet_pass):
     419 | +                    wallet_importprivkey(n.get_wallet_rpc(wallet_name), n.get_deterministic_priv_key().key, 0, label="coinbase")
     420 | +            else:
     421 |                  n.createwallet(wallet_name=wallet_name, load_on_startup=True)
     422 | -            wallet_importprivkey(n.get_wallet_rpc(wallet_name), n.get_deterministic_priv_key().key, 0, label="coinbase")
     423 | +                wallet_importprivkey(n.get_wallet_rpc(wallet_name), n.get_deterministic_priv_key().key, 0, label="coinbase")
    


    pablomartin4btc commented at 11:44 PM on October 20, 2026:

    nit: the two branches only differ in the passphrase and the unlock around the import, so they could perhaps be collapsed. Also w is assigned but then get_wallet_rpc is called again for wallet_importprivkey, so w could be reused there.

    Since this drops the if wallet_name is not None: check, the wallet_names comment could also drop "If wallet names are None, wallet creation is skipped.". That case has been broken since #32452 (fcc457573f) anyway (get_wallet_rpc(None) would rase in urllib.parse.quote).

                passphrase_args = {"passphrase": self.default_wallet_pass} if encrypted else {}
                n.createwallet(wallet_name=wallet_name, load_on_startup=True, **passphrase_args)
                w = n.get_wallet_rpc(wallet_name)
                with WalletUnlock(w, self.default_wallet_pass) if encrypted else contextlib.nullcontext():
                    wallet_importprivkey(w, n.get_deterministic_priv_key().key, 0, label="coinbase")                
    

    (needs import contextlib; tested on top of 2cc0946985 with the full extended functional suite.)

  16. in test/functional/test_framework/test_framework.py:48 in 2cc0946985
      43 | @@ -44,6 +44,9 @@
      44 |      JSONRPCException,
      45 |  )
      46 |  
      47 | +from .wallet_util import WalletUnlock
      48 | +
    


    pablomartin4btc commented at 11:49 PM on October 20, 2026:

    nit: extra blank line here.

  17. in test/functional/wallet_gethdkeys.py:23 in 2cc0946985
      18 | @@ -19,6 +19,8 @@ class WalletGetHDKeyTest(BitcoinTestFramework):
      19 |      def set_test_params(self):
      20 |          self.setup_clean_chain = True
      21 |          self.num_nodes = 1
      22 | +        self.extra_args = [["-deprecatedrpc=encryptwallet"]]
      23 | +
    


    pablomartin4btc commented at 11:49 PM on October 20, 2026:

    nit: extra blank line here.

  18. in test/functional/wallet_encryption.py:24 in 2cc0946985
      19 | @@ -20,6 +20,8 @@ class WalletEncryptionTest(BitcoinTestFramework):
      20 |      def set_test_params(self):
      21 |          self.setup_clean_chain = True
      22 |          self.num_nodes = 1
      23 | +        self.extra_args = [["-deprecatedrpc=encryptwallet"]]
      24 | +
    


    pablomartin4btc commented at 11:49 PM on October 20, 2026:

    nit: extra blank line here.

  19. in test/functional/wallet_listdescriptors.py:49 in 2cc0946985
      44 | +        self.log.info('- With private=true, errors when the wallet is locked.')
      45 | +        assert_raises_rpc_error(-13, 'Please enter the wallet passphrase with walletpassphrase first.', encrypted_wallet.listdescriptors, True)
      46 | +        encrypted_wallet.walletpassphrase(passphrase=self.default_wallet_pass, timeout=self.rpc_timeout)
      47 | +        encrypted_wallet.listdescriptors(True)
      48 | +        encrypted_wallet.unloadwallet()
      49 | +
    


    pablomartin4btc commented at 11:51 PM on October 20, 2026:

    nit: extra blank line here.

  20. in test/functional/wallet_listdescriptors.py:22 in 2cc0946985
      16 | @@ -17,7 +17,10 @@
      17 |      assert_equal,
      18 |      assert_raises_rpc_error,
      19 |  )
      20 | -from test_framework.wallet_util import generate_keypair
      21 | +from test_framework.wallet_util import (
      22 | +        WalletUnlock,
      23 | +        generate_keypair
    


    pablomartin4btc commented at 12:06 AM on October 21, 2026:

    nit: import block indented 8 spaces (and missing trailing comma after generate_keypair).

        WalletUnlock,
        generate_keypair,
    
  21. in test/functional/wallet_listdescriptors.py:47 in 2cc0946985
      42 | +            assert_equal(encrypted_locked_output, encrypted_wallet.listdescriptors())
      43 | +
      44 | +        self.log.info('- With private=true, errors when the wallet is locked.')
      45 | +        assert_raises_rpc_error(-13, 'Please enter the wallet passphrase with walletpassphrase first.', encrypted_wallet.listdescriptors, True)
      46 | +        encrypted_wallet.walletpassphrase(passphrase=self.default_wallet_pass, timeout=self.rpc_timeout)
      47 | +        encrypted_wallet.listdescriptors(True)
    


    pablomartin4btc commented at 12:59 AM on October 21, 2026:

    nit: the previous version of the test compared the private output against expected_private and now the wallet is left unlocked until its unload.

             self.log.info("- With private=true, works when unlocked and matches the public descriptors.")
             with WalletUnlock(encrypted_wallet, self.default_wallet_pass):
                 private_output = encrypted_wallet.listdescriptors(True)
             public_descs = [d["desc"] for d in encrypted_locked_output["descriptors"]]
             private_descs = [d["desc"] for d in private_output["descriptors"]]
             assert all("tprv" in d for d in private_descs)
             assert_equal([self.nodes[0].deriveaddresses(d, [0, 2]) for d in private_descs],
                          [self.nodes[0].deriveaddresses(d, [0, 2]) for d in public_descs])
    
  22. in test/functional/wallet_bumpfee.py:801 in 2cc0946985
     801 | +                            encrypted_wallet.bumpfee, rbfid)
     802 | +
     803 | +    self.log.info('Test that an unlocked wallet can bump txn')
     804 | +    with WalletUnlock(encrypted_wallet, self.default_wallet_pass):
     805 | +        bumpfee_res = encrypted_wallet.bumpfee(rbfid)
     806 | +    assert bumpfee_res["errors"] == []
    


    pablomartin4btc commented at 1:03 AM on October 21, 2026:

    nit:

        assert_equal(bumpfee_res["errors"], [])
    
  23. in test/functional/rpc_deprecated.py:43 in 2cc0946985
      39 | @@ -40,6 +40,12 @@ def run_test(self):
      40 |                  "fakeargument"
      41 |              )
      42 |  
      43 | +            assert_raises_rpc_error(
    


    pablomartin4btc commented at 1:05 AM on October 21, 2026:

    nit (for consistency with the removeprunedfunds check above)...

                self.log.info("Test encryptwallet deprecation")
                assert_raises_rpc_error(
    
  24. pablomartin4btc commented at 1:15 AM on October 21, 2026: member

    Concept ACK

    I think encryptwallet RPC was created before the createwalllet gained the passphrase, and for what it worth creating today a new encrypted wallet and sendall-ing to it is equivalent and cleaner.

    The GUI "Encrypt Wallet…" action (WalletView::encryptWallet → interfaces::Wallet::encryptWallet) is unaffected and is probably where most users hit this. It might be worth mentioning in the description and release notes so the GUI and RPC deprecations ship in the same release (?).

    Left some nits.


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-10-27 10:51 UTC

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