Skip to content

Conversation

@knst
Copy link
Collaborator

@knst knst commented May 14, 2024

Issue being fixed or feature implemented

This PR introduce support of external signers (for example, hardware signers such as Ledger) which could be used to sign transactions, using existing feature PSBT.

What was done?

Bitcoin backports from Bitcoin Core v22, v23, v24 to support hardware wallets in Dash Core.

This PR is not the smallest possible set of backports, but it is set that provide working external signer in both CLI and GUI versions of Dash Core. Extra backports to improve user experience are to be done in the next PRs, see #6913

How Has This Been Tested?

Got successfully signed transaction using a repo https://github.com/knst/hwi/ (WIP, see also knst/HWI#1)

Breaking Changes

It introduces multiple new RPC, doesn't seems as any really breaking change

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation
  • I have assigned this pull request to a milestone (for repository code-owners and collaborators only)

@github-actions
Copy link

This pull request has conflicts, please rebase.

@github-actions
Copy link

This pull request has conflicts, please rebase.

@github-actions
Copy link

github-actions bot commented Apr 1, 2025

This pull request has conflicts, please rebase.

@DashCoreAutoGuix
Copy link

❌ Backport Verification - CATASTROPHIC FAILURE

Original Bitcoin commit: multiple (no specific reference found)
Reviewed commit hash: -verify-

Critical violations detected:

  • Size Explosion: 1,156 files changed (32,244 insertions, 31,996 deletions) - far exceeds 500% threshold for any reasonable backport
  • Catastrophic CI Status: 5 failures out of 29 total checks across multiple platforms
  • Infrastructure Import: This appears to be a wholesale import of hardware wallet infrastructure rather than focused backporting
  • Missing Bitcoin Reference: No specific Bitcoin commit identified for comparison
  • Merge Conflicts: Multiple conflicts requiring manual rebase

This PR violates fundamental backporting principles:

  1. Scope Explosion: A proper backport should be proportional to the original Bitcoin change
  2. Infrastructure Import: Importing entire systems rather than specific features
  3. CI Stability: Multiple build failures indicate architectural incompatibilities

Recommendation: Create focused, smaller PRs that backport specific Bitcoin commits for hardware wallet functionality, following the established backporting process with:

  • Clear Bitcoin commit references
  • Proportional change scope (typically <100 files for infrastructure changes)
  • Clean CI status before submission
  • Proper conflict resolution

This PR has been automatically closed due to catastrophic validation failures.

@DashCoreAutoGuix
Copy link

Automatically closed due to catastrophic validation failures. Please see the detailed analysis above and create focused PRs with proper Bitcoin commit references and proportional scope.

@github-actions
Copy link

github-actions bot commented Oct 1, 2025

✅ No Merge Conflicts Detected

This PR currently has no conflicts with other open PRs.

@github-actions
Copy link

github-actions bot commented Oct 4, 2025

This pull request has conflicts, please rebase.

@github-actions
Copy link

This pull request has conflicts, please rebase.

@knst knst marked this pull request as ready for review October 20, 2025 08:29
@knst knst changed the title feat(wallet): hardware signing feat(wallet): external signer (hardware signer) Oct 20, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 20, 2025

Walkthrough

Replaces build-time Boost.Process option with an opt-in ENABLE_EXTERNAL_SIGNER feature. Adds ExternalSigner API (header + implementation), Node/Wallet interface extensions, wallet integration (WALLET_FLAG_EXTERNAL_SIGNER, ExternalSignerScriptPubKeyMan, displayAddress/FillPSBT flows), RPCs (enumeratesigners, walletdisplayaddress), Qt UI wiring (options, create wallet, receive/send dialogs), tests and mocks, build/config updates (configure.ac, Makefiles, CI scripts), documentation, new error codes/DB enum for external-signer support, and exposes RunCommandParseJSON unconditionally while gating its implementation behind ENABLE_EXTERNAL_SIGNER.

Sequence Diagram(s)

sequenceDiagram
    %% Styling: highlight external signer interaction
    participant UI as Qt UI
    participant Model as WalletModel
    participant Wallet as CWallet
    participant SPKM as ExternalSignerScriptPubKeyMan
    participant Sys as RunCommandParseJSON
    participant Ext as External Signer (script)

    UI->>Model: user triggers send / verify
    Model->>Wallet: create PSBT (sign=false) / display request
    Wallet->>SPKM: FillPSBT / DisplayAddress
    alt ENABLE_EXTERNAL_SIGNER && signer configured
        SPKM->>Sys: RunCommandParseJSON("enumerate"/"displayaddress"/"signtx", stdin=PSBT/params)
        Sys->>Ext: spawn process, write stdin
        Ext-->>Sys: stdout JSON
        Sys-->>SPKM: parsed UniValue or error
        SPKM->>Wallet: signed PSBT / address confirmation or error
        Wallet->>Model: finalized tx / result
        Model->>UI: broadcast or show PSBT/save prompt or success
    else signer unavailable or feature not compiled
        SPKM-->>Wallet: return error / throw runtime_error
        Wallet->>Model: propagate error
        Model->>UI: show configuration/enable guidance
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas needing focused review:

  • External signer subprocess, JSON parsing and error handling: src/external_signer.{h,cpp}, src/util/system.{h,cpp}
  • Wallet PSBT integration and descriptor/scriptpubkey manager changes: src/wallet/{external_signer_scriptpubkeyman.,wallet.,scriptpubkeyman.h,walletdb.*}
  • Interface exposure and Node wrapping: src/interfaces/{node.h,wallet.h}, src/node/interfaces.cpp
  • RPC and command registration: src/rpc/{external_signer.cpp,client.cpp,register.h,wallet/*.cpp}
  • Qt UI and model wiring: src/qt/{createwalletdialog.,optionsdialog.,receiverequestdialog.,sendcoinsdialog.,optionsmodel.*}
  • Build/config and tests: configure.ac, src/Makefile.am, doc/external-signer.md, Doxyfile.in, ci/test/, and test/functional/ (mocks and new tests)

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.15% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature added: external signer support for hardware wallets, which is the primary purpose of this changeset.
Description check ✅ Passed The description adequately explains the feature being implemented (external signer support for hardware wallets), references the Bitcoin Core backports being adapted, notes testing performed, and acknowledges this is part of a larger effort.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 444a8d0 and 013953b.

📒 Files selected for processing (17)
  • ci/test/00_setup_env_arm.sh (1 hunks)
  • ci/test/00_setup_env_mac.sh (1 hunks)
  • ci/test/00_setup_env_mac_native_x86_64.sh (1 hunks)
  • ci/test/00_setup_env_native_asan.sh (1 hunks)
  • ci/test/00_setup_env_native_fuzz.sh (1 hunks)
  • ci/test/00_setup_env_native_multiprocess.sh (1 hunks)
  • ci/test/00_setup_env_native_nowallet.sh (1 hunks)
  • ci/test/00_setup_env_native_qt5.sh (1 hunks)
  • ci/test/00_setup_env_native_tsan.sh (1 hunks)
  • ci/test/00_setup_env_s390x.sh (1 hunks)
  • ci/test/00_setup_env_win64.sh (1 hunks)
  • configure.ac (7 hunks)
  • doc/Doxyfile.in (1 hunks)
  • doc/external-signer.md (1 hunks)
  • src/Makefile.am (5 hunks)
  • src/Makefile.test.include (1 hunks)
  • src/dummywallet.cpp (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • ci/test/00_setup_env_native_qt5.sh
  • ci/test/00_setup_env_mac.sh
  • ci/test/00_setup_env_native_fuzz.sh
  • ci/test/00_setup_env_native_tsan.sh
  • ci/test/00_setup_env_win64.sh
  • doc/Doxyfile.in
  • ci/test/00_setup_env_s390x.sh
  • src/Makefile.am
  • ci/test/00_setup_env_native_asan.sh
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.
Learnt from: knst
Repo: dashpay/dash PR: 6916
File: src/univalue/include/univalue.h:81-88
Timestamp: 2025-10-25T07:08:51.918Z
Learning: For backport PRs from bitcoin/bitcoin, bitcoin-core/gui, etc., backported changes should match the original upstream PRs even if they appear strange, modify vendored code, or seem to violate coding guidelines. Still flag genuine issues like bugs, undefined behavior, crashes, compilation errors, or linter failures.
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.
📚 Learning: 2025-10-05T20:38:28.457Z
Learnt from: knst
Repo: dashpay/dash PR: 6871
File: contrib/guix/libexec/build.sh:358-360
Timestamp: 2025-10-05T20:38:28.457Z
Learning: In the Dash repository, when backporting code from Bitcoin Core, typos and minor issues in comments should be kept as-is to reduce merge conflicts in future backports, even if they remain unfixed in Bitcoin Core's master branch.

Applied to files:

  • doc/external-signer.md
  • configure.ac
📚 Learning: 2025-11-04T18:24:27.241Z
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6933
File: src/llmq/utils.cpp:284-298
Timestamp: 2025-11-04T18:24:27.241Z
Learning: In consensus-critical code (such as quorum formation, block validation, or deployment activation logic), do not suggest changes to the logic itself even if the implementation appears theoretically incorrect or off-by-one. Consensus rules, once deployed on the Dash network, must be preserved exactly to avoid network forks. Refactoring PRs should maintain perfect behavioral equivalence. Only suggest logic changes if explicitly accompanied by a DIP (Dash Improvement Proposal) or if the maintainer indicates the consensus rule needs to be changed with appropriate activation logic.

Applied to files:

  • doc/external-signer.md
  • configure.ac
📚 Learning: 2025-10-13T12:37:12.357Z
Learnt from: knst
Repo: dashpay/dash PR: 6883
File: src/rpc/rawtransaction.cpp:1088-1125
Timestamp: 2025-10-13T12:37:12.357Z
Learning: In backport pull requests (especially from Bitcoin Core), treat "moved" or refactored code as out-of-scope for content-level review. Focus validation on verifying that code is moved correctly: no fields added, no fields removed, no fields reordered, and no unexpected changes beyond whitespace adjustments. Pre-existing issues in the upstream code should be preserved to maintain fidelity to the original implementation.

Applied to files:

  • ci/test/00_setup_env_arm.sh
  • configure.ac
  • ci/test/00_setup_env_mac_native_x86_64.sh
  • ci/test/00_setup_env_native_nowallet.sh
📚 Learning: 2025-10-25T07:08:51.918Z
Learnt from: knst
Repo: dashpay/dash PR: 6916
File: src/univalue/include/univalue.h:81-88
Timestamp: 2025-10-25T07:08:51.918Z
Learning: For backport PRs from bitcoin/bitcoin, bitcoin-core/gui, etc., backported changes should match the original upstream PRs even if they appear strange, modify vendored code, or seem to violate coding guidelines. Still flag genuine issues like bugs, undefined behavior, crashes, compilation errors, or linter failures.

Applied to files:

  • ci/test/00_setup_env_arm.sh
  • configure.ac
  • ci/test/00_setup_env_native_nowallet.sh
📚 Learning: 2025-02-06T14:34:30.466Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request #6543 is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.

Applied to files:

  • ci/test/00_setup_env_arm.sh
  • configure.ac
  • ci/test/00_setup_env_mac_native_x86_64.sh
  • ci/test/00_setup_env_native_nowallet.sh
📚 Learning: 2025-01-14T09:07:12.446Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6532
File: src/net.cpp:4329-4329
Timestamp: 2025-01-14T09:07:12.446Z
Learning: Keep suggestions focused on the scope of the current commit/PR. Avoid suggesting unrelated improvements that should be handled in separate PRs, even if technically valid.

Applied to files:

  • ci/test/00_setup_env_arm.sh
  • ci/test/00_setup_env_native_nowallet.sh
📚 Learning: 2025-07-29T14:32:48.369Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.cpp:247-250
Timestamp: 2025-07-29T14:32:48.369Z
Learning: In PR #6761, kwvg acknowledged a null pointer check issue in ChainLockSigner::Cleanup() method but deferred it to follow-up, consistent with the pattern of avoiding scope creep in refactoring PRs.

Applied to files:

  • configure.ac
📚 Learning: 2025-08-19T14:57:31.801Z
Learnt from: knst
Repo: dashpay/dash PR: 6692
File: src/llmq/blockprocessor.cpp:217-224
Timestamp: 2025-08-19T14:57:31.801Z
Learning: In PR #6692, knst acknowledged a null pointer dereference issue in ProcessBlock() method where LookupBlockIndex may return nullptr but is passed to gsl::not_null, and created follow-up PR #6789 to address it, consistent with avoiding scope creep in performance-focused PRs.

Applied to files:

  • configure.ac
📚 Learning: 2025-08-11T17:16:36.654Z
Learnt from: PastaPastaPasta
Repo: dashpay/dash PR: 6804
File: src/qt/proposalwizard.cpp:40-42
Timestamp: 2025-08-11T17:16:36.654Z
Learning: In the Dash repository, when a PR adds new files that are not from Bitcoin backports, these files must be added to the list in test/util/data/non-backported.txt. This applies to newly created files like qt/proposalwizard.{h,cpp} and forms/proposalwizard.ui. Limited exemptions may exist for subtrees and similar cases.

Applied to files:

  • configure.ac
📚 Learning: 2025-08-01T07:46:37.840Z
Learnt from: UdjinM6
Repo: dashpay/dash PR: 6786
File: ci/test/04_install.sh:99-101
Timestamp: 2025-08-01T07:46:37.840Z
Learning: In backport PRs like #6786, UdjinM6 prefers to defer non-critical fixes (such as shell command expansion issues) to separate commits/PRs to maintain focus on the primary backport objectives, consistent with the project's pattern of avoiding scope creep.

Applied to files:

  • configure.ac
📚 Learning: 2025-06-09T16:43:20.996Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6718
File: test/functional/test_framework/test_framework.py:2102-2102
Timestamp: 2025-06-09T16:43:20.996Z
Learning: In the test framework consolidation PR (#6718), user kwvg prefers to limit functional changes to those directly related to MasternodeInfo, avoiding scope creep even for minor improvements like error handling consistency.

Applied to files:

  • configure.ac
  • ci/test/00_setup_env_native_nowallet.sh
📚 Learning: 2025-07-23T09:30:34.631Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6761
File: src/chainlock/signing.h:5-6
Timestamp: 2025-07-23T09:30:34.631Z
Learning: Dash Core uses BITCOIN_ prefix for header guards as the standard convention, inherited from Bitcoin Core. Only a few BLS-specific files in src/bls/ use DASH_ prefix. The vast majority of files (385+) use BITCOIN_ prefix.

Applied to files:

  • configure.ac
📚 Learning: 2025-01-06T09:51:03.167Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6516
File: depends/patches/gmp/include_ldflags_in_configure.patch:557-621
Timestamp: 2025-01-06T09:51:03.167Z
Learning: The `GMP_GCC_ARM_UMODSI` macro checks only the compiler version, and `GMP_GCC_MIPS_O32` relies on the `-mabi=32` flag. Therefore, `$LDFLAGS` is irrelevant to these tests.

Applied to files:

  • configure.ac
📚 Learning: 2025-01-02T08:33:26.751Z
Learnt from: kwvg
Repo: dashpay/dash PR: 6516
File: ci/test/00_setup_env_mac.sh:11-11
Timestamp: 2025-01-02T08:33:26.751Z
Learning: The removal of DMG support in the macOS packaging process eliminates the need for python-based scripts or python3-setuptools in the build environment. The PACKAGES variable in ci/test/00_setup_env_mac.sh is vestigial due to the use of a general-purpose Docker container defined in contrib/containers/ci/Dockerfile.

Applied to files:

  • ci/test/00_setup_env_mac_native_x86_64.sh
🪛 LanguageTool
doc/external-signer.md

[style] ~70-~70: Consider a more concise word here.
Context: ...ue, "txid": } ``` ## Signer API In order to be compatible with Dash Core any signer...

(IN_ORDER_TO_PREMIUM)


[grammar] ~167-~167: Use a hyphen to join words.
Context: ... support address types, in a BIP44/49/84 compatible manner. The `walletdisplayad...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
doc/external-signer.md

14-14: Dollar signs used before commands without showing output

(MD014, commands-show-output)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


40-40: Dollar signs used before commands without showing output

(MD014, commands-show-output)


48-48: Dollar signs used before commands without showing output

(MD014, commands-show-output)


49-49: Dollar signs used before commands without showing output

(MD014, commands-show-output)


59-59: Dollar signs used before commands without showing output

(MD014, commands-show-output)


80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


122-122: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


141-141: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


147-147: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Lint / Run linters
  • GitHub Check: x86_64-apple-darwin / Build depends
  • GitHub Check: x86_64-w64-mingw32 / Build depends
  • GitHub Check: x86_64-pc-linux-gnu_multiprocess / Build depends
  • GitHub Check: arm-linux-gnueabihf / Build depends
  • GitHub Check: x86_64-pc-linux-gnu / Build depends
  • GitHub Check: Check skip conditions
🔇 Additional comments (10)
src/dummywallet.cpp (1)

54-54: LGTM! Correct placement of the external signer option.

The addition of "-signer=" to the hidden args list is appropriate and ensures consistent command-line argument handling regardless of whether wallet support is compiled in. The alphabetical placement between "-salvagewallet" and "-spendzeroconfchange" is correct.

ci/test/00_setup_env_mac_native_x86_64.sh (1)

13-13: LGTM – Faithful backport change.

The removal of --with-boost-process is consistent with the broader PR's replacement of Boost.Process-based flow with the external signer feature. This change pattern is replicated across all CI test scripts (arm, native_nowallet, native_multiprocess, etc.) and matches the upstream Bitcoin Core backport.

Based on learnings.

ci/test/00_setup_env_native_nowallet.sh (1)

14-14: LGTM – Consistent backport change.

The removal of --with-boost-process aligns with the systematic replacement across all CI test scripts as part of the external signer backport.

Based on learnings.

ci/test/00_setup_env_native_multiprocess.sh (1)

16-16: LGTM – Consistent backport change.

The removal of --with-boost-process is consistent with the systematic replacement across all CI test scripts.

Based on learnings.

configure.ac (6)

322-325: LGTM – Default value now matches help text.

The AC_ARG_ENABLE block correctly sets use_external_signer=auto to match the help text. This addresses the inconsistency flagged in past reviews where the default was "yes" but help claimed "auto".


1440-1440: LGTM – Consistent with fuzzing mode behavior.

Disabling external signer when fuzzing is enabled (enable_fuzz=yes) is consistent with the pattern of disabling all external dependencies during fuzzing (upnp, natpmp, zmq, etc.).


1581-1612: LGTM – Clean Boost detection replacement.

The new Boost detection logic properly:

  1. Conditionally requires Boost only when needed (line 1582-1585)
  2. Checks for Boost base package (line 1590)
  3. Applies necessary preprocessor flags for compatibility (lines 1596-1611)
  4. Suppresses external warnings when requested (lines 1609-1611)

This replaces the old Boost.Process-specific checks and aligns with the external signer feature approach.


1614-1631: External signer platform logic is correct per upstream.

The platform gating approach:

Note: There is no explicit availability check for Boost.Process headers. This matches Bitcoin Core's upstream approach where the assumption is that Boost.Process is available when Boost is present. Users encountering compilation failures due to missing Boost.Process can use --disable-external-signer to work around it.

Based on learnings.


2025-2025: LGTM – Proper variable substitution.

AC_SUBST(ENABLE_EXTERNAL_SIGNER) correctly exposes the external signer enablement flag to the build system for use in Makefiles and configuration.


2093-2093: LGTM – Configuration summary output.

The "external signer = $use_external_signer" display line follows the established pattern for showing enabled features in the final configuration summary.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 26

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/qt/forms/createwalletdialog.ui (1)

150-156: Include Descriptor checkbox in tab order

Descriptor wallet option is missing from , which hurts keyboard navigation.

  <tabstops>
   <tabstop>wallet_name_line_edit</tabstop>
   <tabstop>encrypt_wallet_checkbox</tabstop>
   <tabstop>disable_privkeys_checkbox</tabstop>
   <tabstop>blank_wallet_checkbox</tabstop>
+  <tabstop>descriptor_checkbox</tabstop>
   <tabstop>external_signer_checkbox</tabstop>
  </tabstops>
src/test/system_tests.cpp (1)

39-43: Fix POSIX quoting in RunCommandParseJSON test

The JSON argument isn’t properly escaped; the shell will break on inner quotes.

-        const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\"");
+        const UniValue result = RunCommandParseJSON("echo \"{\\\"success\\\": true}\"");
🧹 Nitpick comments (36)
test/functional/test_framework/test_node.py (1)

884-889: LGTM! Consider adding documentation for developer experience.

The parameter addition is implemented correctly—the signature and call order match the RPC definition in src/wallet/rpc/wallet.cpp. The default value of None appropriately maps to the RPC's default of false.

However, the method signature has grown to 8 parameters without documentation. Consider adding a docstring that explains:

  • The purpose and type of each parameter
  • That external_signer=True requires both disable_private_keys=True and descriptors=True (per the RPC implementation)

This would improve the developer experience when writing tests that use external signers.

Example docstring addition:

+    def createwallet(self, wallet_name, disable_private_keys=None, blank=None, passphrase='', avoid_reuse=None, descriptors=None, load_on_startup=None, external_signer=None):
+        """Create a new wallet via the createwallet RPC.
+        
+        Args:
+            wallet_name: Name for the new wallet
+            disable_private_keys: Disable private keys (watchonly mode)
+            blank: Create a blank wallet (no keys or HD seed)
+            passphrase: Encryption passphrase
+            avoid_reuse: Enable coin reuse avoidance
+            descriptors: Create a descriptor wallet
+            load_on_startup: Add wallet to startup list
+            external_signer: Use external signer (requires disable_private_keys=True and descriptors=True)
+        """
         if descriptors is None:

Based on learnings: Good documentation is especially important in test framework code where multiple developers write tests.

test/functional/rpc_help.py (1)

114-115: Make the titles comparison fully order‑independent

To avoid brittle failures if RPC help ordering changes, sort both sides.

-        assert_equal(titles, sorted(components))
+        assert_equal(sorted(titles), sorted(components))
src/qt/optionsmodel.cpp (1)

226-232: Plumbing looks right; consider hiding when not supported

Persisting external_signer_path and SoftSetArg("-signer") is fine. To avoid confusing users, consider gating this option in the UI when ENABLE_EXTERNAL_SIGNER is off (e.g., hide row or mark read-only).

test/functional/mocks/signer.py (2)

11-19: Handle empty mock_result safely

Avoid index errors when mock_result exists but is empty.

 def perform_pre_checks():
     mock_result_path = os.path.join(os.getcwd(), "mock_result")
     if(os.path.isfile(mock_result_path)):
         with open(mock_result_path, "r", encoding="utf8") as f:
-            mock_result = f.read()
-        if mock_result[0]:
+            mock_result = f.read()
+        if mock_result:
             sys.stdout.write(mock_result[2:])
             sys.exit(int(mock_result[0]))

20-22: Normalize indentation to 4 spaces

Use 4-space indents to satisfy linters and maintain consistency.

-def enumerate(args):
-  sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
+def enumerate(args):
+    sys.stdout.write(json.dumps([{"fingerprint": "00000001", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))
@@
-       sys.argv.extend(buffer.rstrip().split(" "))
+        sys.argv.extend(buffer.rstrip().split(" "))

Also applies to: 96-97

src/qt/sendcoinsdialog.cpp (1)

540-545: Avoid tfm::format to stderr here

This may not compile without including tinyformat and won’t surface in the GUI. Prefer Q_EMIT message or qWarning().

-            if (err != TransactionError::OK) {
-                tfm::format(std::cerr, "Failed to sign PSBT");
-                processSendCoinsReturn(WalletModel::TransactionCreationFailed);
+            if (err != TransactionError::OK) {
+                processSendCoinsReturn(WalletModel::TransactionCreationFailed);
src/qt/walletmodel.h (1)

144-144: Pass string parameter by const reference.

The sAddress parameter creates an unnecessary copy. For strings, pass by const std::string& to avoid allocation overhead.

Apply this diff:

-    bool displayAddress(std::string sAddress);
+    bool displayAddress(const std::string& sAddress);

Note: The corresponding implementation in src/qt/walletmodel.cpp (line 581) should also be updated.

src/qt/forms/optionsdialog.ui (1)

412-441: Add a “Browse…” control and minor UX polish for the signer path.

  • Add a browse button to reduce path-typing errors.
  • Enable the clear button and a placeholder for discoverability.
  • Ensure the group is hidden/disabled at runtime when ENABLE_EXTERNAL_SIGNER is off (guard in optionsdialog.cpp).

Apply minimal UI tweaks:

         <layout class="QVBoxLayout" name="verticalLayoutHww">
           <item>
             <layout class="QHBoxLayout" name="horizontalLayoutHww">
               <item>
                 <widget class="QLabel" name="externalSignerPathLabel">
@@
               </item>
               <item>
                 <widget class="QLineEdit" name="externalSignerPath">
                   <property name="toolTip">
                     <string>Full path to a Dash Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
                   </property>
+                  <property name="placeholderText">
+                    <string>/path/to/hwi(.py|.exe)</string>
+                  </property>
+                  <property name="clearButtonEnabled">
+                    <bool>true</bool>
+                  </property>
                 </widget>
               </item>
+              <item>
+                <widget class="QToolButton" name="externalSignerBrowse">
+                  <property name="text">
+                    <string>…</string>
+                  </property>
+                  <property name="toolTip">
+                    <string>Browse for signer executable/script</string>
+                  </property>
+                </widget>
+              </item>
             </layout>
           </item>
         </layout>

Please confirm optionsdialog.cpp hides/disables groupBoxHww when external signer is not compiled. If not, I can draft the guard.

src/interfaces/node.h (1)

160-168: Const-correctness and naming clarity for interfaces::ExternalSigner.

  • Make getName() const to allow querying name on const instances and align with typical interface contracts.
  • Since there is a global ::ExternalSigner, consider explicitly documenting the distinction or renaming this interface (e.g., IExternalSigner) to avoid confusion in mixed scopes.
-class ExternalSigner
+class ExternalSigner
 {
 public:
     virtual ~ExternalSigner() {};
-    //! Get signer display name
-    virtual std::string getName() = 0;
+    //! Get signer display name
+    virtual std::string getName() const = 0;
 };
src/qt/walletmodel.cpp (1)

582-592: Consider passing a parent widget to QMessageBox.

The method correctly handles exceptions from the wallet layer and displays them to the user. However, QMessageBox::critical(nullptr, ...) uses a null parent, which may result in the dialog appearing disconnected from the main window.

Consider accepting an optional parent widget parameter to improve the UX.

Apply this diff if you want to improve dialog parenting:

-bool WalletModel::displayAddress(std::string sAddress)
+bool WalletModel::displayAddress(std::string sAddress, QWidget* parent)
 {
     CTxDestination dest = DecodeDestination(sAddress);
     bool res = false;
     try {
         res = m_wallet->displayAddress(dest);
     } catch (const std::runtime_error& e) {
-        QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
+        QMessageBox::critical(parent, tr("Can't display address"), e.what());
     }
     return res;
 }
src/util/system.cpp (1)

1399-1434: Harden external command execution: catch spawn errors and read full streams

Two improvements:

  • Propagate spawn failures uniformly (e.g., missing binary) by catching boost::process errors.
  • Read the entire stdout/stderr to avoid truncation on multi-line JSON and reduce deadlock risk if output exceeds a line.

Apply this diff:

 #ifdef ENABLE_EXTERNAL_SIGNER
     namespace bp = boost::process;

     UniValue result_json;
     bp::opstream stdin_stream;
     bp::ipstream stdout_stream;
     bp::ipstream stderr_stream;

     if (str_command.empty()) return UniValue::VNULL;

-    bp::child c(
-        str_command,
-        bp::std_out > stdout_stream,
-        bp::std_err > stderr_stream,
-        bp::std_in < stdin_stream
-    );
-    if (!str_std_in.empty()) {
-        stdin_stream << str_std_in << std::endl;
-    }
-    stdin_stream.pipe().close();
-
-    std::string result;
-    std::string error;
-    std::getline(stdout_stream, result);
-    std::getline(stderr_stream, error);
-
-    c.wait();
-    const int n_error = c.exit_code();
-    if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
-    if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
+    try {
+        bp::child c(
+            str_command,
+            bp::std_out > stdout_stream,
+            bp::std_err > stderr_stream,
+            bp::std_in < stdin_stream
+        );
+        if (!str_std_in.empty()) {
+            stdin_stream << str_std_in << std::endl;
+        }
+        stdin_stream.pipe().close();
+
+        std::string result{std::istreambuf_iterator<char>(stdout_stream), std::istreambuf_iterator<char>()};
+        std::string error{std::istreambuf_iterator<char>(stderr_stream), std::istreambuf_iterator<char>()};
+
+        c.wait();
+        const int n_error = c.exit_code();
+        if (n_error) throw std::runtime_error(strprintf("RunCommandParseJSON error: process(%s) returned %d: %s\n", str_command, n_error, error));
+        if (!result_json.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);
+    } catch (const std::exception& e) {
+        throw std::runtime_error(strprintf("RunCommandParseJSON spawn/IO failure for '%s': %s", str_command, e.what()));
+    }
doc/external-signer.md (3)

13-15: Add fenced code languages and address markdownlint MD014/MD040

Specify languages (bash/json) and either drop leading “$” or include sample outputs to satisfy MD014. Improves readability and CI lint.

Also applies to: 25-33, 39-41, 47-50, 59-61, 80-87, 98-101, 115-119, 122-136, 141-149


70-70: Tighten phrasing

Replace “In order to be compatible…” with “To be compatible…”.


167-167: Hyphenation nit

Use “BIP44/49/84-compatible” for correct hyphenation.

src/qt/optionsdialog.cpp (1)

141-145: UI polish when feature is unavailable

Besides disabling the field, consider hiding the entire row (label + editor) to reduce clutter when external signer support is not compiled.

src/qt/walletcontroller.cpp (1)

296-303: List-and-pass signers flow is OK; handle exceptions

Catching std::runtime_error and surfacing to users is good. Consider also logging the exception in the node logger for diagnostics.

test/functional/rpc_signer.py (2)

52-54: Fix flake8 E128 indentation on continued lines

Adjust hanging indent for assert_raises_rpc_error calls.

-        assert_raises_rpc_error(-1, 'Error: restart dashd with -signer=<cmd>',
-            self.nodes[0].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1, 'Error: restart dashd with -signer=<cmd>',
+            self.nodes[0].enumeratesigners,
+        )
@@
-        assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
-            self.nodes[3].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1, 'execve failed: No such file or directory',
+            self.nodes[3].enumeratesigners,
+        )
@@
-        assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
-            self.nodes[1].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1, 'RunCommandParseJSON error',
+            self.nodes[1].enumeratesigners,
+        )
@@
-        assert_raises_rpc_error(-1, 'fingerprint not found',
-            self.nodes[1].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1, 'fingerprint not found',
+            self.nodes[1].enumeratesigners,
+        )

Also applies to: 57-58, 63-65, 69-70


45-47: Make cleanup resilient

Ignore missing mock_result file during cleanup to avoid spurious failures.

-    def clear_mock_result(self, node):
-        os.remove(os.path.join(node.cwd, "mock_result"))
+    def clear_mock_result(self, node):
+        try:
+            os.remove(os.path.join(node.cwd, "mock_result"))
+        except FileNotFoundError:
+            pass
test/functional/mocks/invalid_signer.py (2)

23-39: Descriptor samples: fix duplicated variable and address type for 84'

Rename the second xpub_pkh to xpub_wpkh and use wpkh() for 84'/… paths.

-    xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
-    xpub_sh = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN"
-    xpub_pkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp"
+    xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
+    xpub_sh  = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN"
+    xpub_wpkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp"
@@
-            "pkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_pkh + "/0/*)#jftn8ppv"
+            "wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/0/*)#jftn8ppv"
@@
-            "pkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_pkh + "/1/*)#rawj6535"
+            "wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/1/*)#rawj6535"

20-22: Unused function argument is acceptable here

Ruff’s ARG001 for enumerate(args) can be ignored since argparse mandates the signature; or prefix with _args.

src/wallet/external_signer_scriptpubkeyman.cpp (2)

42-50: Selecting the first signer is fragile

When multiple devices are present, defaulting to index 0 is ambiguous. Accept a fingerprint preference (CLI/UI) and select by match; otherwise surface a clear error.

-    if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
-    // TODO: add fingerprint argument in case of multiple signers
-    return signers[0];
+    if (signers.empty()) throw std::runtime_error(std::string(__func__) + ": No external signers found");
+    // TODO: wire a preferred fingerprint from args/UI and select by it; for now pick the first as a fallback.
+    return signers[0];

78-85: OK to rely on prior PSBT fill; consider documenting

Relying on a prior non-signing FillPSBT pass is fine; consider a comment that inputs must carry bip32 derivations before invoking the signer.

src/qt/createwalletdialog.cpp (3)

9-9: Remove unused include to speed up builds.

#include <interfaces/node.h> isn’t used in this TU; header already declares setSigners. Please drop it.

-#include <interfaces/node.h>

42-45: Tiny typo in comment.

“immedidately” → “immediately”.

-// watch-only wallet, even though we immedidately fetch keys from the
+// watch-only wallet, even though we immediately fetch keys from the

115-134: setSigners(): robust defaults; consider empty label guard.

Prefilling the wallet name from the first signer is nice. Add a fallback if getName() is empty to keep the OK button logic predictable.

-const std::string label = signers[0]->getName();
-ui->wallet_name_line_edit->setText(QString::fromStdString(label));
+const std::string label = signers[0]->getName();
+ui->wallet_name_line_edit->setText(label.empty() ? tr("Hardware wallet") : QString::fromStdString(label));
src/wallet/wallet.cpp (2)

2505-2518: DisplayAddress ignores device result; return actual outcome.

Currently always returns true despite TODO. Consider checking the signer response and surfacing failure.

-ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
-return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
+return signer_spk_man->DisplayAddress(scriptPubKey, signer);

And inside ExternalSignerScriptPubKeyMan::DisplayAddress(...), parse the UniValue and return false on error (see header/cpp comment below).


3818-3851: Descriptor setup paths look correct; two small improvements.

  • Non‑signer branch: OK.
  • Signer branch: consider validating GetDescriptors() keys exist and logging unexpected shapes; also avoid continue silently on !GetOutputType().
- if (!descriptor_vals.isArray()) throw std::runtime_error(...);
+ if (!descriptor_vals.isArray()) {
+     throw std::runtime_error(std::string(__func__) + ": Unexpected result (no descriptor array)");
+ }
@@
- if (!desc->GetOutputType()) {
-     continue;
- }
+ if (!desc->GetOutputType()) {
+     // Skip non-spendable descriptors but log once for visibility.
+     m_storage.WalletLogPrintf("Skipping descriptor without recognized output type: %s\n", desc_str);
+     continue;
+ }

Also applies to: 3852-3881

src/wallet/external_signer_scriptpubkeyman.h (1)

30-33: API contract: return real device outcome.

Please make DisplayAddress return the parsed success/failure from the signer command instead of unconditionally true (see wallet.cpp comment).

src/node/interfaces.cpp (1)

16-16: Include external_signer.h unconditionally

Optional: guard the include to reduce compile surface when feature is disabled and to keep headers tighter.

-#include <external_signer.h>
+#ifdef ENABLE_EXTERNAL_SIGNER
+#include <external_signer.h>
+#endif
src/wallet/wallet.h (2)

30-31: Avoid pulling external_signer.h into wallet.h

Header coupling. wallet.h doesn't need ExternalSigner types in its public interface. Include externally in wallet.cpp instead, or guard by ENABLE_EXTERNAL_SIGNER to keep builds without the feature leaner.

-#include <external_signer.h>
+#ifdef ENABLE_EXTERNAL_SIGNER
+#include <external_signer.h>
+#endif

697-704: Preserve [[nodiscard]] and align n_signed types

Consider keeping [[nodiscard]] on FillPSBT to prevent silent error ignoring. Also, n_signed here is size_t*, but ScriptPubKeyMan::FillPSBT uses int*. Align types across APIs to avoid accidental narrowing.

-TransactionError FillPSBT(PartiallySignedTransaction& psbtx,
+[[nodiscard]] TransactionError FillPSBT(PartiallySignedTransaction& psbtx,
@@
-                  size_t* n_signed = nullptr,
+                  int* n_signed = nullptr,

If changing the type is disruptive now, at least document the mixed int/size_t and cast explicitly in the implementation.

src/external_signer.h (2)

24-35: Pass-by-reference and naming

Use const std::string& for chain to avoid copying; update comment to say “Dash” rather than “Bitcoin”.

-    ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name);
+    ExternalSigner(const std::string& command, const std::string& chain, const std::string& fingerprint, const std::string& name);
@@
-    //! Bitcoin mainnet, testnet, etc
+    //! Network (mainnet, testnet, etc.)

49-53: DisplayAddress return type vs actual usage

Callers ignore the UniValue and just rely on exceptions. Either parse and validate here and return bool, or document that this throws on errors and the value is unused. Prefer returning bool and inspecting result to make walletdisplayaddress more predictable.

test/functional/wallet_signer.py (2)

21-34: Use sys.executable and quote paths for -signer

Avoid “py ” launcher assumptions and spaces-in-path issues. Use the current interpreter path and quote.

+import sys
@@
-        if platform.system() == "Windows":
-            return "py " + path
-        else:
-            return path
+        return f"\"{sys.executable}\" \"{path}\""
@@
-        if platform.system() == "Windows":
-            return "py " + path
-        else:
-            return path
+        return f"\"{sys.executable}\" \"{path}\""

48-53: Robust mock_result cleanup

Guard against missing file to avoid spurious test errors.

     def clear_mock_result(self, node):
-        os.remove(os.path.join(node.cwd, "mock_result"))
+        p = os.path.join(node.cwd, "mock_result")
+        if os.path.exists(p):
+            os.remove(p)
src/external_signer.cpp (1)

31-31: Avoid copying UniValue objects in the loop.

The loop creates a copy of each UniValue object. Use a const reference for better performance.

Apply this diff:

-    for (UniValue signer : result.getValues()) {
+    for (const UniValue& signer : result.getValues()) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 764893d and e4004df4b14186f0f46ff69c95ea74a2fbf85d81.

📒 Files selected for processing (68)
  • ci/test/00_setup_env_arm.sh (1 hunks)
  • ci/test/00_setup_env_mac.sh (1 hunks)
  • ci/test/00_setup_env_mac_native_x86_64.sh (1 hunks)
  • ci/test/00_setup_env_native_asan.sh (1 hunks)
  • ci/test/00_setup_env_native_fuzz.sh (1 hunks)
  • ci/test/00_setup_env_native_multiprocess.sh (1 hunks)
  • ci/test/00_setup_env_native_nowallet.sh (1 hunks)
  • ci/test/00_setup_env_native_qt5.sh (1 hunks)
  • ci/test/00_setup_env_native_tsan.sh (1 hunks)
  • ci/test/00_setup_env_s390x.sh (1 hunks)
  • ci/test/00_setup_env_win64.sh (1 hunks)
  • configure.ac (7 hunks)
  • doc/Doxyfile.in (1 hunks)
  • doc/external-signer.md (1 hunks)
  • src/Makefile.am (5 hunks)
  • src/Makefile.test.include (1 hunks)
  • src/dummywallet.cpp (1 hunks)
  • src/external_signer.cpp (1 hunks)
  • src/external_signer.h (1 hunks)
  • src/interfaces/node.h (2 hunks)
  • src/interfaces/wallet.h (2 hunks)
  • src/node/interfaces.cpp (3 hunks)
  • src/qt/createwalletdialog.cpp (4 hunks)
  • src/qt/createwalletdialog.h (2 hunks)
  • src/qt/forms/createwalletdialog.ui (2 hunks)
  • src/qt/forms/optionsdialog.ui (1 hunks)
  • src/qt/forms/receiverequestdialog.ui (1 hunks)
  • src/qt/optionsdialog.cpp (3 hunks)
  • src/qt/optionsmodel.cpp (3 hunks)
  • src/qt/optionsmodel.h (1 hunks)
  • src/qt/receiverequestdialog.cpp (1 hunks)
  • src/qt/sendcoinsdialog.cpp (7 hunks)
  • src/qt/walletcontroller.cpp (4 hunks)
  • src/qt/walletmodel.cpp (2 hunks)
  • src/qt/walletmodel.h (1 hunks)
  • src/qt/walletmodeltransaction.cpp (1 hunks)
  • src/qt/walletmodeltransaction.h (1 hunks)
  • src/rpc/client.cpp (1 hunks)
  • src/rpc/external_signer.cpp (1 hunks)
  • src/rpc/register.h (2 hunks)
  • src/test/system_tests.cpp (2 hunks)
  • src/util/error.cpp (1 hunks)
  • src/util/error.h (1 hunks)
  • src/util/system.cpp (3 hunks)
  • src/util/system.h (0 hunks)
  • src/wallet/external_signer_scriptpubkeyman.cpp (1 hunks)
  • src/wallet/external_signer_scriptpubkeyman.h (1 hunks)
  • src/wallet/init.cpp (1 hunks)
  • src/wallet/interfaces.cpp (2 hunks)
  • src/wallet/rpc/addresses.cpp (1 hunks)
  • src/wallet/rpc/backup.cpp (1 hunks)
  • src/wallet/rpc/spend.cpp (1 hunks)
  • src/wallet/rpc/wallet.cpp (6 hunks)
  • src/wallet/scriptpubkeyman.h (2 hunks)
  • src/wallet/wallet.cpp (17 hunks)
  • src/wallet/wallet.h (5 hunks)
  • src/wallet/walletdb.cpp (1 hunks)
  • src/wallet/walletdb.h (1 hunks)
  • src/wallet/walletutil.h (1 hunks)
  • test/config.ini.in (1 hunks)
  • test/functional/mocks/invalid_signer.py (1 hunks)
  • test/functional/mocks/signer.py (1 hunks)
  • test/functional/rpc_help.py (1 hunks)
  • test/functional/rpc_signer.py (1 hunks)
  • test/functional/test_framework/test_framework.py (1 hunks)
  • test/functional/test_framework/test_node.py (1 hunks)
  • test/functional/test_runner.py (1 hunks)
  • test/functional/wallet_signer.py (1 hunks)
💤 Files with no reviewable changes (1)
  • src/util/system.h
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/util/error.h
  • src/qt/walletmodeltransaction.h
  • src/qt/walletmodel.h
  • src/rpc/register.h
  • src/qt/receiverequestdialog.cpp
  • src/wallet/rpc/addresses.cpp
  • src/wallet/init.cpp
  • src/wallet/rpc/backup.cpp
  • src/wallet/walletdb.cpp
  • src/util/system.cpp
  • src/wallet/walletdb.h
  • src/qt/walletmodeltransaction.cpp
  • src/wallet/scriptpubkeyman.h
  • src/interfaces/node.h
  • src/qt/optionsdialog.cpp
  • src/test/system_tests.cpp
  • src/util/error.cpp
  • src/wallet/external_signer_scriptpubkeyman.cpp
  • src/dummywallet.cpp
  • src/interfaces/wallet.h
  • src/qt/createwalletdialog.cpp
  • src/rpc/external_signer.cpp
  • src/node/interfaces.cpp
  • src/qt/optionsmodel.h
  • src/rpc/client.cpp
  • src/qt/walletcontroller.cpp
  • src/wallet/interfaces.cpp
  • src/wallet/walletutil.h
  • src/qt/walletmodel.cpp
  • src/wallet/external_signer_scriptpubkeyman.h
  • src/external_signer.cpp
  • src/qt/optionsmodel.cpp
  • src/qt/sendcoinsdialog.cpp
  • src/wallet/rpc/wallet.cpp
  • src/wallet/wallet.h
  • src/wallet/wallet.cpp
  • src/external_signer.h
  • src/qt/createwalletdialog.h
  • src/wallet/rpc/spend.cpp
ci/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the ci directory (continuous integration)

Files:

  • ci/test/00_setup_env_native_nowallet.sh
  • ci/test/00_setup_env_s390x.sh
  • ci/test/00_setup_env_mac_native_x86_64.sh
  • ci/test/00_setup_env_mac.sh
  • ci/test/00_setup_env_native_qt5.sh
  • ci/test/00_setup_env_native_tsan.sh
  • ci/test/00_setup_env_native_fuzz.sh
  • ci/test/00_setup_env_arm.sh
  • ci/test/00_setup_env_native_multiprocess.sh
  • ci/test/00_setup_env_win64.sh
  • ci/test/00_setup_env_native_asan.sh
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/test_framework/test_framework.py
  • test/functional/test_runner.py
  • test/functional/wallet_signer.py
  • test/functional/rpc_help.py
  • test/functional/test_framework/test_node.py
  • test/functional/rpc_signer.py
  • test/functional/mocks/signer.py
  • test/functional/mocks/invalid_signer.py
src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Files:

  • src/test/system_tests.cpp
doc/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the doc directory (documentation)

Files:

  • doc/Doxyfile.in
  • doc/external-signer.md
🧬 Code graph analysis (28)
src/qt/walletmodeltransaction.h (1)
src/qt/walletmodeltransaction.cpp (2)
  • setWtx (29-32)
  • setWtx (29-29)
src/qt/walletmodel.h (1)
src/qt/walletmodel.cpp (2)
  • displayAddress (582-592)
  • displayAddress (582-582)
src/rpc/register.h (2)
src/rpc/external_signer.cpp (2)
  • RegisterSignerRPCCommands (61-73)
  • RegisterSignerRPCCommands (61-61)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/wallet/rpc/addresses.cpp (2)
src/wallet/rpc/util.cpp (2)
  • GetWalletForJSONRPCRequest (55-77)
  • GetWalletForJSONRPCRequest (55-55)
src/wallet/interfaces.cpp (18)
  • LOCK (250-259)
  • LOCK (260-263)
  • LOCK (291-295)
  • LOCK (296-300)
  • LOCK (353-361)
  • LOCK (479-491)
  • dest (210-214)
  • dest (210-210)
  • dest (223-226)
  • dest (223-223)
  • dest (227-230)
  • dest (227-227)
  • dest (231-249)
  • dest (231-234)
  • dest (264-268)
  • dest (264-264)
  • dest (269-273)
  • dest (269-269)
src/wallet/init.cpp (2)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/dummywallet.cpp (1)
  • argsman (31-31)
src/wallet/walletdb.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/util/system.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/wallet/scriptpubkeyman.h (2)
src/wallet/walletutil.h (1)
  • WalletDescriptor (70-103)
src/wallet/external_signer_scriptpubkeyman.cpp (2)
  • SetupDescriptor (17-40)
  • SetupDescriptor (17-17)
src/interfaces/node.h (2)
src/external_signer.cpp (1)
  • ExternalSigner (17-17)
src/external_signer.h (1)
  • ExternalSigner (18-64)
src/qt/optionsdialog.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/test/system_tests.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/util/error.cpp (1)
src/util/translation.h (1)
  • Untranslated (48-48)
src/wallet/external_signer_scriptpubkeyman.cpp (3)
src/wallet/wallet.cpp (24)
  • batch (550-550)
  • batch (815-815)
  • batch (938-938)
  • batch (1178-1178)
  • batch (1257-1257)
  • batch (1313-1313)
  • batch (1358-1358)
  • batch (1370-1370)
  • batch (1391-1391)
  • batch (1515-1515)
  • batch (1660-1660)
  • batch (1744-1744)
  • batch (1793-1793)
  • batch (2239-2239)
  • batch (2300-2300)
  • batch (2307-2307)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • GetSolvingProvider (3734-3738)
  • GetSolvingProvider (3734-3734)
  • GetSolvingProvider (3740-3748)
  • GetSolvingProvider (3740-3740)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
src/wallet/scriptpubkeyman.cpp (16)
  • GetID (801-804)
  • GetID (801-801)
  • GetID (2375-2379)
  • GetID (2375-2375)
  • TopUp (1410-1413)
  • TopUp (1410-1410)
  • TopUp (1943-2004)
  • TopUp (1943-1943)
  • GetSolvingProvider (678-681)
  • GetSolvingProvider (678-678)
  • GetSolvingProvider (2231-2234)
  • GetSolvingProvider (2231-2231)
  • FillPSBT (734-778)
  • FillPSBT (734-734)
  • FillPSBT (2288-2355)
  • FillPSBT (2288-2288)
src/external_signer.cpp (4)
  • Enumerate (24-60)
  • Enumerate (24-24)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
test/functional/rpc_help.py (2)
test/functional/test_framework/test_framework.py (2)
  • is_external_signer_compiled (1115-1117)
  • is_zmq_compiled (1135-1137)
test/functional/test_framework/util.py (1)
  • assert_equal (69-74)
src/interfaces/wallet.h (2)
src/qt/walletmodel.cpp (2)
  • displayAddress (582-592)
  • displayAddress (582-582)
src/wallet/interfaces.cpp (12)
  • dest (210-214)
  • dest (210-210)
  • dest (223-226)
  • dest (223-223)
  • dest (227-230)
  • dest (227-227)
  • dest (231-249)
  • dest (231-234)
  • dest (264-268)
  • dest (264-264)
  • dest (269-273)
  • dest (269-269)
test/functional/test_framework/test_node.py (1)
src/wallet/rpc/wallet.cpp (2)
  • createwallet (615-713)
  • createwallet (615-615)
src/qt/createwalletdialog.cpp (2)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/qt/createwalletdialog.h (1)
  • CreateWalletDialog (24-44)
test/functional/rpc_signer.py (3)
test/functional/test_framework/test_framework.py (1)
  • skip_if_no_external_signer (1106-1109)
test/functional/test_framework/util.py (2)
  • assert_equal (69-74)
  • assert_raises_rpc_error (132-148)
test/functional/wallet_signer.py (6)
  • mock_signer_path (21-26)
  • set_test_params (35-41)
  • skip_test_if_missing_module (43-45)
  • set_mock_result (47-49)
  • clear_mock_result (51-52)
  • run_test (54-57)
src/rpc/external_signer.cpp (2)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/external_signer.cpp (2)
  • Enumerate (24-60)
  • Enumerate (24-24)
src/node/interfaces.cpp (1)
src/external_signer.cpp (2)
  • Enumerate (24-60)
  • Enumerate (24-24)
src/qt/walletmodel.cpp (1)
src/wallet/interfaces.cpp (28)
  • dest (210-214)
  • dest (210-210)
  • dest (223-226)
  • dest (223-223)
  • dest (227-230)
  • dest (227-227)
  • dest (231-249)
  • dest (231-234)
  • dest (264-268)
  • dest (264-264)
  • dest (269-273)
  • dest (269-269)
  • m_wallet (158-161)
  • m_wallet (166-166)
  • m_wallet (175-175)
  • m_wallet (176-176)
  • m_wallet (182-182)
  • m_wallet (183-183)
  • m_wallet (443-446)
  • m_wallet (447-450)
  • m_wallet (522-522)
  • m_wallet (523-523)
  • m_wallet (524-524)
  • m_wallet (525-525)
  • m_wallet (526-526)
  • m_wallet (527-527)
  • m_wallet (532-532)
  • m_wallet (592-592)
src/wallet/external_signer_scriptpubkeyman.h (5)
src/wallet/scriptpubkeyman.h (6)
  • wallet (28-241)
  • DescriptorScriptPubKeyMan (512-634)
  • WalletStorage (34-234)
  • bool (179-179)
  • bool (218-218)
  • bool (224-224)
src/wallet/walletutil.h (1)
  • WalletDescriptor (70-103)
src/wallet/external_signer_scriptpubkeyman.cpp (8)
  • SetupDescriptor (17-40)
  • SetupDescriptor (17-17)
  • GetExternalSigner (42-50)
  • GetExternalSigner (42-42)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • FillPSBT (64-85)
  • FillPSBT (64-64)
src/external_signer.cpp (2)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
src/wallet/wallet.cpp (4)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
src/external_signer.cpp (2)
src/external_signer.h (1)
  • ExternalSigner (18-64)
src/util/system.cpp (2)
  • RunCommandParseJSON (1397-1434)
  • RunCommandParseJSON (1397-1397)
src/wallet/rpc/wallet.cpp (2)
src/wallet/rpc/addresses.cpp (2)
  • walletdisplayaddress (670-708)
  • walletdisplayaddress (670-670)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/wallet/wallet.h (6)
src/external_signer.cpp (2)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
src/wallet/wallet.cpp (4)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
src/wallet/external_signer_scriptpubkeyman.cpp (4)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • FillPSBT (64-85)
  • FillPSBT (64-64)
src/wallet/interfaces.cpp (12)
  • dest (210-214)
  • dest (210-210)
  • dest (223-226)
  • dest (223-223)
  • dest (227-230)
  • dest (227-227)
  • dest (231-249)
  • dest (231-234)
  • dest (264-268)
  • dest (264-264)
  • dest (269-273)
  • dest (269-269)
src/wallet/scriptpubkeyman.cpp (4)
  • FillPSBT (734-778)
  • FillPSBT (734-734)
  • FillPSBT (2288-2355)
  • FillPSBT (2288-2288)
src/psbt.cpp (1)
  • PartiallySignedTransaction (16-20)
src/wallet/wallet.cpp (4)
src/util/translation.h (1)
  • Untranslated (48-48)
src/external_signer.cpp (2)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
src/wallet/external_signer_scriptpubkeyman.cpp (4)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • GetExternalSigner (42-50)
  • GetExternalSigner (42-42)
src/wallet/interfaces.cpp (12)
  • dest (210-214)
  • dest (210-210)
  • dest (223-226)
  • dest (223-223)
  • dest (227-230)
  • dest (227-227)
  • dest (231-249)
  • dest (231-234)
  • dest (264-268)
  • dest (264-264)
  • dest (269-273)
  • dest (269-269)
src/external_signer.h (3)
src/external_signer.cpp (11)
  • ExternalSigner (17-17)
  • NetworkArg (19-22)
  • NetworkArg (19-19)
  • Enumerate (24-60)
  • Enumerate (24-24)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
  • GetDescriptors (67-70)
  • GetDescriptors (67-67)
  • SignTransaction (72-117)
  • SignTransaction (72-72)
src/wallet/wallet.cpp (6)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • SignTransaction (1998-2015)
  • SignTransaction (1998-1998)
  • SignTransaction (2017-2030)
  • SignTransaction (2017-2017)
src/wallet/external_signer_scriptpubkeyman.cpp (2)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
src/qt/createwalletdialog.h (2)
src/interfaces/node.h (2)
  • interfaces (48-411)
  • ExternalSigner (161-168)
src/qt/createwalletdialog.cpp (4)
  • setSigners (115-134)
  • setSigners (115-115)
  • CreateWalletDialog (17-108)
  • CreateWalletDialog (110-113)
🪛 Flake8 (7.3.0)
test/functional/wallet_signer.py

[error] 106-106: continuation line under-indented for visual indent

(E128)


[error] 107-107: closing bracket does not match visual indentation

(E124)


[error] 126-126: continuation line missing indentation or outdented

(E122)

test/functional/rpc_signer.py

[error] 52-52: continuation line under-indented for visual indent

(E128)


[error] 57-57: continuation line under-indented for visual indent

(E128)


[error] 63-63: continuation line under-indented for visual indent

(E128)


[error] 69-69: continuation line under-indented for visual indent

(E128)

test/functional/mocks/signer.py

[error] 21-21: indentation is not a multiple of 4

(E111)


[error] 96-96: indentation is not a multiple of 4

(E111)

🪛 LanguageTool
doc/external-signer.md

[style] ~70-~70: Consider a more concise word here.
Context: ...ue, "txid": } ``` ## Signer API In order to be compatible with Dash Core any signer...

(IN_ORDER_TO_PREMIUM)


[grammar] ~167-~167: Use a hyphen to join words.
Context: ... support address types, in a BIP44/49/84 compatible manner. The `walletdisplayad...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
doc/external-signer.md

14-14: Dollar signs used before commands without showing output

(MD014, commands-show-output)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


40-40: Dollar signs used before commands without showing output

(MD014, commands-show-output)


48-48: Dollar signs used before commands without showing output

(MD014, commands-show-output)


49-49: Dollar signs used before commands without showing output

(MD014, commands-show-output)


59-59: Dollar signs used before commands without showing output

(MD014, commands-show-output)


80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


122-122: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


141-141: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


147-147: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.0)
test/functional/test_framework/test_framework.py

1109-1109: Avoid specifying long messages outside the exception class

(TRY003)

test/functional/mocks/signer.py

20-20: Unused function argument: args

(ARG001)

test/functional/mocks/invalid_signer.py

20-20: Unused function argument: args

(ARG001)

@@ -25,4 +25,4 @@ export RUN_FUNCTIONAL_TESTS=false
export GOAL="install"
# -Wno-psabi is to disable ABI warnings: "note: parameter passing for argument of type ... changed in GCC 7.1"
# This could be removed once the ABI change warning does not show up by default
export BITCOIN_CONFIG="--enable-reduce-exports CXXFLAGS=-Wno-psabi --with-boost-process"
export BITCOIN_CONFIG="--enable-reduce-exports CXXFLAGS=-Wno-psabi"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider the scope of CI changes in this PR.

This file removes the --with-boost-process flag from the build configuration. The coding guidelines state: "Unless specifically prompted, avoid making changes to the ci directory."

Given the PR's scope issues, consider isolating CI configuration changes into a separate, focused PR.

As per coding guidelines.

🤖 Prompt for AI Agents
ci/test/00_setup_env_arm.sh around line 28: this change removed the
--with-boost-process flag from the CI build config (export
BITCOIN_CONFIG="--enable-reduce-exports CXXFLAGS=-Wno-psabi"), which violates
the guideline to avoid unsolicited ci/ changes; either restore the original
BITCOIN_CONFIG to include --with-boost-process and keep CI untouched in this PR,
or revert this file and move the CI configuration change into a separate focused
PR that documents and justifies the change.

@@ -12,4 +12,4 @@ export TEST_RUNNER_EXTRA="--timeout-factor=4" # Increase timeout because saniti
export FUNCTIONAL_TESTS_CONFIG="--exclude wallet_multiwallet.py" # Temporarily suppress ASan heap-use-after-free (see issue #14163)
export RUN_BENCH=true
export GOAL="install"
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=address,integer,undefined CC=clang CXX=clang++ --with-boost-process"
export BITCOIN_CONFIG="--enable-zmq --with-incompatible-bdb --with-gui=qt5 CPPFLAGS=-DDEBUG_LOCKORDER --with-sanitizers=address,integer,undefined CC=clang CXX=clang++"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider the scope of CI changes in this PR.

This file removes the --with-boost-process flag from the build configuration. While the change itself is straightforward, the coding guidelines state: "Unless specifically prompted, avoid making changes to the ci directory."

Given the PR's catastrophic validation failures (1,156 files changed, 32,244 insertions, 31,996 deletions), consider whether CI changes should be isolated into a separate, focused PR with clear justification and Bitcoin commit references.

As per coding guidelines.

🤖 Prompt for AI Agents
ci/test/00_setup_env_native_asan.sh lines 15-15: this PR removed the
--with-boost-process build flag from the CI script, which violates the guideline
to avoid changes in the ci directory unless explicitly prompted; restore the
original BITCOIN_CONFIG line (re-adding --with-boost-process) in this PR or else
revert this file here and create a separate, focused PR that only modifies CI
with a clear justification and references to the upstream Bitcoin commits that
mandate the change, and include in that PR a brief rationale and test plan so
reviewers can evaluate CI-only changes independently.


## Example usage

The following example is based on the [HWI](https://github.com/bitcoin-core/HWI) tool. Version 2.0 or newer is required. Although this tool is hosted under the Dash Core GitHub organization and maintained by Dash Core developers, it should be used with caution. It is considered experimental and has far less review than Dash Core itself. Be particularly careful when running tools such as these on a computer with private keys on it.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix HWI org reference

HWI is hosted under the bitcoin-core organization, not Dash Core. Update the wording to avoid confusion.

🤖 Prompt for AI Agents
In doc/external-signer.md around line 7, the paragraph incorrectly states HWI is
hosted under the Dash Core GitHub organization; change the wording to say HWI is
hosted under the bitcoin-core organization (link remains the same) to avoid
confusion and clarify maintainership.

Comment on lines +907 to 911
// First fill transaction with our data without signing,
// so external signers are not asked sign more than once.
bool complete;
(void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Initialize ‘complete’ before first FillPSBT and document the ignored error.

Initialize to a known value to avoid any potential read-before-write on error paths inside FillPSBT, and make the intentional error ignore explicit.

-    bool complete;
-    (void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
+    bool complete = false;
+    // Intentionally pre-fill without signing so external signers are prompted only once.
+    // Ignore the return value here; the subsequent signing call will surface errors.
+    (void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// First fill transaction with our data without signing,
// so external signers are not asked sign more than once.
bool complete;
(void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
// First fill transaction with our data without signing,
// so external signers are not asked sign more than once.
bool complete = false;
// Intentionally pre-fill without signing so external signers are prompted only once.
// Ignore the return value here; the subsequent signing call will surface errors.
(void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true);
const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false);
🤖 Prompt for AI Agents
In src/wallet/rpc/spend.cpp around lines 907 to 911, initialize the local bool
complete to a known value (e.g., bool complete = false;) before the first call
to pwallet->FillPSBT to avoid any potential read-before-write inside FillPSBT,
and replace the current silent cast-to-void with an explicit comment documenting
that the return/error from the first FillPSBT is intentionally ignored (e.g.,
"/* intentionally ignore result; only used to populate PSBT for external signers
*/") so readers and static analyzers understand the intent.

Comment on lines +628 to 629
{"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

New createwallet arg external_signer: tighten invariants

Enforce at RPC boundary: when external_signer=true require descriptors=true, disable_private_keys=true, and a non-empty -signer. Otherwise fail fast with a clear error.

Apply this minimal guard before wallet creation:

@@
-    if (!request.params[7].isNull() && request.params[7].get_bool()) {
+    if (!request.params[7].isNull() && request.params[7].get_bool()) {
 #ifdef ENABLE_EXTERNAL_SIGNER
-        flags |= WALLET_FLAG_EXTERNAL_SIGNER;
+        if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
+            throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor support must be enabled when using an external signer");
+        }
+        if (!(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+            throw JSONRPCError(RPC_WALLET_ERROR, "Private keys must be disabled when using an external signer");
+        }
+        if (gArgs.GetArg("-signer", "").empty()) {
+            throw JSONRPCError(RPC_INVALID_PARAMETER, "External signer requires -signer to be configured");
+        }
+        flags |= WALLET_FLAG_EXTERNAL_SIGNER;
 #else
         throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
 #endif
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/wallet/rpc/wallet.cpp around lines 628-629, enforce at the RPC boundary
that if the new createwallet argument external_signer is true then descriptors
must be true, disable_private_keys must be true, and the configured -signer
string must be non-empty; if any of these invariants do not hold, return an RPC
error immediately with a clear message explaining which requirement failed (e.g.
"external_signer requires descriptors=true, disable_private_keys=true, and
-signer to be set"). Add this minimal guard check immediately before wallet
creation so the RPC fails fast rather than proceeding to wallet creation.

Comment on lines +11 to +19
def perform_pre_checks():
mock_result_path = os.path.join(os.getcwd(), "mock_result")
if(os.path.isfile(mock_result_path)):
with open(mock_result_path, "r", encoding="utf8") as f:
mock_result = f.read()
if mock_result[0]:
sys.stdout.write(mock_result[2:])
sys.exit(int(mock_result[0]))

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard empty mock_result and check exit code explicitly

Indexing mock_result[0] will crash on empty files; also make the exit test explicit.

 def perform_pre_checks():
     mock_result_path = os.path.join(os.getcwd(), "mock_result")
     if(os.path.isfile(mock_result_path)):
         with open(mock_result_path, "r", encoding="utf8") as f:
             mock_result = f.read()
-        if mock_result[0]:
-            sys.stdout.write(mock_result[2:])
-            sys.exit(int(mock_result[0]))
+        if mock_result:
+            code_char = mock_result[0]
+            if code_char != '0':
+                sys.stdout.write(mock_result[2:])
+                sys.exit(int(code_char))
🤖 Prompt for AI Agents
In test/functional/mocks/invalid_signer.py around lines 11 to 19: after reading
mock_result, first guard for an empty file (if not mock_result: return or exit
early) to avoid indexing; then validate the first character is an explicit digit
(use .isdigit()) before converting to int; write the rest of the file to stdout
after consuming that digit (use mock_result[1:] or strip a known separator if
present) and call sys.exit(int(mock_result[0])) so the exit code conversion is
explicit and safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/test/system_tests.cpp (1)

39-46: Fix invalid string literal and JSON quoting on non‑Windows.

The literal at Line 41 is unterminated and will not compile. Escape inner quotes.

-#else
-        const UniValue result = RunCommandParseJSON("echo \"{\"success\": true}\"");
-#endif
+#else
+        const UniValue result = RunCommandParseJSON("echo \"{\\\"success\\\": true}\"");
+#endif
♻️ Duplicate comments (23)
src/Makefile.test.include (1)

42-49: Unconditional wallet linkage breaks --disable-wallet builds.

Linking $(LIBBITCOIN_WALLET) in FUZZ_SUITE_LD_COMMON unconditionally causes linker errors when wallet is disabled. Move it under ENABLE_WALLET.

@@
 FUZZ_SUITE_LD_COMMON = \
  $(LIBTEST_FUZZ) \
  $(LIBTEST_UTIL) \
  $(LIBBITCOIN_NODE) \
- $(LIBBITCOIN_WALLET) \
  $(LIBBITCOIN_COMMON) \
  $(LIBBITCOIN_UTIL) \
@@
 if ENABLE_WALLET
@@
-FUZZ_SUITE_LD_COMMON +=\
+FUZZ_SUITE_LD_COMMON += \
+ $(LIBBITCOIN_WALLET) \
  $(SQLITE_LIBS) \
  $(BDB_LIBS)

Also applies to: 223-226

src/wallet/rpc/spend.cpp (1)

907-911: Initialize ‘complete’ before first FillPSBT.

Avoid potential read-before-write on error paths; keep the explanatory comment.

-    // First fill transaction with our data without signing,
-    // so external signers are not asked sign more than once.
-    bool complete;
-    (void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true);
+    // First fill transaction with our data without signing,
+    // so external signers are not asked sign more than once.
+    bool complete = false;
+    (void)pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true);
configure.ac (1)

327-331: Make external‑signer truly ‘auto’ and degrade gracefully when Boost.Process is absent.

Default is currently “yes” (contradicts help) and forces hard failure without Boost.Process. Switch to auto-detect and only error if explicitly requested. Also drop unnecessary AC_SUBST.

@@
-AC_ARG_ENABLE([external-signer],
-    [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default is auto, requires Boost::Process)])],
-    [use_external_signer=$enableval],
-    [use_external_signer=yes])
+AC_ARG_ENABLE([external-signer],
+    [AS_HELP_STRING([--enable-external-signer],[compile external signer support (default: auto; requires Boost::Process)])],
+    [use_external_signer=$enableval],
+    [use_external_signer=auto])
@@
-  dnl Opt-in to Boost Process if external signer support is requested
-  if test "$use_external_signer" != no; then
-    AC_MSG_CHECKING(for Boost Process)
-    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <boost/process.hpp>]],
-     [[ boost::process::child* child = new boost::process::child; delete child; ]])],
-     [ AC_MSG_RESULT(yes)
-     AC_DEFINE([ENABLE_EXTERNAL_SIGNER],,[define if external signer support is enabled])
-     ],
-     [ AC_MSG_ERROR([Boost::Process is required for external signer support, but not available!])]
-    )
-  fi
+  dnl Probe Boost.Process when enabled explicitly or in auto mode
+  if test "$use_external_signer" = yes || test "$use_external_signer" = auto; then
+    AC_MSG_CHECKING([for Boost.Process])
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <boost/process.hpp>]],
+      [[ boost::process::child c((char*)""); ]])],
+      [ AC_MSG_RESULT([yes])
+        use_external_signer=yes
+        AC_DEFINE([ENABLE_EXTERNAL_SIGNER],[1],[Define if external signer support is enabled]) ],
+      [ AC_MSG_RESULT([no])
+        if test "$use_external_signer" = yes; then
+          AC_MSG_ERROR([--enable-external-signer requires Boost.Process])
+        else
+          use_external_signer=no
+        fi ])
+  fi
@@
-AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = "yes"])
+AM_CONDITIONAL([ENABLE_EXTERNAL_SIGNER], [test "$use_external_signer" = yes])
@@
-AC_SUBST(ENABLE_EXTERNAL_SIGNER)
+# not needed; use AM_CONDITIONAL and C/C++ macro

Also applies to: 1556-1586, 1591-1592, 1978-1979

ci/test/00_setup_env_native_asan.sh (1)

15-15: Consider isolating CI configuration changes.

The coding guidelines state: "Unless specifically prompted, avoid making changes to the ci directory." Given the PR's substantial scope, CI configuration changes may be better suited for a separate, focused PR.

As per coding guidelines.

ci/test/00_setup_env_native_nowallet.sh (1)

14-14: Consider isolating CI configuration changes.

The coding guidelines state: "Unless specifically prompted, avoid making changes to the ci directory." Given the PR's substantial scope, CI configuration changes may be better suited for a separate, focused PR.

As per coding guidelines.

src/qt/receiverequestdialog.cpp (1)

99-101: Fix signal connection leak in setInfo.

The connect() call creates a new signal-slot connection each time setInfo() is invoked. If called multiple times, clicking the Verify button will trigger displayAddress() multiple times.

Apply one of these fixes:

Option 1 (preferred): Move connection to constructor
Move the signal connection to the constructor so it's only connected once:

 ReceiveRequestDialog::ReceiveRequestDialog(QWidget *parent) :
     QDialog(parent, GUIUtil::dialog_flags),
     ui(new Ui::ReceiveRequestDialog),
     model(nullptr)
 {
     ui->setupUi(this);
 
     GUIUtil::updateFonts();
 
     GUIUtil::handleCloseWindowShortcut(this);
+
+    connect(ui->btnVerify, &QPushButton::clicked, this, [this] {
+        if (model) {
+            model->displayAddress(info.address.toStdString());
+        }
+    });
 }

Then remove the connect call from setInfo():

     ui->btnVerify->setVisible(model->wallet().hasExternalSigner());
-
-    connect(ui->btnVerify, &QPushButton::clicked, [this] {
-        model->displayAddress(info.address.toStdString());
-    });
 }

Option 2: Use Qt::UniqueConnection flag

-    connect(ui->btnVerify, &QPushButton::clicked, [this] {
+    connect(ui->btnVerify, &QPushButton::clicked, this, [this] {
         model->displayAddress(info.address.toStdString());
-    });
+    }, Qt::UniqueConnection);
src/wallet/rpc/addresses.cpp (1)

669-710: RPC handler implementation is correct, but underlying DisplayAddress has known issue.

The RPC endpoint properly validates the address and checks the return value from DisplayAddress. However, as noted in previous reviews, the underlying implementation in src/wallet/external_signer_scriptpubkeyman.cpp does not inspect the result of signer.DisplayAddress() and unconditionally returns true, which could mask hardware device failures.

doc/external-signer.md (1)

7-7: Fix HWI org and wording.

HWI is under the bitcoin-core org, not Dash. Also reword to avoid implying Dash maintainership.

-The following example is based on the [HWI](https://github.com/dashpay/HWI) tool. Version 2.0 or newer is required. Although this tool is hosted under the Dash Core GitHub organization and maintained by Dash Core developers, it should be used with caution.
+The following example is based on the [HWI](https://github.com/bitcoin-core/HWI) tool. Version 2.0 or newer is required. Although this tool is hosted under the bitcoin-core GitHub organization, it should be used with caution.
src/wallet/external_signer_scriptpubkeyman.cpp (1)

52-61: Guard null provider and handle DisplayAddress errors.

Current code may dereference a null provider and ignores failures from the external command.

 bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const
 {
     // TODO: avoid the need to infer a descriptor from inside a descriptor wallet
-    auto provider = GetSolvingProvider(scriptPubKey);
-    auto descriptor = InferDescriptor(scriptPubKey, *provider);
-
-    signer.DisplayAddress(descriptor->ToString());
-    // TODO inspect result
-    return true;
+    auto provider = GetSolvingProvider(scriptPubKey);
+    if (!provider) {
+        return false;
+    }
+    auto descriptor = InferDescriptor(scriptPubKey, *provider);
+    if (!descriptor) {
+        return false;
+    }
+    try {
+        (void)signer.DisplayAddress(descriptor->ToString());
+        return true;
+    } catch (const std::exception&) {
+        return false;
+    }
 }
test/functional/mocks/invalid_signer.py (1)

11-19: Harden mock pre-checks, fix duplicate var, and require --account

  • Guard empty mock_result, validate exit digit, and only treat nonzero as error.
  • Remove duplicate xpub assignment typo.
  • Make --account required to avoid None concatenation errors.
  • Silence ARG001 by marking enumerate’s arg unused.
 def perform_pre_checks():
     mock_result_path = os.path.join(os.getcwd(), "mock_result")
     if(os.path.isfile(mock_result_path)):
         with open(mock_result_path, "r", encoding="utf8") as f:
             mock_result = f.read()
-        if mock_result[0]:
-            sys.stdout.write(mock_result[2:])
-            sys.exit(int(mock_result[0]))
+        if mock_result:
+            code_char = mock_result[0]
+            payload = mock_result[1:] if len(mock_result) > 1 else ""
+            if code_char.isdigit():
+                code = int(code_char)
+                if code != 0:
+                    sys.stdout.write(payload.lstrip())
+                    sys.exit(code)

-    xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
+    # legacy pkh xpub (unused here, kept for reference if needed)
+    # xpub_pkh_legacy = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6"
     xpub_sh = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN"
     xpub_pkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp"

-parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
-parser_enumerate.set_defaults(func=enumerate)
+parser_enumerate = subparsers.add_parser('enumerate', help='list available signers')
+parser_enumerate.set_defaults(func=enumerate)
 
 parser_getdescriptors = subparsers.add_parser('getdescriptors')
 parser_getdescriptors.set_defaults(func=getdescriptors)
-parser_getdescriptors.add_argument('--account', metavar='account')
+parser_getdescriptors.add_argument('--account', metavar='account', required=True)

-def enumerate(args):
+def enumerate(_args):
     sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}]))

Also applies to: 24-27, 52-55

src/qt/sendcoinsdialog.cpp (3)

901-913: Apply same watch‑only fix in updateCoinControlState()

Keep behavior consistent across code paths.

-    // Include watch-only for wallets without private key
-    m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
+    // Include watch-only for wallets without private key or with external signer
+    m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner();

769-773: Assign external balance when showing “External balance”

Balance remains 0 in this branch. Use watch‑only balance for external-signer wallets.

-        if (model->wallet().hasExternalSigner()) {
-            ui->labelBalanceName->setText(tr("External balance:"));
+        if (model->wallet().hasExternalSigner()) {
+            balance = balances.watch_only_balance;
+            ui->labelBalanceName->setText(tr("External balance:"));

855-859: Allow watch‑only for external signer wallets

Coin control must include watch‑only UTXOs for external-signer wallets.

-    m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
+    m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner();
src/node/interfaces.cpp (1)

530-551: Harden listExternalSigners: catch enumerate errors; fix typos; minor cleanups

Wrap Enumerate() to avoid crashing GUI/RPC on malformed signer output; log and return empty. Also fix comment typos and use .empty().

 #ifdef ENABLE_EXTERNAL_SIGNER
-        std::vector<ExternalSigner> signers = {};
-        const std::string command = gArgs.GetArg("-signer", "");
-        if (command == "") return {};
-        ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+        std::vector<ExternalSigner> signers;
+        const std::string command = gArgs.GetArg("-signer", "");
+        if (command.empty()) return {};
+        try {
+            ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
+        } catch (const std::exception& e) {
+            LogPrintf("External signer enumerate failed: %s\n", e.what());
+            return {};
+        }
@@
 #else
-        // This result is undistinguisable from a succesful call that returns
+        // This result is indistinguishable from a successful call that returns
         // no signers. For the current GUI this doesn't matter, because the wallet
         // creation dialog disables the external signer checkbox in both
         // cases. The return type could be changed to std::optional<std::vector>
         // (or something that also includes error messages) if this distinction
         // becomes important.
         return {};
 #endif // ENABLE_EXTERNAL_SIGNER
src/external_signer.h (3)

54-59: Make GetDescriptors const

It doesn’t mutate state; mark const to improve API clarity.

-    UniValue GetDescriptors(const int account);
+    UniValue GetDescriptors(int account) const;

60-64: Make SignTransaction const

Method doesn’t modify members; declare const.

-    bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error);
+    bool SignTransaction(PartiallySignedTransaction& psbt, std::string& error) const;

42-53: Safer command construction (argv, not shell strings)

Public API embeds user-derived strings (descriptor, fingerprint) into shell command lines downstream. Switch RunCommandParseJSON to an argv-based exec (no shell), or add a robust escaping layer before concatenation. This prevents injection if unexpected characters appear.

src/external_signer.cpp (5)

19-22: Quote the chain flag to avoid shell splitting

Wrap chain value in quotes when building the argument.

-    return " --chain " + m_chain;
+    return " --chain \"" + m_chain + "\"";

47-51: Don’t abort enumeration on a duplicate

Use continue, not break, to keep processing remaining signers. Also avoid shadowing the signer name in the inner loop.

-        bool duplicate = false;
-        for (const ExternalSigner& signer : signers) {
-            if (signer.m_fingerprint.compare(fingerprintStr) == 0) duplicate = true;
-        }
-        if (duplicate) break;
+        bool duplicate = false;
+        for (const ExternalSigner& existing : signers) {
+            if (existing.m_fingerprint == fingerprintStr) { duplicate = true; break; }
+        }
+        if (duplicate) continue;

62-65: Command construction uses raw concatenation (injection risk)

descriptor and m_fingerprint are interpolated into a shell string executed by RunCommandParseJSON. Migrate to an argv-based RunCommandParseJSON overload (no shell), or strictly escape inputs. Short-term, ensure inputs are validated/escaped.


67-70: Same injection concern in GetDescriptors

Avoid embedding m_fingerprint directly into a shell string; use argv-based exec or strict escaping.


72-96: Same injection concern in SignTransaction command

m_fingerprint is embedded in the executed command. Use argv-based exec; keep PSBT only on stdin. Also consider quoting/escaping until argv refactor is done.

test/functional/wallet_signer.py (1)

119-132: Fix flake8 indentation and whitespace in list literal

The suggested indentation changes address E122, but the diff is incomplete—it omits the E231 whitespace fix required at line 122. The list [0,1] must be updated to [0, 1].

-        result = mock_wallet.importdescriptors([{
+        result = mock_wallet.importdescriptors([
+        {
             "desc": "pkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0/*)#6k3x80k9",
             "timestamp": 0,
-            "range": [0,1],
+            "range": [0, 1],
             "internal": False,
             "active": True
         },
         {
             "desc": "pkh([00000001/84'/1'/0']tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/*)#tz5866xa",
             "timestamp": 0,
             "range": [0, 0],
             "internal": True,
             "active": True
-        }])
+        }
+        ])
🧹 Nitpick comments (15)
src/util/system.cpp (1)

1397-1434: More robust stdout/stderr reads to handle multi-line JSON.

Current code reads only one line from each stream; prefer consuming entire streams to avoid truncation. Safe as a non-functional improvement.

-    std::string result;
-    std::string error;
-    std::getline(stdout_stream, result);
-    std::getline(stderr_stream, error);
+    std::string result{std::istreambuf_iterator<char>(stdout_stream), std::istreambuf_iterator<char>()};
+    std::string error{std::istreambuf_iterator<char>(stderr_stream), std::istreambuf_iterator<char>()};
ci/test/00_setup_env_win64.sh (1)

19-19: Consider isolating CI configuration changes.

The coding guidelines state: "Unless specifically prompted, avoid making changes to the ci directory." Given the PR's substantial scope (1,156 files changed), CI configuration changes may be better suited for a separate, focused PR with clear upstream Bitcoin commit references.

As per coding guidelines.

doc/external-signer.md (1)

141-145: Add languages to code fences and drop leading “$” prompts.

This satisfies MD040/MD014 and improves readability.

-```sh
-$ dashd -signer=../HWI/hwi.py
+```sh
+dashd -signer=../HWI/hwi.py
-```
-$ dash-cli enumeratesigners
+```sh
+dash-cli enumeratesigners
 {
   "signers": [
     {
       "fingerprint": "c8df832a"
     }
   ]
 }
-```
+```
-```sh
-$ dash-cli createwallet "hww" true true "" true true true
+```sh
+dash-cli createwallet "hww" true true "" true true true
-```sh
-$ dash-cli -rpcwallet=<wallet> getnewaddress
-$ dash-cli -rpcwallet=<wallet> walletdisplayaddress <address>
+```sh
+dash-cli -rpcwallet=<wallet> getnewaddress
+dash-cli -rpcwallet=<wallet> walletdisplayaddress <address>
-```sh
-$ dash-cli -rpcwallet=<wallet> sendtoaddress <address> <amount>
+```sh
+dash-cli -rpcwallet=<wallet> sendtoaddress <address> <amount>
-```
+```json
 {"complete": true, "txid": <txid>}


Also applies to: 147-149, 13-15, 25-33, 59-61, 80-87, 98-101, 115-118, 122-136

</blockquote></details>
<details>
<summary>src/interfaces/node.h (1)</summary><blockquote>

`166-168`: **Make ExternalSigner::getName() const noexcept.**

This improves API usability and signals non-throwing behavior.

```diff
-    virtual std::string getName() = 0;
+    virtual std::string getName() const noexcept = 0;
src/Makefile.am (1)

910-929: Gate external signer sources behind ENABLE_EXTERNAL_SIGNER.

To keep build lean when the feature is disabled, compile these only if enabled.

-  external_signer.cpp \
+if ENABLE_EXTERNAL_SIGNER
+  external_signer.cpp \
@@
-  rpc/external_signer.cpp \
+  rpc/external_signer.cpp \
+endif
-  wallet/external_signer_scriptpubkeyman.cpp \
+if ENABLE_EXTERNAL_SIGNER
+  wallet/external_signer_scriptpubkeyman.cpp \
+endif

Headers may remain listed in BITCOIN_CORE_H as they don’t affect linking.

Please ensure configure.ac defines AM_CONDITIONAL(ENABLE_EXTERNAL_SIGNER, ...).

Also applies to: 650-651, 224-224

test/functional/mocks/signer.py (2)

11-19: Harden perform_pre_checks() against empty/short files.

 def perform_pre_checks():
     mock_result_path = os.path.join(os.getcwd(), "mock_result")
-    if(os.path.isfile(mock_result_path)):
-        with open(mock_result_path, "r", encoding="utf8") as f:
-            mock_result = f.read()
-        if mock_result[0]:
-            sys.stdout.write(mock_result[2:])
-            sys.exit(int(mock_result[0]))
+    if os.path.isfile(mock_result_path):
+        with open(mock_result_path, "r", encoding="utf8") as f:
+            first = f.readline().strip()
+            payload = f.read()
+        try:
+            code = int(first)
+        except Exception:
+            code = 1
+        if payload:
+            sys.stdout.write(payload)
+        sys.exit(code)

69-76: Optional: silence unused-arg warning in enumerate().

-parser_enumerate.set_defaults(func=enumerate)
+parser_enumerate.set_defaults(func=lambda a: enumerate(a))

Or prefix arg with “_args” to signal intentional unused.

src/wallet/external_signer_scriptpubkeyman.cpp (1)

42-50: Consider caching the selected signer.

GetExternalSigner() enumerates on each call; caching the chosen device (fingerprint) would reduce latency and avoid device churn between calls.

Also applies to: 78-85

src/wallet/wallet.h (2)

30-31: Avoid heavy include from a public header

wallet.h doesn’t need <external_signer.h>; implementation includes it. Dropping it reduces compile times and coupling.

-#include <external_signer.h>

566-568: Make DisplayAddress const (pure query)

The method doesn’t mutate state; making it const expands usability and matches other query methods.

-    bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+    bool DisplayAddress(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);

Please confirm no callers rely on non-const; adjust the definition in wallet.cpp accordingly.

src/wallet/wallet.cpp (2)

2505-2518: Harden DisplayAddress against unexpected states and exceptions

Add an early flag check and catch runtime errors from signer discovery/invocation to avoid unexpected crashes; return false and log instead.

 bool CWallet::DisplayAddress(const CTxDestination& dest)
 {
     CScript scriptPubKey = GetScriptForDestination(dest);
     const auto spk_man = GetScriptPubKeyMan(scriptPubKey);
     if (spk_man == nullptr) {
         return false;
     }
     auto signer_spk_man = dynamic_cast<ExternalSignerScriptPubKeyMan*>(spk_man);
     if (signer_spk_man == nullptr) {
         return false;
     }
-    ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
-    return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+    if (!IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) return false;
+    try {
+        ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
+        return signer_spk_man->DisplayAddress(scriptPubKey, signer);
+    } catch (const std::exception& e) {
+        WalletLogPrintf("DisplayAddress failed: %s\n", e.what());
+        return false;
+    }
 }

3852-3880: Descriptor parsing from external source: consider requiring checksums

Parsers currently allow descriptors without checksum (last arg false). Given input is external, prefer strictness to avoid subtle mismatches.

-                std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, desc_error, false);
+                std::unique_ptr<Descriptor> desc = Parse(desc_str, keys, desc_error, /*require_checksum=*/true);

If mocks don’t include checksums yet, gate this under a flag or update tests accordingly.

src/external_signer.h (1)

27-28: Minor: drop const on value return type

Returning const std::string adds no benefit and inhibits moves. Prefer std::string NetworkArg() const;.

-    const std::string NetworkArg() const;
+    std::string NetworkArg() const;
test/functional/wallet_signer.py (1)

103-108: Fix flake8 indentation for multiline call

Align continuation lines and closing paren.

-        assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
-            hww.walletdisplayaddress, address1
-        )
+        assert_raises_rpc_error(
+            -1,
+            'RunCommandParseJSON error',
+            hww.walletdisplayaddress,
+            address1,
+        )
src/qt/sendcoinsdialog.cpp (1)

229-240: UX copy: refine tooltip vs enabled state

If a signer path is configured, enabling the button while showing “Connect your hardware wallet first.” is contradictory. Consider enabling with a “Ready to sign on device” tooltip; when unset, disable with the current “Set external signer…” message.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4004df4b14186f0f46ff69c95ea74a2fbf85d81 and a7c7318e407f4816f196d0ac649245d8d9e068db.

📒 Files selected for processing (68)
  • ci/test/00_setup_env_arm.sh (1 hunks)
  • ci/test/00_setup_env_mac.sh (1 hunks)
  • ci/test/00_setup_env_mac_native_x86_64.sh (1 hunks)
  • ci/test/00_setup_env_native_asan.sh (1 hunks)
  • ci/test/00_setup_env_native_fuzz.sh (1 hunks)
  • ci/test/00_setup_env_native_multiprocess.sh (1 hunks)
  • ci/test/00_setup_env_native_nowallet.sh (1 hunks)
  • ci/test/00_setup_env_native_qt5.sh (1 hunks)
  • ci/test/00_setup_env_native_tsan.sh (1 hunks)
  • ci/test/00_setup_env_s390x.sh (1 hunks)
  • ci/test/00_setup_env_win64.sh (1 hunks)
  • configure.ac (7 hunks)
  • doc/Doxyfile.in (1 hunks)
  • doc/external-signer.md (1 hunks)
  • src/Makefile.am (5 hunks)
  • src/Makefile.test.include (1 hunks)
  • src/dummywallet.cpp (1 hunks)
  • src/external_signer.cpp (1 hunks)
  • src/external_signer.h (1 hunks)
  • src/interfaces/node.h (2 hunks)
  • src/interfaces/wallet.h (2 hunks)
  • src/node/interfaces.cpp (3 hunks)
  • src/qt/createwalletdialog.cpp (4 hunks)
  • src/qt/createwalletdialog.h (2 hunks)
  • src/qt/forms/createwalletdialog.ui (2 hunks)
  • src/qt/forms/optionsdialog.ui (1 hunks)
  • src/qt/forms/receiverequestdialog.ui (1 hunks)
  • src/qt/optionsdialog.cpp (3 hunks)
  • src/qt/optionsmodel.cpp (3 hunks)
  • src/qt/optionsmodel.h (1 hunks)
  • src/qt/receiverequestdialog.cpp (1 hunks)
  • src/qt/sendcoinsdialog.cpp (7 hunks)
  • src/qt/walletcontroller.cpp (4 hunks)
  • src/qt/walletmodel.cpp (2 hunks)
  • src/qt/walletmodel.h (1 hunks)
  • src/qt/walletmodeltransaction.cpp (1 hunks)
  • src/qt/walletmodeltransaction.h (1 hunks)
  • src/rpc/client.cpp (1 hunks)
  • src/rpc/external_signer.cpp (1 hunks)
  • src/rpc/register.h (2 hunks)
  • src/test/system_tests.cpp (2 hunks)
  • src/util/error.cpp (1 hunks)
  • src/util/error.h (1 hunks)
  • src/util/system.cpp (3 hunks)
  • src/util/system.h (0 hunks)
  • src/wallet/external_signer_scriptpubkeyman.cpp (1 hunks)
  • src/wallet/external_signer_scriptpubkeyman.h (1 hunks)
  • src/wallet/init.cpp (1 hunks)
  • src/wallet/interfaces.cpp (2 hunks)
  • src/wallet/rpc/addresses.cpp (1 hunks)
  • src/wallet/rpc/backup.cpp (1 hunks)
  • src/wallet/rpc/spend.cpp (1 hunks)
  • src/wallet/rpc/wallet.cpp (6 hunks)
  • src/wallet/scriptpubkeyman.h (2 hunks)
  • src/wallet/wallet.cpp (9 hunks)
  • src/wallet/wallet.h (5 hunks)
  • src/wallet/walletdb.cpp (1 hunks)
  • src/wallet/walletdb.h (1 hunks)
  • src/wallet/walletutil.h (1 hunks)
  • test/config.ini.in (1 hunks)
  • test/functional/mocks/invalid_signer.py (1 hunks)
  • test/functional/mocks/signer.py (1 hunks)
  • test/functional/rpc_help.py (1 hunks)
  • test/functional/rpc_signer.py (1 hunks)
  • test/functional/test_framework/test_framework.py (1 hunks)
  • test/functional/test_framework/test_node.py (1 hunks)
  • test/functional/test_runner.py (1 hunks)
  • test/functional/wallet_signer.py (1 hunks)
💤 Files with no reviewable changes (1)
  • src/util/system.h
🚧 Files skipped from review as they are similar to previous changes (27)
  • src/wallet/init.cpp
  • src/rpc/external_signer.cpp
  • src/qt/createwalletdialog.cpp
  • src/dummywallet.cpp
  • src/wallet/rpc/backup.cpp
  • src/qt/optionsmodel.cpp
  • src/wallet/walletutil.h
  • ci/test/00_setup_env_arm.sh
  • ci/test/00_setup_env_native_tsan.sh
  • src/rpc/register.h
  • src/rpc/client.cpp
  • doc/Doxyfile.in
  • ci/test/00_setup_env_native_qt5.sh
  • src/qt/forms/receiverequestdialog.ui
  • src/wallet/walletdb.h
  • src/wallet/walletdb.cpp
  • src/qt/walletmodeltransaction.cpp
  • ci/test/00_setup_env_s390x.sh
  • test/functional/rpc_help.py
  • src/qt/forms/optionsdialog.ui
  • test/functional/test_runner.py
  • src/qt/createwalletdialog.h
  • test/functional/test_framework/test_node.py
  • src/qt/walletmodel.h
  • src/wallet/scriptpubkeyman.h
  • src/wallet/rpc/wallet.cpp
  • src/wallet/interfaces.cpp
🧰 Additional context used
📓 Path-based instructions (5)
ci/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the ci directory (continuous integration)

Files:

  • ci/test/00_setup_env_native_nowallet.sh
  • ci/test/00_setup_env_mac.sh
  • ci/test/00_setup_env_native_multiprocess.sh
  • ci/test/00_setup_env_mac_native_x86_64.sh
  • ci/test/00_setup_env_native_asan.sh
  • ci/test/00_setup_env_win64.sh
  • ci/test/00_setup_env_native_fuzz.sh
src/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.{cpp,h,cc,cxx,hpp}: Dash Core C++ codebase must be written in C++20 and require at least Clang 16 or GCC 11.1
Dash uses unordered_lru_cache for efficient caching with LRU eviction

Files:

  • src/interfaces/node.h
  • src/interfaces/wallet.h
  • src/util/error.h
  • src/qt/walletmodel.cpp
  • src/wallet/wallet.h
  • src/external_signer.cpp
  • src/qt/walletcontroller.cpp
  • src/util/system.cpp
  • src/wallet/rpc/spend.cpp
  • src/util/error.cpp
  • src/qt/receiverequestdialog.cpp
  • src/wallet/external_signer_scriptpubkeyman.cpp
  • src/qt/optionsdialog.cpp
  • src/external_signer.h
  • src/wallet/wallet.cpp
  • src/qt/sendcoinsdialog.cpp
  • src/qt/walletmodeltransaction.h
  • src/wallet/external_signer_scriptpubkeyman.h
  • src/qt/optionsmodel.h
  • src/wallet/rpc/addresses.cpp
  • src/test/system_tests.cpp
  • src/node/interfaces.cpp
test/functional/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

Functional tests should be written in Python and placed in test/functional/

Files:

  • test/functional/mocks/invalid_signer.py
  • test/functional/wallet_signer.py
  • test/functional/mocks/signer.py
  • test/functional/rpc_signer.py
  • test/functional/test_framework/test_framework.py
src/{test,wallet/test,qt/test}/**/*.{cpp,h,cc,cxx,hpp}

📄 CodeRabbit inference engine (CLAUDE.md)

Unit tests for C++ code should be placed in src/test/, src/wallet/test/, or src/qt/test/ and use Boost::Test or Qt 5 for GUI tests

Files:

  • src/test/system_tests.cpp
doc/**

📄 CodeRabbit inference engine (CLAUDE.md)

Unless specifically prompted, avoid making changes to the doc directory (documentation)

Files:

  • doc/external-signer.md
🧬 Code graph analysis (18)
src/interfaces/node.h (2)
src/external_signer.cpp (1)
  • ExternalSigner (17-17)
src/external_signer.h (1)
  • ExternalSigner (18-64)
src/interfaces/wallet.h (2)
src/qt/walletmodel.cpp (2)
  • displayAddress (582-592)
  • displayAddress (582-582)
src/wallet/interfaces.cpp (12)
  • dest (209-213)
  • dest (209-209)
  • dest (222-225)
  • dest (222-222)
  • dest (226-229)
  • dest (226-226)
  • dest (230-248)
  • dest (230-233)
  • dest (263-267)
  • dest (263-263)
  • dest (268-272)
  • dest (268-268)
src/qt/walletmodel.cpp (1)
src/wallet/interfaces.cpp (28)
  • dest (209-213)
  • dest (209-209)
  • dest (222-225)
  • dest (222-222)
  • dest (226-229)
  • dest (226-226)
  • dest (230-248)
  • dest (230-233)
  • dest (263-267)
  • dest (263-263)
  • dest (268-272)
  • dest (268-268)
  • m_wallet (157-160)
  • m_wallet (165-165)
  • m_wallet (174-174)
  • m_wallet (175-175)
  • m_wallet (181-181)
  • m_wallet (182-182)
  • m_wallet (442-445)
  • m_wallet (446-449)
  • m_wallet (521-521)
  • m_wallet (522-522)
  • m_wallet (523-523)
  • m_wallet (524-524)
  • m_wallet (525-525)
  • m_wallet (526-526)
  • m_wallet (531-531)
  • m_wallet (591-591)
src/wallet/wallet.h (5)
src/external_signer.cpp (2)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
src/wallet/wallet.cpp (4)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
src/wallet/external_signer_scriptpubkeyman.cpp (4)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • FillPSBT (64-85)
  • FillPSBT (64-64)
src/wallet/interfaces.cpp (12)
  • dest (209-213)
  • dest (209-209)
  • dest (222-225)
  • dest (222-222)
  • dest (226-229)
  • dest (226-226)
  • dest (230-248)
  • dest (230-233)
  • dest (263-267)
  • dest (263-263)
  • dest (268-272)
  • dest (268-268)
src/wallet/scriptpubkeyman.cpp (4)
  • FillPSBT (734-778)
  • FillPSBT (734-734)
  • FillPSBT (2288-2355)
  • FillPSBT (2288-2288)
src/external_signer.cpp (3)
src/external_signer.h (1)
  • ExternalSigner (18-64)
src/util/system.cpp (2)
  • RunCommandParseJSON (1397-1434)
  • RunCommandParseJSON (1397-1397)
src/wallet/external_signer_scriptpubkeyman.cpp (2)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
src/util/system.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/util/error.cpp (1)
src/util/translation.h (1)
  • Untranslated (48-48)
src/wallet/external_signer_scriptpubkeyman.cpp (3)
src/wallet/wallet.cpp (24)
  • batch (550-550)
  • batch (815-815)
  • batch (938-938)
  • batch (1178-1178)
  • batch (1257-1257)
  • batch (1313-1313)
  • batch (1358-1358)
  • batch (1370-1370)
  • batch (1391-1391)
  • batch (1515-1515)
  • batch (1660-1660)
  • batch (1744-1744)
  • batch (1793-1793)
  • batch (2239-2239)
  • batch (2300-2300)
  • batch (2307-2307)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • GetSolvingProvider (3734-3738)
  • GetSolvingProvider (3734-3734)
  • GetSolvingProvider (3740-3748)
  • GetSolvingProvider (3740-3740)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
src/wallet/scriptpubkeyman.cpp (16)
  • GetID (801-804)
  • GetID (801-801)
  • GetID (2375-2379)
  • GetID (2375-2375)
  • TopUp (1410-1413)
  • TopUp (1410-1410)
  • TopUp (1943-2004)
  • TopUp (1943-1943)
  • GetSolvingProvider (678-681)
  • GetSolvingProvider (678-678)
  • GetSolvingProvider (2231-2234)
  • GetSolvingProvider (2231-2231)
  • FillPSBT (734-778)
  • FillPSBT (734-734)
  • FillPSBT (2288-2355)
  • FillPSBT (2288-2288)
src/external_signer.cpp (4)
  • Enumerate (24-60)
  • Enumerate (24-24)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
test/functional/wallet_signer.py (6)
test/functional/test_framework/test_framework.py (6)
  • skip_test_if_missing_module (423-425)
  • skip_test_if_missing_module (1477-1478)
  • skip_if_no_external_signer (1106-1109)
  • skip_if_no_wallet (1063-1071)
  • restart_node (694-697)
  • is_bdb_compiled (1147-1149)
test/functional/rpc_signer.py (4)
  • mock_signer_path (21-26)
  • skip_test_if_missing_module (38-39)
  • set_mock_result (41-43)
  • clear_mock_result (45-46)
src/wallet/rpc/wallet.cpp (16)
  • res (591-591)
  • createwallet (615-713)
  • createwallet (615-615)
  • getwalletinfo (152-276)
  • getwalletinfo (152-152)
  • setwalletflag (547-613)
  • setwalletflag (547-547)
  • getnewaddress (1098-1098)
  • getaddressinfo (1097-1097)
  • result (308-308)
  • result (766-766)
  • result (1088-1088)
  • walletdisplayaddress (1108-1108)
  • walletcreatefundedpsbt (1151-1151)
  • walletprocesspsbt (1150-1150)
  • send (1149-1149)
test/functional/test_framework/test_node.py (2)
  • createwallet (884-889)
  • get_wallet_rpc (363-369)
src/wallet/rpc/addresses.cpp (10)
  • getnewaddress (17-56)
  • getnewaddress (17-17)
  • getaddressinfo (418-559)
  • getaddressinfo (418-418)
  • result (254-254)
  • result (703-703)
  • walletdisplayaddress (670-708)
  • walletdisplayaddress (670-670)
  • dest (382-382)
  • dest (382-382)
src/wallet/rpc/spend.cpp (11)
  • result (631-631)
  • result (725-725)
  • result (919-919)
  • result (1014-1014)
  • result (1164-1164)
  • walletcreatefundedpsbt (1025-1171)
  • walletcreatefundedpsbt (1025-1025)
  • walletprocesspsbt (946-1023)
  • walletprocesspsbt (946-946)
  • send (732-944)
  • send (732-732)
src/qt/optionsdialog.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/external_signer.h (3)
src/external_signer.cpp (11)
  • ExternalSigner (17-17)
  • NetworkArg (19-22)
  • NetworkArg (19-19)
  • Enumerate (24-60)
  • Enumerate (24-24)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
  • GetDescriptors (67-70)
  • GetDescriptors (67-67)
  • SignTransaction (72-117)
  • SignTransaction (72-72)
src/wallet/wallet.cpp (6)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • SignTransaction (1998-2015)
  • SignTransaction (1998-1998)
  • SignTransaction (2017-2030)
  • SignTransaction (2017-2017)
src/wallet/external_signer_scriptpubkeyman.cpp (2)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
src/wallet/wallet.cpp (4)
src/util/translation.h (1)
  • Untranslated (48-48)
src/external_signer.cpp (2)
  • DisplayAddress (62-65)
  • DisplayAddress (62-62)
src/wallet/external_signer_scriptpubkeyman.cpp (4)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • GetExternalSigner (42-50)
  • GetExternalSigner (42-42)
src/wallet/interfaces.cpp (12)
  • dest (209-213)
  • dest (209-209)
  • dest (222-225)
  • dest (222-222)
  • dest (226-229)
  • dest (226-226)
  • dest (230-248)
  • dest (230-233)
  • dest (263-267)
  • dest (263-263)
  • dest (268-272)
  • dest (268-268)
src/qt/walletmodeltransaction.h (1)
src/qt/walletmodeltransaction.cpp (2)
  • setWtx (29-32)
  • setWtx (29-29)
src/wallet/external_signer_scriptpubkeyman.h (4)
src/wallet/scriptpubkeyman.h (6)
  • wallet (28-241)
  • DescriptorScriptPubKeyMan (512-634)
  • WalletStorage (34-234)
  • bool (179-179)
  • bool (218-218)
  • bool (224-224)
src/wallet/walletutil.h (1)
  • WalletDescriptor (70-103)
src/wallet/external_signer_scriptpubkeyman.cpp (8)
  • SetupDescriptor (17-40)
  • SetupDescriptor (17-17)
  • GetExternalSigner (42-50)
  • GetExternalSigner (42-42)
  • DisplayAddress (52-61)
  • DisplayAddress (52-52)
  • FillPSBT (64-85)
  • FillPSBT (64-64)
src/wallet/wallet.cpp (4)
  • DisplayAddress (2505-2518)
  • DisplayAddress (2505-2505)
  • FillPSBT (2032-2081)
  • FillPSBT (2032-2032)
test/functional/rpc_signer.py (4)
test/functional/test_framework/test_framework.py (1)
  • skip_if_no_external_signer (1106-1109)
test/functional/test_framework/util.py (2)
  • assert_equal (69-74)
  • assert_raises_rpc_error (132-148)
test/functional/wallet_signer.py (5)
  • mock_signer_path (21-26)
  • set_test_params (35-41)
  • skip_test_if_missing_module (43-45)
  • set_mock_result (47-49)
  • clear_mock_result (51-52)
src/rpc/external_signer.cpp (3)
  • enumeratesigners (16-59)
  • enumeratesigners (16-16)
  • result (54-54)
src/wallet/rpc/addresses.cpp (2)
src/wallet/rpc/util.cpp (2)
  • GetWalletForJSONRPCRequest (55-77)
  • GetWalletForJSONRPCRequest (55-55)
src/wallet/interfaces.cpp (18)
  • LOCK (249-258)
  • LOCK (259-262)
  • LOCK (290-294)
  • LOCK (295-299)
  • LOCK (352-360)
  • LOCK (478-490)
  • dest (209-213)
  • dest (209-209)
  • dest (222-225)
  • dest (222-222)
  • dest (226-229)
  • dest (226-226)
  • dest (230-248)
  • dest (230-233)
  • dest (263-267)
  • dest (263-263)
  • dest (268-272)
  • dest (268-268)
src/test/system_tests.cpp (1)
src/node/interfaces.cpp (1)
  • ENABLE_EXTERNAL_SIGNER (530-551)
src/node/interfaces.cpp (1)
src/external_signer.cpp (2)
  • Enumerate (24-60)
  • Enumerate (24-24)
🪛 Flake8 (7.3.0)
test/functional/wallet_signer.py

[error] 106-106: continuation line under-indented for visual indent

(E128)


[error] 107-107: closing bracket does not match visual indentation

(E124)


[error] 126-126: continuation line missing indentation or outdented

(E122)

test/functional/mocks/signer.py

[error] 21-21: indentation is not a multiple of 4

(E111)


[error] 96-96: indentation is not a multiple of 4

(E111)

test/functional/rpc_signer.py

[error] 52-52: continuation line under-indented for visual indent

(E128)


[error] 57-57: continuation line under-indented for visual indent

(E128)


[error] 63-63: continuation line under-indented for visual indent

(E128)


[error] 69-69: continuation line under-indented for visual indent

(E128)

🪛 LanguageTool
doc/external-signer.md

[style] ~70-~70: Consider a more concise word here.
Context: ...ue, "txid": } ``` ## Signer API In order to be compatible with Dash Core any signer...

(IN_ORDER_TO_PREMIUM)


[grammar] ~167-~167: Use a hyphen to join words.
Context: ... support address types, in a BIP44/49/84 compatible manner. The `walletdisplayad...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
doc/external-signer.md

14-14: Dollar signs used before commands without showing output

(MD014, commands-show-output)


25-25: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


40-40: Dollar signs used before commands without showing output

(MD014, commands-show-output)


48-48: Dollar signs used before commands without showing output

(MD014, commands-show-output)


49-49: Dollar signs used before commands without showing output

(MD014, commands-show-output)


59-59: Dollar signs used before commands without showing output

(MD014, commands-show-output)


80-80: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


98-98: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


115-115: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


122-122: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


141-141: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


147-147: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.14.1)
test/functional/mocks/invalid_signer.py

20-20: Unused function argument: args

(ARG001)

test/functional/mocks/signer.py

20-20: Unused function argument: args

(ARG001)

test/functional/test_framework/test_framework.py

1109-1109: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (23)
src/qt/optionsmodel.h (1)

76-76: LGTM!

The addition of ExternalSignerPath to the OptionID enum is consistent with the external signer feature integration and properly typed as QString.

src/util/error.h (1)

33-34: LGTM!

The addition of EXTERNAL_SIGNER_NOT_FOUND and EXTERNAL_SIGNER_FAILED to the TransactionError enum appropriately extends error handling for the external signer feature.

test/config.ini.in (1)

26-26: LGTM!

The addition of ENABLE_EXTERNAL_SIGNER to the components section follows the established pattern for feature flags and appropriately enables external signer support in the test environment.

src/qt/walletmodeltransaction.h (1)

30-31: LGTM!

The addition of the setWtx() method complements the existing getWtx() accessor and follows standard setter patterns. The implementation is straightforward and correct.

src/qt/forms/createwalletdialog.ui (1)

112-121: LGTM!

The external signer checkbox is properly integrated into the Advanced Options section with clear tooltip guidance for users.

src/util/error.cpp (1)

36-39: LGTM!

The new external signer error strings are clear and follow the established pattern in this function.

ci/test/00_setup_env_mac_native_x86_64.sh (1)

13-13: LGTM!

Removal of --with-boost-process aligns with the architectural shift to external signer support.

ci/test/00_setup_env_mac.sh (1)

17-17: LGTM!

Consistent with the removal of Boost.Process dependency across CI configurations.

src/interfaces/wallet.h (1)

154-155: LGTM!

The new interface methods are well-positioned and follow the established pattern for wallet interface extensions. The placement near related functionality (address operations and key management) enhances code organization.

Also applies to: 294-295

test/functional/test_framework/test_framework.py (1)

1106-1117: LGTM!

The new test framework methods follow the established pattern for feature detection and test skipping. The static analysis hint about message length is a false positive—the message is appropriately concise for a SkipTest exception.

src/qt/walletmodel.cpp (1)

582-592: LGTM!

The displayAddress implementation properly handles errors with appropriate user feedback via QMessageBox. The try-catch block ensures graceful handling of runtime errors from the underlying wallet interface.

doc/external-signer.md (2)

66-67: Double-check sendtoaddress output shape.

Example shows {"complete": true, "txid": ...}. dash-cli sendtoaddress typically returns a txid string. Please confirm actual output and adjust the example accordingly.


27-36: Fix descriptor types in getdescriptors example.

Wrapped SegWit must be sh(wpkh(...)), and native SegWit must be wpkh(...). Current text shows pkh in those slots.

-            "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
-            "sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
-            "pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
+            "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
+            "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
+            "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
@@
-            "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
-            "sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
-            "pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
+            "pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
+            "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
+            "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"

Likely an incorrect or invalid review comment.

src/interfaces/node.h (1)

234-236: API surface change: ensure all Node impls override listExternalSigners().

Header adds a new pure virtual. Confirm all Node implementations are updated (node/interfaces.cpp shows an override).

src/qt/optionsdialog.cpp (1)

141-145: UI wiring looks good; verify OptionsModel plumbed.

  • Gating and tooltip under !ENABLE_EXTERNAL_SIGNER are correct.
  • Restart warning hook on externalSignerPath is consistent.
  • Data mapper adds ExternalSignerPath.

Please confirm OptionsModel::ExternalSignerPath exists, persists, and is included in save/load.

Also applies to: 277-277, 344-345

ci/test/00_setup_env_native_fuzz.sh (1)

18-18: Confirm intent after dropping Boost.Process in fuzz config.

Ensure this job doesn’t rely on Boost.Process-dependent bits and that sanitizer/fuzz targets compile without external signer support.

ci/test/00_setup_env_native_multiprocess.sh (1)

16-16: Verify external signer build behavior or explicitly disable it.

The analysis confirms your concern is valid. When --with-boost-process is removed:

  1. External signer defaults to enabled (line 330 of configure.ac: use_external_signer=yes)
  2. Build will check for boost/process.hpp (line 1579: AC_COMPILE_IFELSE test)
  3. Build fails if Boost::Process is unavailable (line 1584: AC_MSG_ERROR if missing)

The CI job will either:

  • Succeed if the CI environment has Boost::Process headers available, OR
  • Fail at configure time if not

To clarify intent, either:

  • Confirm the CI environment has boost/process.hpp and document this dependency, OR
  • Add --disable-external-signer to explicitly skip this feature for this job
src/wallet/wallet.cpp (2)

315-335: Good preflight validation for external signer wallets

Enforcing “disable private keys” and “descriptors” when WALLET_FLAG_EXTERNAL_SIGNER is set prevents invalid configurations. LGTM.


3805-3811: SPKM type selection via wallet flag is correct

Selecting ExternalSignerScriptPubKeyMan when WALLET_FLAG_EXTERNAL_SIGNER is set is the right split. LGTM.

src/node/interfaces.cpp (2)

17-17: LGTM include

Header include is fine; guarded usage below prevents ODR/link issues when feature is disabled.


402-411: LGTM: thin wrapper

Wrapper forwards name and moves the underlying signer; OK.

src/qt/sendcoinsdialog.cpp (2)

383-389: LGTM: clearer PSBT-only prompt

Conditionally clarifies draft/PSBT flow when no external signer.


515-603: LGTM: safe PSBT flow

Fill without signing first, then sign if external signer, finalize/broadcast if complete; good error surfacing.
Please verify a tx-complete path also emits coinsSent or equivalent UI feedback when broadcast occurs via this branch.

Comment on lines +19 to +20
Follow the hardware manufacturers instructions for the initial device setup, as well as their instructions for creating a backup. Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/dashpay/HWI).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update second HWI link to bitcoin-core.

-Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/dashpay/HWI).
+Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/bitcoin-core/HWI).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Follow the hardware manufacturers instructions for the initial device setup, as well as their instructions for creating a backup. Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/dashpay/HWI).
Follow the hardware manufacturers instructions for the initial device setup, as well as their instructions for creating a backup. Alternatively, for some devices, you can use the `setup`, `restore` and `backup` commands provided by [HWI](https://github.com/bitcoin-core/HWI).
🤖 Prompt for AI Agents
In doc/external-signer.md around lines 19 to 20, the second HWI link points to
the dashpay/HWI repo; update that link to the bitcoin-core HWI repository by
replacing the URL https://github.com/dashpay/HWI with
https://github.com/bitcoin-core/HWI so the documentation references the official
Bitcoin Core HWI project.

Comment on lines +70 to +71

In order to be compatible with Dash Core any signer command should conform to the specification below. This specification is subject to change. Ideally a BIP should propose a standard so that other wallets can also make use of it.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten wording.

-In order to be compatible with Dash Core any signer command should conform to the specification below.
+To be compatible with Dash Core, any signer command must conform to the specification below.
🧰 Tools
🪛 LanguageTool

[style] ~70-~70: Consider a more concise word here.
Context: ...ue, "txid": } ``` ## Signer API In order to be compatible with Dash Core any signer...

(IN_ORDER_TO_PREMIUM)

🤖 Prompt for AI Agents
In doc/external-signer.md around lines 70 to 71, tighten the wording of the
paragraph to be more precise and directive: replace the tentative phrasing with
an active, concise sentence such as "To be compatible with Dash Core, signer
commands must conform to the specification below (subject to change);" and
follow with a separate clear sentence like "Ideally, a BIP should propose this
as a standard so other wallets can implement it." Ensure use of "must" for
compatibility requirement, remove vague words like "any" and "should" in the
compatibility clause, and split into two sentences for clarity.

Comment on lines +167 to +169
It then imports descriptors for all support address types, in a BIP44/49/84 compatible manner.

The `walletdisplayaddress` RPC reuses some code from `getaddressinfo` on the provided address and obtains the inferred descriptor. It then calls `<cmd> --fingerprint=00000000 displayaddress --desc=<descriptor>`.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hyphenate compound modifier.

-It then imports descriptors for all support address types, in a BIP44/49/84 compatible manner.
+It then imports descriptors for all supported address types in a BIP44/49/84-compatible manner.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
It then imports descriptors for all support address types, in a BIP44/49/84 compatible manner.
The `walletdisplayaddress` RPC reuses some code from `getaddressinfo` on the provided address and obtains the inferred descriptor. It then calls `<cmd> --fingerprint=00000000 displayaddress --desc=<descriptor>`.
It then imports descriptors for all supported address types in a BIP44/49/84-compatible manner.
The `walletdisplayaddress` RPC reuses some code from `getaddressinfo` on the provided address and obtains the inferred descriptor. It then calls `<cmd> --fingerprint=00000000 displayaddress --desc=<descriptor>`.
🧰 Tools
🪛 LanguageTool

[grammar] ~167-~167: Use a hyphen to join words.
Context: ... support address types, in a BIP44/49/84 compatible manner. The `walletdisplayad...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In doc/external-signer.md around lines 167 to 169, hyphenate the compound
modifier "BIP44/49/84 compatible manner" to "BIP44/49/84-compatible manner" so
the adjective correctly modifies "manner"; update that phrase in the sentence
describing imported descriptors and leave the rest of the wording unchanged.

#include <string>
#include <vector>

ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name): m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Validate inputs early (fingerprint/chain)

Reject invalid m_fingerprint (non-hex or wrong length) and unknown m_chain in ctor to reduce downstream risk.

-ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name): m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name) {}
+ExternalSigner::ExternalSigner(const std::string& command, const std::string chain, const std::string& fingerprint, const std::string name)
+    : m_command(command), m_chain(chain), m_fingerprint(fingerprint), m_name(name)
+{
+    if (!(m_chain == "main" || m_chain == "test" || m_chain == "regtest" || m_chain == "signet")) {
+        throw std::invalid_argument("ExternalSigner: invalid chain");
+    }
+    if (!IsHex(m_fingerprint) || m_fingerprint.size() != 8) {
+        throw std::invalid_argument("ExternalSigner: invalid fingerprint (expected 8 hex chars)");
+    }
+}
🤖 Prompt for AI Agents
In src/external_signer.cpp around line 17, the constructor currently accepts any
fingerprint and chain; add early validation to reject invalid inputs by
verifying the fingerprint is hex and the expected length (e.g., 64 characters
for a 32-byte fingerprint or the project-specific length) and that the chain
string is one of the known/allowed values (e.g., "mainnet", "testnet", etc.). If
validation fails, throw a std::invalid_argument with a clear message indicating
whether the fingerprint format/length is wrong or the chain is unknown; perform
these checks before initializing member fields so invalid objects are never
created.

Comment on lines +296 to +380
std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
try {
signers = node().listExternalSigners();
} catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
m_create_wallet_dialog->setSigners(signers);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Move unique_ptr signers into the dialog to fix compilation

std::vector<std::unique_ptr<...>> is move-only. Passing by copy won’t compile. Move it.

-    m_create_wallet_dialog->setSigners(signers);
+    m_create_wallet_dialog->setSigners(std::move(signers));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
try {
signers = node().listExternalSigners();
} catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
m_create_wallet_dialog->setSigners(signers);
std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
try {
signers = node().listExternalSigners();
} catch (const std::runtime_error& e) {
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
}
m_create_wallet_dialog->setSigners(std::move(signers));
🤖 Prompt for AI Agents
In src/qt/walletcontroller.cpp around lines 296 to 303, the local variable
signers is a std::vector of move-only std::unique_ptrs and cannot be copied into
the dialog; change the call to transfer ownership by moving the vector into the
dialog (use std::move on signers when calling setSigners) so the dialog receives
the vector by move rather than by copy.

Comment on lines +27 to +36
"receive": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
"sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
"pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
],
"internal": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
"sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
"pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Correct descriptor types in getdescriptors mock response.

Align with wrapped/native SegWit.

-            "sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
-            "pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
+            "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
+            "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
@@
-            "sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
-            "pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
+            "sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
+            "wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"receive": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
"sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
"pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
],
"internal": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
"sh(pkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
"pkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
]
"receive": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/0/*)#vt6w3l3j",
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/0/*))#az7uyg3n",
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/0/*)#58ssc2nq"
],
"internal": [
"pkh([00000001/44'/1'/" + args.account + "']" + xpub + "/1/*)#all0v2p2",
"sh(wpkh([00000001/49'/1'/" + args.account + "']" + xpub + "/1/*))#mpkel968",
"wpkh([00000001/84'/1'/" + args.account + "']" + xpub + "/1/*)#9n439lrc"
]
🤖 Prompt for AI Agents
In test/functional/mocks/signer.py around lines 27 to 36, the mock
getdescriptors response is using incorrect descriptor types (e.g., sh(pkh(...))
and pkh(...) for SegWit xpubs); update the descriptors to use correct wrapped
and native SegWit forms: keep legacy 44' as pkh(...), change 49' to
sh(wpkh(...)) for P2SH-P2WPKH (wrapped SegWit), and change 84' to wpkh(...) for
native SegWit, applying the same replacements for both the "receive" (/0/*) and
"internal" (/1/*) entries so the mock matches real descriptor types.

Comment on lines +88 to +92
parser_signtx = subparsers.add_parser('signtx')
parser_signtx.add_argument('psbt', metavar='psbt')

parser_signtx.set_defaults(func=signtx)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Command mismatch: use “signtransaction” (and keep “signtx” as alias).

Core calls signtransaction; the mock uses signtx. This will break tests/integration.

-def signtx(args):
+def signtransaction(args):
@@
-parser_signtx = subparsers.add_parser('signtx')
-parser_signtx.add_argument('psbt', metavar='psbt')
-
-parser_signtx.set_defaults(func=signtx)
+parser_signtransaction = subparsers.add_parser('signtransaction')
+parser_signtransaction.add_argument('psbt', metavar='psbt')
+parser_signtransaction.set_defaults(func=signtransaction)
+
+# Back-compat alias
+parser_signtx = subparsers.add_parser('signtx')
+parser_signtx.add_argument('psbt', metavar='psbt')
+parser_signtx.set_defaults(func=signtransaction)

Also applies to: 54-68

🤖 Prompt for AI Agents
In test/functional/mocks/signer.py around lines 88-92 (and similarly lines
54-68), the mock CLI command is registered as 'signtx' while Core expects
'signtransaction'; update the parser to register the primary command name as
"signtransaction" and add "signtx" as an alias or alternative name via
add_parser/aliases so both names map to the same handler, and ensure
set_defaults(func=signtx) remains so the existing handler is used for both
names.

Comment on lines +51 to +58
assert_raises_rpc_error(-1, 'Error: restart dashd with -signer=<cmd>',
self.nodes[0].enumeratesigners
)

# Handle script missing:
assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
self.nodes[3].enumeratesigners
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix E128 under-indented continuations and make error assertion OS-portable

  • Flake8 E128: align wrapped arguments under the opening parenthesis.
  • The missing-signer error message is OS-specific (“execve failed” on POSIX vs CreateProcess/The system cannot find the file on Windows). Relax the assertion to a portable substring.

Apply:

-        assert_raises_rpc_error(-1, 'Error: restart dashd with -signer=<cmd>',
-            self.nodes[0].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1,
+            'Error: restart dashd with -signer=<cmd>',
+            self.nodes[0].enumeratesigners,
+        )

-        assert_raises_rpc_error(-1, 'execve failed: No such file or directory',
-            self.nodes[3].enumeratesigners
-        )
+        err_substr = 'No such file or directory' if platform.system() != 'Windows' else 'find the file'
+        assert_raises_rpc_error(
+            -1,
+            err_substr,
+            self.nodes[3].enumeratesigners,
+        )

-        assert_raises_rpc_error(-1, 'RunCommandParseJSON error',
-            self.nodes[1].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1,
+            'RunCommandParseJSON error',
+            self.nodes[1].enumeratesigners,
+        )

-        assert_raises_rpc_error(-1, 'fingerprint not found',
-            self.nodes[1].enumeratesigners
-        )
+        assert_raises_rpc_error(
+            -1,
+            'fingerprint not found',
+            self.nodes[1].enumeratesigners,
+        )

Also consider quoting the Windows mock path to survive spaces:

-            return "py " + path
+            return f'py "{path}"'

Also applies to: 60-64, 67-70

🧰 Tools
🪛 Flake8 (7.3.0)

[error] 52-52: continuation line under-indented for visual indent

(E128)


[error] 57-57: continuation line under-indented for visual indent

(E128)

@knst knst added this to the 23.1 milestone Oct 21, 2025
@knst knst requested a review from UdjinM6 November 21, 2025 19:26
knst and others added 25 commits November 22, 2025 06:59
f75e0c1 doc: add external-signer.md (Sjors Provoost)
d4b0107 rpc: send: support external signer (Sjors Provoost)
245b445 rpc: signerdisplayaddress (Sjors Provoost)
7ebc7c0 wallet: ExternalSigner: add GetDescriptors method (Sjors Provoost)
fc5da52 wallet: add GetExternalSigner() (Sjors Provoost)
259f52c test: external_signer wallet flag is immutable (Sjors Provoost)
2655197 rpc: add external_signer option to createwallet (Sjors Provoost)
2700f09 rpc: signer: add enumeratesigners to list external signers (Sjors Provoost)
07b7c94 rpc: add external signer RPC files (Sjors Provoost)
8ce7767 wallet: add ExternalSignerScriptPubKeyMan (Sjors Provoost)
157ea7c wallet: add external_signer flag (Sjors Provoost)
f3e6ce7 test: add external signer test (Sjors Provoost)
8cf543f wallet: add -signer argument for external signer command (Sjors Provoost)
f7eb7ec test: framework: add skip_if_no_external_signer (Sjors Provoost)
87a9794 configure: add --enable-external-signer (Sjors Provoost)

Pull request description:

  Big picture overview in [this gist](https://gist.github.com/Sjors/29d06728c685e6182828c1ce9b74483d).

  This PR lets `bitcoind` call an arbitrary command `-signer=<cmd>`, e.g. a hardware wallet driver,  where it can fetch public keys, ask to display an address, and sign a transaction (using PSBT under the hood).

  It's design to work with https://github.com/bitcoin-core/HWI, which supports multiple hardware wallets. Any command with the same arguments and return values will work. It simplifies the manual procedure described [here](https://github.com/bitcoin-core/HWI/blob/master/docs/bitcoin-core-usage.md).

  Usage is documented in [doc/external-signer.md](
  https://github.com/Sjors/bitcoin/blob/2019/08/hww-box2/doc/external-signer.md), which also describes what protocol a different signer binary should conform to.

  Use `--enable-external-signer` to opt in, requires Boost::Process:

  ```
  Options used to compile and link:
    with wallet     = yes
    with gui / qt   = no
    external signer = yes
  ```

  It adds the following RPC methods:
  * `enumeratesigners`: asks <cmd> for a list of signers (e.g. devices) and their master key fingerprint
  * `signerdisplayaddress <address>`:  asks <cmd> to display an address

  It enhances the following RPC methods:
  * `createwallet`: takes an additional `external_signer` argument and fetches keys from device
  * `send`: automatically sends transaction to device and waits

  Usage TL&DR:
  * clone HWI repo somewhere and launch `bitcoind -signer=../HWI/hwi.py`
  * check if you can see your hardware device: `bitcoin-cli enumeratesigners`
  * create wallet and auto import keys `bitcoin-cli createwallet "hww" true true "" true true true`
  * display address on device: `bitcoin-cli signerdisplayaddress ...`
  * to spend, use `send` RPC and approve transaction on device

  Prerequisites:
  - [x] bitcoin#21127 load wallet flags before everything else
  - [x] bitcoin#21182 remove mostly pointless BOOST_PROCESS macro

  Potentially useful followups:
  - GUI support: bitcoin-core/gui#4
  - bumpfee support
  - (automatically) verify (a subset of) keys on the device after import, through message signing

ACKs for top commit:
  laanwj:
    re-ACK f75e0c1

Tree-SHA512: 7db8afd54762295c1424c3f01d8c587ec256a72f34bd5256e04b21832dabd5dc212be8ab975ae3b67de75259fd569a561491945750492f417111dc7b6641e77f
da30c1b wallet: fix doc typo in signer option (William Casarin)

Pull request description:

ACKs for top commit:
  0xB10C:
    ACK da30c1b
  darosior:
    ACK da30c1b

Tree-SHA512: 1f4ba501121101bbe94a18a0276df6a3592123548110ed5d1e4e1937b75c9a832bc3f0d6fa26bab69b3304526ef0548b2540d446e05a6402793321a34f508b09
…ion and typos

8b08d0f build, doc: Fix configure script output indentation and typos (Hennadii Stepanov)

Pull request description:

  This PR is follow up of bitcoin#16546, that breaks the `configure` script output indentation for gui/qt/qr lines:
  ```
  Options used to compile and link:
    external signer = no
    multiprocess    = no
    with libs       = yes
    with wallet     = yes
      with sqlite   = yes
      with bdb      = yes
      with gui / qt = yes
    with qr         = yes
    with zmq        = yes
    with test       = yes
  ...
  ```

  With this PR:
  ```
  Options used to compile and link:
    external signer = no
    multiprocess    = no
    with libs       = yes
    with wallet     = yes
      with sqlite   = yes
      with bdb      = yes
    with gui / qt   = yes
      with qr       = yes
    with zmq        = yes
    with test       = yes
  ...
  ```

  Also typos are fixed.

ACKs for top commit:
  Sjors:
    utACK 8b08d0f
  vasild:
    ACK 8b08d0f

Tree-SHA512: 46dfcfb754192dbcc080348781327d1714e2f9a696f3ed9252746b426e3afc628d84adb197ba3b8080eacaa6053ccac07e670998930ae92cef8ed713dd457c10
57ff5a4 doc: specify minimum HWI version (Sjors Provoost)
03308b2 rpc: don't require wallet for enumeratesigners (Sjors Provoost)

Pull request description:

  HWI just released 2.0. See https://github.com/bitcoin-core/HWI/releases/tag/2.0.0

  As of bitcoin#16546 we already rely on features that are in 2.0 and not in the previous 1.* releases:
  * `--chain` param

  This shouldn't be a problem, because HWI 2.0 has been released before we release v22.

  Misc improvements:
  * document that HWI 2.0 is required
  * drop wallet requirement for `enumeratesigners`

ACKs for top commit:
  achow101:
    Code Review ACK 57ff5a4

Tree-SHA512: 3fb6ba20894e52a116f89525a5f5a1f61d363ccd904e1cffd0e6d095640fc6d2edf0388cd6ae20f83bbc31e5f458255ec090b6e823798d426eba3e45b4336bf9
88d4d5f rpc: add help for enumeratesigners and walletdisplayaddress (Sjors Provoost)
b0db187 ci: use --enable-external-signer instead of --with-boost-process (Sjors Provoost)
b54b2e7 Move external signer out of wallet module (Sjors Provoost)

Pull request description:

  In addition, this PR enables external signer testing on CI.

  This PR moves the ExternalSigner class and RPC methods out of the wallet module.

  The `enumeratesigners` RPC can be used without a wallet since bitcoin#21417. With additional modifications external signers could be used without a wallet in general, e.g. via `signrawtransaction`.

  The `signerdisplayaddress` RPC is ranamed to `walletdisplayaddress` because it requires wallet context. A future `displayaddress` RPC call without wallet context could take a descriptor argument.

  This commit fixes a `rpc_help.py` failure when configured with `--disable-wallet`.

ACKs for top commit:
  ryanofsky:
    Code review ACK 88d4d5f
  fanquake:
    ACK 88d4d5f

Tree-SHA512: 3242a24e22313aed97eee32a520bfcb1c17495ba32a2b8e06a5e151e2611320e2da5ef35b572d84623af0a49a210d2f9377a2531250868d1a0ccf3e144352a97
c8f469c external_signer: remove ExternalSignerException (fanquake)
9e0b199 external_signer: use const where appropriate (fanquake)
aaa4e5a wallet: remove CWallet::GetExternalSigner() (fanquake)
06a0673 external_signer: remove ignore_errors from Enumerate() (fanquake)
8fdbb89 refactor: unify external wallet runtime errors (fanquake)
f4652bf refactor: add missing includes to external signer code (fanquake)
54569cc refactor: move all signer code inside ENABLE_EXTERNAL_SIGNER #ifdefs (fanquake)

Pull request description:

  These are a few followups after bitcoin#21467.

ACKs for top commit:
  Sjors:
    tACK c8f469c
  instagibbs:
    utACK bitcoin@c8f469c

Tree-SHA512: 3d5ac5df81680075e71e0e4a7595c520d746c3e37f016cf168c1e10da15541ebb1595aecaf2c08575636e9ff77d499644cae53180232b7049cfae0b923106e4e
…allet)

1c4b456 gui: send using external signer (Sjors Provoost)
24815c6 gui: wallet creation detects external signer (Sjors Provoost)
3f845ea node: add externalSigners to interface (Sjors Provoost)
62ac119 gui: display address on external signer (Sjors Provoost)
450cb40 wallet: add displayAddress to interface (Sjors Provoost)
eef8d64 gui: create wallet with external signer (Sjors Provoost)
6cdbc83 gui: add external signer path to options dialog (Sjors Provoost)

Pull request description:

  Big picture overview in [this gist](https://gist.github.com/Sjors/29d06728c685e6182828c1ce9b74483d).

  This PR adds GUI support for external signers, based on the since merged bitcoin#16546 (RPC).

  The UX isn't amazing - especially the blocking calls - but it works.

  First we adds a GUI setting for the signer script (e.g. path to HWI):

  <img width="625" alt="Schermafbeelding 2019-08-05 om 19 32 59" src="https://user-images.githubusercontent.com/10217/62483415-e1ff1680-b7b7-11e9-97ca-8d2ce54ca1cb.png">

  Then we add an external signer checkbox to the wallet creation dialog:

  <img width="374" alt="Schermafbeelding 2019-11-07 om 19 17 23" src="https://user-images.githubusercontent.com/10217/68416387-b57ee000-0194-11ea-9730-127d60273008.png">

  It's checked by default if HWI detects a device. It also grabs the name. It then creates a fresh wallet and imports the keys.

  You can verify an address on the device (blocking...):
  <img width="673" alt="Schermafbeelding 2019-08-05 om 19 29 22" src="https://user-images.githubusercontent.com/10217/62483560-43bf8080-b7b8-11e9-9902-8a036116dc4b.png">

  Sending, including coin selection, Just Works(tm) as long the device is present.

  ~External signer support is enabled by default when the GUI is configured and Boost::Process is present.~

  External signer support remains disabled by default, see bitcoin#21935.

ACKs for top commit:
  achow101:
    Code Review ACK 1c4b456
  hebasto:
    ACK 1c4b456, tested on Linux Mint 20.1 (Qt 5.12.8) with HWW `2.0.2-rc.1`.
  promag:
    Tested ACK 1c4b456 but rebased with e033ca1, with HWI 2.0.2, with Nano S and Nano X.
  meshcollider:
    re-code-review ACK 1c4b456

Tree-SHA512: 3503113c5c69d40adb6ce364d8e7cae23ce82d032a00474ba9aeb6202eb70f496ef4a6bf2e623e5171e524ad31ade7941a4e0e89539c64518aaec74f4562d86b
…n unsupported

e60cd26 Do not load external signers wallets when unsupported (Andrew Chow)

Pull request description:

  When external signer support is not compiled, do not load external signer wallets.

  Alternative to bitcoin#22168.

ACKs for top commit:
  promag:
    Tested ACK e60cd26.
  meshcollider:
    Code review ACK e60cd26

Tree-SHA512: aed2d0038f448c2f89c6b48f412b106e63c9ed20e748e69aae21fb58c33fc7e4fa73375a52372c73788669eb2b968a8da6b022c65658fa4484f5bbcf205b1b15
…e #ifdef

2f5bdcb gui: misc external signer fixes and translation hints (Sjors Provoost)
d672404 refactor: make ExternalSigner NetworkArg() and m_chain private (Sjors Provoost)
4455145 refactor: reduce #ifdef ENABLE_EXTERNAL_SIGNER usage (Sjors Provoost)
5be90c9 build: enable external signer by default (Sjors Provoost)
7d94530 refactor: clean up external_signer.h includes (Sjors Provoost)
fc0eca3 fuzz: fix fuzz binary linking order (Sjors Provoost)

Pull request description:

  This follows the introduction of GUI support in bitcoin-core/gui#4

  I don't think we should expect GUI users to self compile. This also enables external signer support by default for RPC users.

  In addition this PR reduces the number of `#ifdef ENABLE_EXTERNAL_SIGNER`, which also fixes bitcoin#21919. When compiled with `--disable-external-signer` such wallets can't be created in RPC or GUI, but they can be loaded. Attempting any action that calls HWI will trigger an error.

  Side-note: this PR may or may not (currently) break CI for the GUI repository, as explained here: bitcoin-core/gui#4 (comment)

ACKs for top commit:
  achow101:
    ACK 2f5bdcb
  hebasto:
    re-ACK 2f5bdcb

Tree-SHA512: 1b71c5a8bea2be077ee9fa33a01130c957a0cf90951d4b7b04d3d0ef826bb77e474c3963abddfef2e2c1ea99d9c72cd2302d1eb9b5fcb7ba0bd2a625f006aa05
…ocess

67669ab build: Fix Boost Process compatibility with mingw-w64 compiler (Hennadii Stepanov)

Pull request description:

  On master (9c3751a) the cross build for Win64 is broken if configured with `--enable-external-signer`:
  ```
  ...
    CXX      crypto/libbitcoin_crypto_base_a-chacha_poly_aead.o
  In file included from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handles.hpp:11,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/used_handles.hpp:17,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/async_in.hpp:20,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/async.hpp:49,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process.hpp:23,
                   from util/system.cpp:9:
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:208:51: error: expected ‘)’ before ‘*’ token
    208 | typedef ::boost::winapi::NTSTATUS_ (__kernel_entry *nt_system_query_information_p )(
        |                                    ~              ^~
        |                                                   )
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:223:51: error: expected ‘)’ before ‘*’ token
    223 | typedef ::boost::winapi::NTSTATUS_ (__kernel_entry *nt_query_object_p )(
        |                                    ~              ^~
        |                                                   )
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp: In function ‘boost::winapi::NTSTATUS_ boost::process::detail::windows::workaround::nt_system_query_information(boost::process::detail::windows::workaround::SYSTEM_INFORMATION_CLASS_, void*, boost::winapi::ULONG_, boost::winapi::PULONG_)’:
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:239:12: error: ‘nt_system_query_information_p’ does not name a type; did you mean ‘nt_system_query_information’?
    239 |     static nt_system_query_information_p f = reinterpret_cast<nt_system_query_information_p>(::boost::winapi::get_proc_address(h, "NtQuerySystemInformation"));
        |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        |            nt_system_query_information
  In file included from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handles.hpp:11,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/used_handles.hpp:17,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/async_in.hpp:20,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/async.hpp:49,
                   from /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process.hpp:23,
                   from util/system.cpp:9:
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:241:14: error: ‘f’ was not declared in this scope
    241 |     return (*f)(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);
        |              ^
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp: In function ‘boost::winapi::BOOL_ boost::process::detail::windows::workaround::nt_query_object(boost::winapi::HANDLE_, boost::process::detail::windows::workaround::OBJECT_INFORMATION_CLASS_, void*, boost::winapi::ULONG_, boost::winapi::PULONG_)’:
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:253:12: error: ‘nt_query_object_p’ does not name a type; did you mean ‘nt_query_object’?
    253 |     static nt_query_object_p f = reinterpret_cast<nt_query_object_p>(::boost::winapi::get_proc_address(h, "NtQueryObject"));
        |            ^~~~~~~~~~~~~~~~~
        |            nt_query_object
  /home/hebasto/GitHub/bitcoin/depends/x86_64-w64-mingw32/include/boost/process/detail/windows/handle_workaround.hpp:255:14: error: ‘f’ was not declared in this scope
    255 |     return (*f)(Handle, ObjectInformationClass, ObjectInformation, ObjectInformationLength, ReturnLength);
        |              ^
  make[2]: *** [Makefile:9906: util/libbitcoin_util_a-system.o] Error 1
  make[2]: *** Waiting for unfinished jobs....
    CXX      crypto/libbitcoin_crypto_base_a-chacha20.o
  make[2]: Leaving directory '/home/hebasto/GitHub/bitcoin/src'
  make[1]: *** [Makefile:16141: all-recursive] Error 1
  make[1]: Leaving directory '/home/hebasto/GitHub/bitcoin/src'
  make: *** [Makefile:820: all-recursive] Error 1
  ```

  The upstream bug: boostorg/process#96
  Also see: https://stackoverflow.com/a/59338759

  bitcoin#22348 (comment):
  > [This commit](boostorg/process@7fc41b2), containing the `__kernel_entry` [SAL annotations](https://docs.microsoft.com/en-us/cpp/code-quality/using-sal-annotations-to-reduce-c-cpp-code-defects?view=msvc-160) was included in Boost Process as part of the `1.71.0` release, which broke support for compiling with mingw-w64 because it doesn't define the `__kernel_entry` SAL annotation (but it does define some others, i.e see [`sal.h`](https://github.com/mirror/mingw-w64/blob/master/mingw-w64-headers/include/sal.h)).
  >
  > A [commit was made](boostorg/process@d7a721e) to remove the annotations, however, it hasn't made it into either of the two Boost releases that have happened since (1.75.0 & 1.76.0). Meaning that this is currently needed for all versions of Boost process from 1.71.0 onwards.

ACKs for top commit:
  fanquake:
    ACK 67669ab - thanks for updating this.

Tree-SHA512: 5931ca1fb77ce38c042cf5a7556add024ea2386c208bf26c792a8ca4a771d97fac9802c32fa8aa2e3de1ad35f3362d8c066f0a83ee675859d226c602fd0bcf93
…abled without signers

a9b9ca8 gui: ensure external signer option remains disabled without signers (Andrew Chow)

Pull request description:

  When no external signers are available, the option to enable external signers should always be disabled. However the encrypt wallet checkbox can erroneously re-enable the external signer checkbox. To avoid this, CreateWalletDialog now stores whether signers were available during setSigners so that future calls to external_signer_checkbox->setEnabled can account for whether signers are available.

  Fixes dashpay#395

ACKs for top commit:
  hebasto:
    ACK a9b9ca8, tested on Linux Mint 20.2 (Qt 5.12.8).
  Sjors:
    tACK a9b9ca8
  jarolrod:
    ACK a9b9ca8

Tree-SHA512: 98951bcadc23fce99a66ea2d367c44360989e888c253845a767e1f7085c594562d0f099de4130f4a078c5072aa7806294097d976ee6407291f3d3c5a4a608b44
…gic (stop on first match)

d047ed7 external_signer: improve fingerprint matching logic (stop on first match) (Sebastian Falbesoner)

Pull request description:

  The fingerprint matching logic in `ExternalSigner::SignTransaction` currently always iterates all inputs of a PSBT, even after a match has already been found. I guess the reason for that is not that it was not thought of, but rather the fact that breaking out of a nested loop is simply not possible (at least not without adding ugly constructs like gotos or extra state variables).
  This PR fixes this by using `std::any_of` from C++'s standard library, see http://www.cplusplus.com/reference/algorithm/any_of/

ACKs for top commit:
  lsilva01:
    Code Review ACK bitcoin@d047ed7
  Sjors:
    utACK d047ed7
  Zero-1729:
    crACK d047ed7
  mjdietzx:
    Code review ACK d047ed7
  hebasto:
    ACK d047ed7, I have reviewed the code and it looks OK, I agree it can be merged.

Tree-SHA512: 447e7c0c6a5b5549a2c09d52e55ba4146302c1a06e4d96de11f6945d09f98c89129cba221202dff7e0718e01a83dd173b9f19b1f02b6be228978f3f6e35d8096
a032fa3 multiprocess: add interfaces::ExternalSigner class (Russell Yanofsky)

Pull request description:

  Add `interfaces::ExternalSigner` class to let signer objects be passed between processes and let signer code run in the original process where the object was created.

  ---

  This PR is part of the [process separation project](https://github.com/bitcoin/bitcoin/projects/10).

ACKs for top commit:
  laanwj:
    Concept and code review ACK a032fa3
  hebasto:
    re-ACK a032fa3

Tree-SHA512: 99a729fb3a64d010e142cc778a9f1f358e58345b77faaf2664de7d2277715d59df3352326e8f0f2a6628038670eaa4556310a549079fb28af6d2eeb05aea1460
5493e92 Check descriptors returned by external signers (sstone)

Pull request description:

  Check that descriptors returned by external signers have been parsed properly when creating a new wallet.
  See bitcoin#23627 for context.

  The problem is that parsing an invalid descriptor will return `null` which is not checked for in `CWallet::SetupDescriptorScriptPubKeyMans()`.

  I'm not completely sure what the best fix is since there several strategies for dealing with errors in the current codebase but the proposed fix is very simple and consistent with other validation checks in `CWallet::SetupDescriptorScriptPubKeyMans()`.

ACKs for top commit:
  jamesob:
    Code review ACK bitcoin@5493e92
  achow101:
    ACK 5493e92

Tree-SHA512: 63259f4aa519405a86c554b6813efdb741314bdaa18bf005b70ea8bb92a27abc6e2b65f7c584641dc257fc78a6499f42b51b5310c243e611c4663430dccf3d04
b75f4c8 RPC: Return external_signer in getwalletinfo (Kristaps Kaupe)

Pull request description:

  Add `external_signer` to the result object of `getwalletinfo` RPC which indicates whether `WALLET_FLAG_EXTERNAL_SIGNER` flag is set for the wallet.

ACKs for top commit:
  S3RK:
    utACK b75f4c8
  achow101:
    ACK b75f4c8
  prayank23:
    utACK bitcoin@b75f4c8
  brunoerg:
    utACK b75f4c8

Tree-SHA512: 066ccb97541fd4dc3d9728834645db714a3c8c93ccf29142811af4d79cfb9440a97bbb6c845434a909bc6e1775ef3737fcbb368c1f0582bc63973f6deb17a45f
…or message

7f3a6a9 wallet: Add external-signer-support specific error message (Hennadii Stepanov)

Pull request description:

  On master (5f44c5c) an attempt to load an external signer wallet using Bitcoin Core compiled without external signer support fails with the following log messages:
  ```
  2022-02-20T19:01:11Z [qt-walletctrl] Using SQLite Version 3.31.1
  2022-02-20T19:01:11Z [qt-walletctrl] Using wallet /home/hebasto/.bitcoin/testnet3/wallets/coldcard-0220
  2022-02-20T19:01:11Z [qt-walletctrl] init message: Loading wallet…
  2022-02-20T19:01:11Z [qt-walletctrl] [coldcard-0220] Error: External signer wallet being loaded without external signer support compiled
  2022-02-20T19:01:11Z [qt-walletctrl] [coldcard-0220] Releasing wallet
  ```

  While log messages are good, a message in the GUI window is completely misleading:

  ![Screenshot from 2022-02-20 20-43-46](https://user-images.githubusercontent.com/32963518/154859854-b87032e0-c428-4e11-8009-39e38200482c.png)

  This PR fixes this issue:

  ![Screenshot from 2022-02-20 21-01-18](https://user-images.githubusercontent.com/32963518/154859868-e3a2c89d-4f0f-424e-96cb-7accaa48acc0.png)

ACKs for top commit:
  achow101:
    ACK 7f3a6a9
  kristapsk:
    ACK 7f3a6a9
  brunoerg:
    crACK 7f3a6a9

Tree-SHA512: a4842751c0ca8a37ccc3ea00503678f6b712a7f53d6cbdc07ce02dcb85ca8a94890d1c2da20307be043faa347747abeba29185c88ba12edd5253bfca56531585
…rnalSigner::SignTransaction

2a22f03 parsing external signer master fingerprint string as bytes instead of caring for lower/upper case in ExternalSigner::SignTransaction (avirgovi)

Pull request description:

  Some external signers scripts may provide master fingerprint in uppercase format. In that case core will fail with `Signer fingerprint 00000000 does not match any of the inputs` as it only works with lowercase format. Even if the fingerprints match, yet one is lowercase the other uppercase.

  ExternalSigner::SignTransaction is the only place where it is needed IMO, as changing it in other places may break the communication with the external signer (i.e. enumerating with lowercase may not find the device).

ACKs for top commit:
  achow101:
    ACK 2a22f03
  theStack:
    Code-review ACK 2a22f03
  Sjors:
    utACK 2a22f03

Tree-SHA512: f3d84b83fb0b5e06c405eaf9bf20a2fa864bf4172fd4de113b80b9b9a525a76f2f8cf63031b480358b3a7666023a2aef131fb89ff50448c66df3ed541da10f99
…gner` configure option

8df063e build: Fix help string for `--enable-external-signer` configure option (Hennadii Stepanov)

Pull request description:

  This PR is a follow up of bitcoin#24065 and fixes the help string according to the actual default value https://github.com/bitcoin/bitcoin/blob/816ca01650f4cc66a61ac2f9b0f8b74cd9cd0cf8/configure.ac#L324-L327

ACKs for top commit:
  kristapsk:
    cr utACK 8df063e
  jarolrod:
    ACK 8df063e

Tree-SHA512: ad3f457a53c9238ddd8ded9efd1224e564e6cb9da8b7ff7733a11e32a7daad5c0f6c6223509218f44944a874470cb0d2447897662eaf4e78c763b30785717c50
…ationDialog

742918c qt: hide Create Unsigned button behind an expert mode option (Andrew Chow)
5c3b800 qt: Add Create Unsigned button to SendConfirmationDialog (Andrew Chow)

Pull request description:

  Instead of having different buttons or changing button behavior for making a PSBT, just have SendConfirmationDialog return whether the user wants a PSBT or a broadcasted transaction. Since this dialog is used by both the bumpFeeAction and the SendCoinsDialog, changes to both to support the different behavior is needed. They will check the return value of the SendConfirmationDialog for whether a PSBT needs to be created instead of checking whether private keys are disabled.

  Strings used in this dialog are being slightly modified to work with both private keys enabled and disabled wallets.

  Moved from bitcoin#18789

ACKs for top commit:
  jarolrod:
    ACK 742918c
  ryanofsky:
    Code review ACK 742918c. Just suggested changes since last review. Looks great!
  hebasto:
    ACK 742918c, tested on Linux Mint 20.2 (Qt 5.12.8).

Tree-SHA512: dd29f4364c7b4f15befe8fe63257b26187918786b005e0f8336183270b1a162680b93f6ced60f0285c6e607c084cc0d24950fc68a8f9c056521ede614041be66
…igning on Windows

e2ab9f8 build: disable external signer on Windows (fanquake)

Pull request description:

  This change explicitly disables support for external signing when targeting Windows and OpenBSD. The driver for this is that Boost Process uses boost::filesystem internally, when targeting Windows, which gets in the way of removing our usage of it (bitcoin#20744). While we could adjust bitcoin#20744 to still link against the Boost libs when building for Windows, that would be disappointing, as we wouldn't have cleanly removed the Boost usage we're trying too (including the build infrastructure), and, we'd be in a position where we would be building releases differently depending on the platform, which is something I want to avoid.

  After discussion with Sjors, Achow and Hebasto, this seemed like a reasonable step to move bitcoin#20744 forward (as-is). Note that support for external signing ([while already being experimental](https://github.com/bitcoin/bitcoin/blob/master/doc/external-signer.md#example-usage)), could be considered even more experimental on Windows. Also, oddly, we have external-signing [explicitly disabled in our Windows (cross-compile) CI](https://github.com/bitcoin/bitcoin/blob/807169e10b4a18324356ed6ee4d69587b96a7c70/ci/test/00_setup_env_win64.sh#L16), it's not clear why this is the case, as, if it's a feature being built into releases, it should be being built and tested in the CI which is most-like the release process.

  There is an [issue open upstream](boostorg/process#207), in regards to migrating Boost Process to std::filesystem, or having an option to use it. However there hasn't been much discussion since it was opened ~9 months ago. There is another related issue here: klemens-morgenstern/boost-process#164.

  Resolves bitcoin#24036.

ACKs for top commit:
  Sjors:
    utACK e2ab9f8
  achow101:
    ACK e2ab9f8
  kallewoof:
    utACK e2ab9f8
  hebasto:
    ACK e2ab9f8, tested on Linux Mint 20.2 (x86_64).

Tree-SHA512: 36fcfc0e1a008a8271dc76b8e12e93d3e1d1e528bf668e95a559e9f6fd7d5f031bd7a6a6bc8b9fa9d057b2cd56f9ec8838c7f74e87899bf9a6aeb787afbd112c
Copy link

@UdjinM6 UdjinM6 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 013953b

@PastaPastaPasta PastaPastaPasta merged commit 8bff900 into dashpay:develop Nov 25, 2025
62 of 64 checks passed
@knst knst deleted the bp-hwi-1 branch November 25, 2025 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RPC Some notable changes to RPC params/behaviour/descriptions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants