Improve display address handling for external signer #24313

pull Sjors wants to merge 3 commits into bitcoin:master from Sjors:2022/02/displayaddress changing 13 files +68 −38
  1. Sjors commented at 4:22 PM on February 10, 2022: member
    • HWI returns the requested address: as a sanity check, we now compare that to what we expected
      • external signer documentation now reflects that HWI alternatives must implement this check
    • both RPC and GUI will now return an error text, rather than just fail (the GUI even failed silently in some cases)
  2. DrahtBot added the label GUI on Feb 10, 2022
  3. DrahtBot added the label interfaces on Feb 10, 2022
  4. DrahtBot added the label RPC/REST/ZMQ on Feb 10, 2022
  5. DrahtBot added the label Wallet on Feb 10, 2022
  6. DrahtBot commented at 10:51 AM on February 11, 2022: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK brunoerg, achow101
    Approach ACK jonatack

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #bitcoin-core/gui/700 (Dialog for allowing the user to choose the change output when bumping a tx by achow101)

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

  7. in test/functional/wallet_signer.py:115 in 803387f054 outdated
     110 | @@ -111,8 +111,10 @@ def test_valid_signer(self):
     111 |          assert_equal(address_info['hdkeypath'], "m/44'/1'/0'/0/0")
     112 |  
     113 |          self.log.info('Test walletdisplayaddress')
     114 | -        result = hww.walletdisplayaddress(address1)
     115 | -        assert_equal(result, {"address": address1})
     116 | +        for address in [address1, address2, address3]:
     117 | +            print(address)
    


    jonatack commented at 1:24 PM on February 11, 2022:

    c83ab3059 The printed addresses correspond to the updated mocks. I think you intended to remove this line, though.

  8. in src/wallet/external_signer_scriptpubkeyman.h:36 in 803387f054 outdated
      28 | @@ -27,7 +29,11 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
      29 |  
      30 |    static ExternalSigner GetExternalSigner();
      31 |  
      32 | -  bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
      33 | +  /**
      34 | +  * Display address on the device and verify that the returned value matches.
      35 | +  * @returns an error message or nullopt
      36 | +  */
      37 | +  std::optional<bilingual_str> DisplayAddress(const CTxDestination& dest, const ExternalSigner &signer) const;
    


    jonatack commented at 1:26 PM on February 11, 2022:

    c83ab3059 since you're touching this line

      std::optional<bilingual_str> DisplayAddress(const CTxDestination& dest, const ExternalSigner& signer) const;
    
  9. in src/wallet/external_signer_scriptpubkeyman.cpp:61 in 803387f054 outdated
      60 |      auto descriptor = InferDescriptor(scriptPubKey, *provider);
      61 |  
      62 | -    signer.DisplayAddress(descriptor->ToString());
      63 | -    // TODO inspect result
      64 | -    return true;
      65 | +    UniValue result = signer.DisplayAddress(descriptor->ToString());
    


    jonatack commented at 1:28 PM on February 11, 2022:

    c83ab3059

    @@ -54,11 +54,11 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
     std::optional<bilingual_str> ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestination& dest, const ExternalSigner &signer) const
     {
         // TODO: avoid the need to infer a descriptor from inside a descriptor wallet
    -    CScript scriptPubKey = GetScriptForDestination(dest);
    +    const CScript& scriptPubKey = GetScriptForDestination(dest);
         auto provider = GetSolvingProvider(scriptPubKey);
         auto descriptor = InferDescriptor(scriptPubKey, *provider);
     
    -    UniValue result = signer.DisplayAddress(descriptor->ToString());
    +    const UniValue& result = signer.DisplayAddress(descriptor->ToString());
    
  10. in src/wallet/external_signer_scriptpubkeyman.cpp:73 in 803387f054 outdated
      70 | +    const UniValue& ret_address = find_value(result, "address");
      71 | +    if (!ret_address.isStr()) return _("Signer did not echo address");
      72 | +
      73 | +    if (ret_address.getValStr() != EncodeDestination(dest)) {
      74 | +        return strprintf(_("Signer echoed unexpected address %s"), ret_address.getValStr());
      75 | +    }
    


    jonatack commented at 1:34 PM on February 11, 2022:

    Tests of these new errors would be nice, not sure how feasible this would be.


    Sjors commented at 10:48 AM on February 17, 2022:

    This particular one should be doable.


    Sjors commented at 2:08 PM on June 23, 2022:

    Added.

  11. jonatack commented at 1:34 PM on February 11, 2022: member

    Approach ACK

  12. in src/wallet/external_signer_scriptpubkeyman.cpp:70 in 803387f054 outdated
      66 | +
      67 | +    const UniValue& error = find_value(result, "error");
      68 | +    if (error.isStr()) return strprintf(_("Signer returned error: %s"), error.getValStr());
      69 | +
      70 | +    const UniValue& ret_address = find_value(result, "address");
      71 | +    if (!ret_address.isStr()) return _("Signer did not echo address");
    


    luke-jr commented at 1:21 AM on February 13, 2022:

    Won't this break older external signers?


    Sjors commented at 10:47 AM on February 17, 2022:

    HWI has always done this, we just didn't check. That said, it's not (yet) in the spec (which may be a bit outdated) so other signer software might not do this. Do you know of any? cc @achow101

    https://github.com/bitcoin/bitcoin/blob/master/doc/external-signer.md#displayaddress-optional


    achow101 commented at 5:02 PM on May 24, 2023:

    AFAIK there aren't any other external signer software yet.

  13. in src/qt/walletmodel.cpp:572 in 803387f054 outdated
     570 |      }
     571 | -    return res;
     572 | +    if (error) {
     573 | +        QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(error->translated));
     574 | +    }
     575 | +    return true;
    


    luke-jr commented at 1:55 AM on February 20, 2022:
        return !error;
    
  14. Sjors commented at 2:08 PM on June 23, 2022: member

    Rebased and addressed comments.

    I could change "Signer did not echo address" from an error to a warning. However I'd rather only do this is we know there is an HWI clone out there that relies on the (less safe) earlier behavior. I updated the documentation. See also #24313 (review).

  15. Sjors force-pushed on Jun 23, 2022
  16. Sjors force-pushed on Sep 6, 2022
  17. DrahtBot added the label Needs rebase on Oct 26, 2022
  18. Sjors force-pushed on Oct 26, 2022
  19. Sjors commented at 2:15 PM on October 26, 2022: member

    Rebased after #23578.

  20. DrahtBot removed the label Needs rebase on Oct 26, 2022
  21. DrahtBot added the label Needs rebase on Apr 21, 2023
  22. achow101 requested review from achow101 on Apr 25, 2023
  23. Sjors commented at 1:28 PM on May 8, 2023: member

    Rebased and tested that it still works.

  24. Sjors force-pushed on May 8, 2023
  25. DrahtBot removed the label Needs rebase on May 8, 2023
  26. DrahtBot added the label CI failed on May 13, 2023
  27. Sjors force-pushed on May 19, 2023
  28. Sjors commented at 8:46 AM on May 19, 2023: member

    Rebased because <key_io.h> moved (Kernel related?) ~and to add an explicit include <univalue.h> (macOS / clang doesn't care, but CI does and it makes sense anyway)~.

  29. Sjors force-pushed on May 19, 2023
  30. Sjors commented at 9:03 AM on May 19, 2023: member

    Oops, I didn't rebase all the way. Specifically not past #27605. Will fix.

  31. Sjors force-pushed on May 19, 2023
  32. maflcko commented at 10:24 AM on May 19, 2023: member

    Looks like CI failed?

  33. Sjors commented at 12:46 PM on May 19, 2023: member

    Ah yes, broken by my own #26076.

  34. Sjors force-pushed on May 19, 2023
  35. Sjors force-pushed on May 19, 2023
  36. Sjors force-pushed on May 19, 2023
  37. DrahtBot removed the label CI failed on May 19, 2023
  38. Sjors commented at 7:31 AM on May 20, 2023: member

    CI is happy now

  39. in src/interfaces/wallet.h:128 in 8218ce6b02 outdated
     124 | @@ -125,7 +125,7 @@ class Wallet
     125 |      virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
     126 |  
     127 |      //! Display address on external signer
     128 | -    virtual bool displayAddress(const CTxDestination& dest) = 0;
     129 | +    virtual std::optional<bilingual_str> displayAddress(const CTxDestination& dest) = 0;
    


    achow101 commented at 9:27 PM on May 23, 2023:

    In 8218ce6b02b24eeef31f4e343ea777b7844907fc "wallet: return and display signer error"

    Returning the error in an optional feels a bit wrong since it inverts the typical pattern. Rather than checking that the result is equivalent to true, we have to check that it's equivalent to false.

    I think it would be better to use util::Result<bool> instead of an optional in order to preserve our typical error checking pattern.


    Sjors commented at 9:26 AM on May 24, 2023:

    Done


    ryanofsky commented at 12:35 PM on May 24, 2023:

    re: #24313 (review)

    Returning the error in an optional feels a bit wrong since it inverts the typical pattern. Rather than checking that the result is equivalent to true, we have to check that it's equivalent to false.

    Returning optional<bilingual_str> or Result<bool> both seem ok here, but the ideal thing would be to return Result<void> so there are just 2 cases for calling code to handle 1-success, and 2-failure with an error message. Not 3 cases to handle: 1-success returning true, 2-success returning false, and 3-failure with an error message.

    Complaining about the optional<bilingual_str> pattern is a recurring topic, see #27632 (review), #27632 (review), #25648 (review) and I have a PR to replace uses of optional<bilingual_str> with util::Result in #25977, but it's blocked waiting on a complicated base PR which adds support for Result<void>

    Since this keeps coming up, though I think I will just add simple support for Result<void> to #25977 and decouple it from the base PR so it can get unstuck.

    Just thinking out loud here, though (and advertising #25977)


    Sjors commented at 2:21 PM on August 1, 2023:

    I switched to using util::Result in my last rebase a few months ago.

  40. Sjors force-pushed on May 24, 2023
  41. Sjors force-pushed on May 24, 2023
  42. DrahtBot added the label CI failed on May 24, 2023
  43. DrahtBot removed the label CI failed on May 24, 2023
  44. achow101 commented at 5:02 PM on May 24, 2023: member

    ACK e3d02f8ef8cfe1d3c502ede863d14f82212a0ce6

  45. fanquake referenced this in commit 4d13fe47be on May 26, 2023
  46. sidhujag referenced this in commit 20272f34e0 on May 26, 2023
  47. DrahtBot added the label CI failed on May 31, 2023
  48. DrahtBot removed the label CI failed on May 31, 2023
  49. in src/wallet/rpc/addresses.cpp:794 in e3d02f8ef8 outdated
     779 | @@ -780,9 +780,8 @@ RPCHelpMan walletdisplayaddress()
     780 |                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
     781 |              }
     782 |  
     783 | -            if (!pwallet->DisplayAddress(dest)) {
     784 | -                throw JSONRPCError(RPC_MISC_ERROR, "Failed to display address");
     785 | -            }
     786 | +            util::Result<bool> res = pwallet->DisplayAddress(dest);
     787 | +            if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).translated);
     788 |  
    


    maflcko commented at 2:47 PM on August 1, 2023:

    <strike> Pretty sure this is wrong, as you are not checking !res.value(). (Previously it threw, now it silently continues. Now it only throws when no ExternalSignerScriptPubKeyMan is available.)</strike>

    edit: nvm. The res.value() is just unused.

    This can be fixed non-confusingly by using Result<void>

  50. Sjors commented at 3:49 PM on August 1, 2023: member

    Rebased and switched to Result<void>. (need to push a fix)

  51. Sjors force-pushed on Aug 1, 2023
  52. Sjors force-pushed on Aug 1, 2023
  53. DrahtBot added the label CI failed on Aug 1, 2023
  54. in src/wallet/rpc/addresses.cpp:784 in 0e0163a062 outdated
     779 | @@ -780,9 +780,8 @@ RPCHelpMan walletdisplayaddress()
     780 |                  throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
     781 |              }
     782 |  
     783 | -            if (!pwallet->DisplayAddress(dest)) {
     784 | -                throw JSONRPCError(RPC_MISC_ERROR, "Failed to display address");
     785 | -            }
     786 | +            util::Result<void> res = pwallet->DisplayAddress(dest);
     787 | +            if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).translated);
    


    ryanofsky commented at 5:35 PM on August 1, 2023:

    In commit "wallet: return and display signer error" (0e0163a062b09a3f762d7dd0af3d6e78e675aae1)

    s/translated/original/ here since RPC methods return untranslated error strings


    Sjors commented at 4:21 PM on August 2, 2023:

    Fixed

  55. DrahtBot removed the label CI failed on Aug 1, 2023
  56. Sjors force-pushed on Aug 2, 2023
  57. achow101 commented at 7:26 PM on August 24, 2023: member

    re-ACK 5536ace894e67322161a649f998265f461fbc8ad

  58. DrahtBot added the label CI failed on Dec 10, 2023
  59. DrahtBot removed the label CI failed on Dec 15, 2023
  60. brunoerg commented at 2:41 PM on December 18, 2023: contributor

    Concept ACK

  61. DrahtBot requested review from jonatack on Dec 18, 2023
  62. in src/wallet/external_signer_scriptpubkeyman.cpp:58 in 35daa69980 outdated
      52 | @@ -52,15 +53,19 @@ ExternalSigner ExternalSignerScriptPubKeyMan::GetExternalSigner() {
      53 |      return signers[0];
      54 |  }
      55 |  
      56 | -bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const
      57 | +bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CTxDestination& dest, const ExternalSigner &signer) const
      58 |  {
      59 |      // TODO: avoid the need to infer a descriptor from inside a descriptor wallet
    


    brunoerg commented at 3:51 PM on December 18, 2023:

    Do you intend to address this TODO?


    Sjors commented at 10:57 AM on December 22, 2023:

    Not in this PR, unless someone else gives me the code for it :-)

  63. in src/wallet/wallet.cpp:2647 in 5536ace894 outdated
    2643 | @@ -2644,7 +2644,7 @@ bool CWallet::DisplayAddress(const CTxDestination& dest)
    2644 |          ExternalSigner signer = ExternalSignerScriptPubKeyMan::GetExternalSigner();
    2645 |          return signer_spk_man->DisplayAddress(dest, signer);
    2646 |      }
    2647 | -    return false;
    2648 | +    return util::Error{_("No external signer ScriptPubKeyManager")};
    


    brunoerg commented at 10:12 PM on December 18, 2023:

    In 5536ace894e67322161a649f998265f461fbc8ad: I think we can improve the "No external signer ScriptPubKeyManager" message, it doesn't sound intuitive. First time I tested it prior reading the code, I got this error and I thought I didn't have any external signer, so I checked it with enumerate.

    Perhaps: "There is no ScriptPubKeyManager for this address".


    Sjors commented at 10:58 AM on December 22, 2023:

    Will consider new wording if I need to touch / rebase.


    Sjors commented at 12:24 PM on February 13, 2024:

    Done

  64. brunoerg commented at 1:30 PM on December 22, 2023: contributor

    I tested it with HWI and Ledger Nano S Plus.


    • I created a wallet:
    ./src/bitcoin-cli -named createwallet wallet_name=ledger3 disable_private_keys=true descriptors=true external_signer=true`
    
    • Got a new address:
    $ ./src/bitcoin-cli -rpcwallet=ledger3 getnewaddress
    bc1qfshzjl770c6qz560cade6ynswj2a3scqzm4a4m
    
    • Ran walletdisplayaddress:
    $ ./src/bitcoin-cli -rpcwallet=ledger3 walletdisplayaddress "bc1qfshzjl770c6qz560cade6ynswj2a3scqzm4a4m"
    
    • Checked it in the Ledger device: <img src="https://github.com/bitcoin/bitcoin/assets/19480819/88003e4f-e72e-4c74-89ef-3c0831e5b545" data-canonical-src="https://github.com/bitcoin/bitcoin/assets/19480819/88003e4f-e72e-4c74-89ef-3c0831e5b545" width="200" height="150" />

    • I locked the Ledger device and tried walletdisplayaddress to check the error message:

    This PR: '/Users/bg/projects/HWI/hwi.py' error: Could not open client or get fingerprint information: Exception: invalid status 0x6982

    Master: '/Users/brunogarcia/projects/HWI/hwi.py' error: Could not open client or get fingerprint information: Exception: invalid status 0x6982


    • Ran walletdisplayaddress with a random address:

    On master:

    error code: -1
    error message:
    Failed to display address
    

    This PR:

    error code: -1
    error message:
    No external signer ScriptPubKeyManager
    
  65. Sjors commented at 2:25 PM on December 22, 2023: member

    Mmm, on the bright side, both the master and this PR have a really unclear message when the device is not connected or locked :-)

    I'll try to improve that.

  66. Sjors commented at 2:27 PM on December 22, 2023: member

    @achow101 any thoughts on having HWI catch this cryptic 0x6982 status code and throw a more clear error (that we can then just pass on to the user)?

  67. brunoerg commented at 2:53 PM on December 22, 2023: contributor

    @achow101 any thoughts on having HWI catch this cryptic 0x6982 status code and throw a more clear error (that we can then just pass on to the user)?

    Not sure, but this status code is specific from Ledger?

  68. Sjors commented at 3:38 PM on December 22, 2023: member

    @brunoerg most likely yes, but I can't find a full list anywhere.

  69. brunoerg commented at 4:10 PM on December 22, 2023: contributor

    @brunoerg most likely yes, but I can't find a full list anywhere.

    Me either. I've some of them but I manually mapped.


    edit: I found them in HWI:

    class DeviceException(Exception):  # pylint: disable=too-few-public-methods
        exc: Dict[int, Any] = {
            0x6985: DenyError,
            0x6982: SecurityStatusNotSatisfiedError,
            0x6A80: IncorrectDataError,
            0x6A82: NotSupportedError,
            0x6A86: WrongP1P2Error,
            0x6A87: WrongDataLengthError,
            0x6D00: InsNotSupportedError,
            0x6E00: ClaNotSupportedError,
            0xB000: WrongResponseLengthError,
            0xB007: BadStateError,
            0xB008: SignatureFailError,
            0xE000: InterruptedExecution,  # not an error
        }
    
  70. DrahtBot added the label CI failed on Jan 14, 2024
  71. achow101 referenced this in commit 59eeb858b1 on Jan 17, 2024
  72. Sjors force-pushed on Feb 13, 2024
  73. Sjors commented at 12:35 PM on February 13, 2024: member

    Rebased to give CI another try.

    HWI now returns better errors, though I have not yet tested it here.

  74. DrahtBot removed the label CI failed on Feb 13, 2024
  75. Sjors commented at 10:05 AM on February 16, 2024: member

    Tested with HWI 2.4.0

    When a Ledger Nano X is in screen saver mode, I now get the following error:

    '...hwi' error: Could not open client or get fingerprint information: ('0x5515', 'Error in <DefaultInsType.GET_VERSION: 1> command', '')
    

    That's what HWI returns, so can't make it much better...

    When the Bitcoin app is not opened on the device the error is more clear:

    '...hwi' error: Could not open client or get fingerprint information: Ledger is not in either the Bitcoin or Bitcoin Testnet app
    
  76. test: use h marker for external signer mock
    Consistent with #26076
    6c1a2cc09a
  77. wallet: compare address returned by displayaddress
    Update external signer documentation to reflect this requirement, which HWI already implements.
    dc55531087
  78. wallet: return and display signer error
    Both RPC and GUI now render a useful error message instead of (silently) failing.
    
    Replace bool with util::Result<void> to clarify that this either succeeds or returns an error message.
    4357158c47
  79. Sjors force-pushed on Apr 16, 2024
  80. Sjors commented at 4:01 PM on April 16, 2024: member

    Rebased after #28981 (I previously already tested on top of that).

    Briefly tested with HWI 3.0 as well on a Ledger Nano X.

  81. Sjors requested review from brunoerg on Apr 16, 2024
  82. brunoerg commented at 6:05 PM on April 17, 2024: contributor

    ACK 4357158c4712d479522d5cd441ad4dd1693fdd05

    I did same test as #24313 (comment) but with the latest version of HWI.

    ./src/bitcoin-cli -rpcwallet=ledger7 walletdisplayaddress "bc1qfshzjl770c6qz560cade6ynswj2a3scqzm4a4m"
    error code: -1
    error message:
    '/Users/brunogarcia/projects/HWI/hwi.py' error: Could not open client or get fingerprint information: Ledger is not in either the Bitcoin or Bitcoin Testnet app
    
    ./src/bitcoin-cli -rpcwallet=ledger7 walletdisplayaddress "randomaddresshere"
    error code: -1
    error message:
    There is no ScriptPubKeyManager for this address
    
  83. DrahtBot requested review from achow101 on Apr 17, 2024
  84. achow101 commented at 9:13 PM on April 23, 2024: member

    ACK 4357158c4712d479522d5cd441ad4dd1693fdd05

  85. achow101 merged this on Apr 23, 2024
  86. achow101 closed this on Apr 23, 2024

  87. Sjors deleted the branch on May 6, 2024
  88. bitcoin locked this on May 6, 2025

github-metadata-mirror

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

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