wallet: relax external_signer flag constraints #33112

pull Sjors wants to merge 5 commits into bitcoin:master from Sjors:2025/07/external-signer-relax changing 8 files +57 βˆ’57
  1. Sjors commented at 10:11 am on August 1, 2025: member

    The external_signer indicates that an external signer device may be called via HWI or equivalent application.

    When it was initially introduced some additional constraints were placed on wallets with this flag: it had to be a descriptor wallet and watch-only. Also the flag could not be added or removed later.

    The constraints aren’t a problem for the main supported and documented use case of connecting a single hardware wallet and using it just like a normal single sig Bitcoin Core wallet.

    But they get in the way of more advanced constructions, which are becoming increasingly practical especially once MuSig2 support lands with #29675. See https://github.com/Sjors/bitcoin/pull/91 and https://github.com/bitcoin-core/HWI/pull/794 for a collection of changes on top of that which facilitate MuSig2 with a hardware wallet. And see #24861.

    This pull requests drops the following constraints:

    • disable_private_keys is no longer mandatory (but still default)
    • external_signer flag is now mutable

    Additionally it does the following:

    • make the blank option for createwallet consistent with regular wallets by not importing keys from the connected signer
    • avoid going through createTransaction for external signers (otherwise the GUI breaks)
    • update external-signer.md mainly to use named arguments and point out a GUI setting

    This should no noticeable effect on the default single sig use case described above.

  2. doc: improve external-signer.md
    - use bitcoin rpc instead of bitcoin-cli
    - use named "external_signer" argument
    - mention GUI setting
    36db95e7e8
  3. wallet: don't import external keys at creation if blank
    There's no need to treat external signer wallets different in this
    regard. When the user sets the 'blank' flag, don't generate or
    import keys.
    
    For multisig setups than involve an external signer, it may be useful
    to start from a blank wallet and manually import descriptors.
    2faf306123
  4. DrahtBot added the label Wallet on Aug 1, 2025
  5. DrahtBot commented at 10:11 am on August 1, 2025: contributor

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

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33112.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK rkrux

    If your review is incorrectly listed, please react with πŸ‘Ž to this comment and the bot will ignore it on the next update.

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #33043 ([POC] wallet: Enable non-electronic (paper-based) wallet backup with codex32 by w0xlt)
    • #32966 (Silent Payments: Receiving by Eunovo)
    • #30354 (doc: external-signer, example ‘createwallet’ RPC call requires eight argument by cobratbq)
    • #28333 (wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively by achow101)

    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.

  6. fanquake commented at 10:31 am on August 1, 2025: member

    https://cirrus-ci.com/task/6572167942373376?logs=ci#L1621:

    0[06:14:38.327] /ci_container_base/src/wallet/rpc/wallet.cpp: In function β€˜wallet::createwallet()::<lambda(const RPCHelpMan&, const JSONRPCRequest&)>’:
    1[06:14:38.327] /ci_container_base/src/wallet/rpc/wallet.cpp:420:45: error: β€˜*(unsigned char*)((char*)&disable_private_keys + offsetof(std::optional<bool>,std::optional<bool>::<unnamed>.std::_Optional_base<bool, true, true>::<unnamed>))’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
    2[06:14:38.327]   420 |     if (disable_private_keys.has_value() && *disable_private_keys) {
    3[06:14:38.327]       |                                             ^~~~~~~~~~~~~~~~~~~~~
    4[06:14:38.327] cc1plus: all warnings being treated as errors
    5[06:14:38.328] gmake[2]: *** [src/wallet/CMakeFiles/bitcoin_wallet.dir/build.make:370: src/wallet/CMakeFiles/bitcoin_wallet.dir/rpc/wallet.cpp.o] Error 1
    
  7. in src/wallet/rpc/wallet.cpp:383 in dee72e7419 outdated
    378@@ -379,9 +379,8 @@ static RPCHelpMan createwallet()
    379 {
    380     WalletContext& context = EnsureWalletContext(request.context);
    381     uint64_t flags = 0;
    382-    if (!request.params[1].isNull() && request.params[1].get_bool()) {
    383-        flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
    384-    }
    385+
    386+    std::optional<bool> disable_private_keys = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
    


    maflcko commented at 11:01 am on August 1, 2025:
    nit: Would be good to use named args, while touching this: self.MaybeArg<bool>("disable_private_keys")

    Sjors commented at 11:07 am on August 1, 2025:
    That looks much nicer indeed.
  8. in src/wallet/rpc/wallet.cpp:420 in dee72e7419 outdated
    415 #else
    416         throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
    417 #endif
    418     }
    419 
    420+    if (disable_private_keys.has_value() && *disable_private_keys) {
    


    maflcko commented at 11:02 am on August 1, 2025:
    not sure if this fixes the maybe-uninitialized false-positive from gcc, but you can try if (disable_private_keys.has_value() && disable_private_keys.value()) {, or add -Wno-error=maybe-uninitialized to the ci task config.

    Sjors commented at 11:48 am on August 1, 2025:
    I’ll try value_or(false)
  9. Sjors force-pushed on Aug 1, 2025
  10. DrahtBot added the label CI failed on Aug 1, 2025
  11. DrahtBot commented at 11:49 am on August 1, 2025: contributor

    🚧 At least one of the CI tasks failed. Task previous releases, depends DEBUG: https://github.com/bitcoin/bitcoin/runs/47191506122 LLM reason (✨ experimental): The CI failed due to a compilation error caused by treating warnings as errors, specifically an uninitialized variable warning in wallet.cpp.

    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.

  12. Sjors force-pushed on Aug 1, 2025
  13. Sjors commented at 12:56 pm on August 1, 2025: member
    Added 7beb338a0d4343a622236876c7d63d56bf7039e3 wallet: avoid createTransaction() with signer to prevent breaking GUI signing for private key enabled external signer wallets (even when they don’t have other keys).
  14. wallet: avoid createTransaction() with signer
    External signer enabled wallets should always use the process PSBT flow.
    Avoid going through CreateTransaction.
    
    This has no effect until the next commit when WALLET_FLAG_EXTERNAL_SIGNER
    no longer implies WALLET_FLAG_DISABLE_PRIVATE_KEYS. Without this change
    signing with the GUI would break for external signers with private keys
    enabled.
    7beb338a0d
  15. wallet: make watch-only optional for external signer
    Before this change the external_signer flag required the wallet to be watch-only.
    This precludes multisig setups in which we hold a hot key.
    
    Remove this as a requirement, but disable private keys by default. This leaves
    the typical (and only documented) use case of a single external signer unaffected.
    08f7813f53
  16. wallet: make external_signer flag mutable
    With the removal of legacy wallets and the relaxing of restrictions
    in the previous commit, it's no longer a problem to toggle this flag.
    a9734039a7
  17. Sjors force-pushed on Aug 1, 2025
  18. DrahtBot removed the label CI failed on Aug 1, 2025
  19. in src/wallet/wallet.cpp:2881 in 2faf306123 outdated
    2880@@ -2881,7 +2881,13 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
    2881         // Only descriptor wallets can be created
    


    rkrux commented at 2:00 pm on August 11, 2025:

    In 2faf30612350fd90d9d3c44d1bb7b055addc8331 “wallet: don’t import external keys at creation if blank”

    Nit in commit message:

    0- For multisig setups than involve an external signer
    1+ For multisig setups that involve an external signer 
    
  20. in src/qt/walletmodel.cpp:203 in 7beb338a0d outdated
    202@@ -203,7 +203,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact
    203         int nChangePosRet = -1;
    


    rkrux commented at 2:12 pm on August 11, 2025:

    In 7beb338a0d4343a622236876c7d63d56bf7039e3 “wallet: avoid createTransaction() with signer”

    Nit in commit message:

    0- wallet: avoid createTransaction() with signer
    1+ wallet: avoid signing via createTransaction() with external signer
    
  21. in test/functional/wallet_signer.py:68 in 08f7813f53 outdated
    64@@ -65,23 +65,22 @@ def run_test(self):
    65     def test_valid_signer(self):
    66         self.log.debug(f"-signer={self.mock_signer_path()}")
    67 
    68-        # Create new wallets for an external signer.
    


    rkrux commented at 2:18 pm on August 11, 2025:

    In 08f7813f536c242b7a4b65d01cfbc73601846a25 “wallet: make watch-only optional for external signer”

    This comment can stay?

  22. in test/functional/wallet_signer.py:76 in 08f7813f53 outdated
    76+        # Private keys are disabled by default
    77+        assert_equal(hww.getwalletinfo()["private_keys_enabled"], False)
    78+
    79         # Flag can't be set afterwards (could be added later for non-blank descriptor based watch-only wallets)
    80-        self.nodes[1].createwallet(wallet_name='not_hww', disable_private_keys=True, external_signer=False)
    81+        self.nodes[1].createwallet(wallet_name='not_hww', external_signer=False)
    


    rkrux commented at 2:31 pm on August 11, 2025:

    In 08f7813f536c242b7a4b65d01cfbc73601846a25 “wallet: make watch-only optional for external signer”

    Do you intend to add a test for an external signer wallet with private keys enabled in a later PR?

  23. rkrux commented at 2:34 pm on August 11, 2025: contributor

    ACK a9734039a7a34e38145927f02b891685f96ab9e8

    Agree with the intent to relax these constraints for external signer wallets.


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: 2025-08-13 06:13 UTC

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