Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OutputType::BECH32M and related wallet support for fetching bech32m addresses #22154

Merged
merged 6 commits into from
Jun 24, 2021

Conversation

achow101
Copy link
Member

@achow101 achow101 commented Jun 4, 2021

Currently bech32m addresses are classfied as bech32. Because bech32m is incompatible with bech32, we need to define a new OutputType for it so that it can be handled correctly. This PR adds OutputType::BECH32M, updates all of the relevant OutputType classifications, and handle requests for bech32m addresses. There is now a bech32m address type string that can be used.

  • tr() descriptors now report their output type as OutputType::BECH32M. WtinessV1Taproot and WitnessUnknown are also classified as OutputType::BECH32M.
  • Bech32m addresses are completely disabled for legacy wallets. They cannot be imported (explicitly disallowed in importaddress and importmulti), will not be created when getting all destinations for a pubkey, and will not be added with addmultisigaddress. Additional protections have been added to LegacyScriptPubKeyMan to disallow attempting to retrieve bech32m addresses.
  • Since Taproot multisigs are not implemented yet, createmultisig will also disallow the bech32m address type.
  • As Taproot is not yet active, DescriptorScriptPubKeyMan cannot and will not create a tr() descriptor. Protections have been added to make sure this cannot occur.
  • The change address type detection algorithm has been updated to return bech32m when there is a segwit v1+ output script and the wallet has a bech32m ScriptPubKeyMan, falling back to bech32 if one is not available.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 5, 2021

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

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.

src/rpc/misc.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@fjahr fjahr left a comment

Choose a reason for hiding this comment

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

Looks close, only some style nits and open questions/suggestions.

@@ -254,6 +254,13 @@ class ScriptPubKeyMan
boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
};

/** OutputTypes supported by the LegacyScriptPubKeyMan */
static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: could use constexpr I think

Copy link
Member Author

Choose a reason for hiding this comment

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

Tried that but it doesn't work.

Copy link
Member

Choose a reason for hiding this comment

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

You can't constexpr things that need allocation. You could use an array instead of an unordered_set, and use std::find to look up (which will likely be faster too, due to the tiny size), but it really doesn't matter here.

test/functional/wallet_address_types.py Outdated Show resolved Hide resolved
test/functional/wallet_address_types.py Outdated Show resolved Hide resolved
src/wallet/scriptpubkeyman.cpp Show resolved Hide resolved
src/wallet/wallet.cpp Outdated Show resolved Hide resolved
@fjahr
Copy link
Contributor

fjahr commented Jun 13, 2021

utACK 05ff76c

Copy link
Member

@Sjors Sjors left a comment

Choose a reason for hiding this comment

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

tACK 05ff76c

I made a followup PR that makes the defaults a bit more friendly, and adds GUI support: #22260

@@ -23,6 +23,11 @@ const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;

bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
Copy link
Member

Choose a reason for hiding this comment

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

a137346 : is this just belt and suspenders? When I do getnewaddress Test bech32m on legacy wallet for this commit, it's caught earlier and returns No bech32m addresses available. That's because CWallet::GetNewDestination tries GetScriptPubKeyMan first for the requested type.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, just belt and suspenders.

@@ -1906,7 +1906,12 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
// Check if any destination contains a witness program:
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
const bool is_wit = recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram);
if (is_wit && witnessversion >= 1 && GetScriptPubKeyMan(OutputType::BECH32M, true)) {
Copy link
Member

Choose a reason for hiding this comment

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

As an extra safety measure, maybe double-check that Taproot is active like in #22156

Copy link
Member Author

Choose a reason for hiding this comment

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

Since this is generic for future segwit versions, I'm not sure doing that makes sense. For taproot specifically, there won't be a bech32m ScriptPubKeyMan until taproot activates anyways.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
@Sjors
Copy link
Member

Sjors commented Jun 17, 2021

re-utACK f70d55c

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

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

left two comments.

src/wallet/wallet.cpp Outdated Show resolved Hide resolved
@@ -2115,7 +2115,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label,
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
} else {
error = strprintf("Error: No %s addresses available.", FormatOutputType(type));
error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
Copy link
Member

Choose a reason for hiding this comment

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

Not sure how difficult it would be to fix, but the general idea for translations is to pass up the bilingual string and let the caller pick the right version. Otherwise the RPC responses (and debug log) might be partially translated. Functional test might fail when run with the gui.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that will be a bit invasive since several of the functions involved use std::string error rather than bilingual_str. Perhaps for a followup.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, if you're going to translate anything, it's better to use bilingual_str. I would prefer to do this right in the first go, instead of having another follow-up commit to prevent RPC messages from being translated.

Alternatively just leave this untranslated for now. This is marked for 22.0 and we've passed the translation string freeze.

Copy link
Member Author

Choose a reason for hiding this comment

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

For the strings I added, I was just following the pattern already in use in these functions. For this change in particular, it was to make the style unified. Unless this is considered a blocker, I'm going to leave it as is.

Copy link
Member

Choose a reason for hiding this comment

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

Ok…

@achow101 achow101 force-pushed the outputtype-bech32m branch 2 times, most recently from cae6672 to f33361c Compare June 17, 2021 18:39
@practicalswift
Copy link
Contributor

Concept ACK

@Sjors
Copy link
Member

Sjors commented Jun 18, 2021

re-utACK f33361c

@fjahr
Copy link
Contributor

fjahr commented Jun 20, 2021

re-ACK f33361c

The only changes since my last review was a reorganization of the code changes in TransactionChangeType() and ensuring there that BECH32 address types are also available before using them. That commit was also slightly renamed.

@laanwj
Copy link
Member

laanwj commented Jun 21, 2021

Code review ACK f33361c
re-review ACK 754f134

I think the potential RPC error message translation issue mentioned in #22154 (comment) needs to be addressed at some point, but also think it's not a blocker for 22.0.

@meshcollider
Copy link
Contributor

I get an error in wallet_taproot.py:

Remaining jobs: [wallet_taproot.py]
1/1 - wallet_taproot.py failed, Duration: 1 s

stdout:
2021-06-23T01:47:26.095000Z TestFramework (INFO): Initializing test directory /tmp/test_runner__🏃_20210623_134725/wallet_taproot_0
2021-06-23T01:47:26.359000Z TestFramework (INFO): Creating wallets...
2021-06-23T01:47:26.605000Z TestFramework (INFO): Mining blocks...
2021-06-23T01:47:26.694000Z TestFramework (INFO): Testing tr(XPRV) address derivation
2021-06-23T01:47:26.745000Z TestFramework (INFO): Testing tr(XPRV) through sendtoaddress
2021-06-23T01:47:26.770000Z TestFramework (ERROR): JSONRPC error
Traceback (most recent call last):
  File "/home/meshcollider/bitcoin/test/functional/test_framework/test_framework.py", line 128, in main
    self.run_test()
  File "/home/meshcollider/bitcoin/test/functional/wallet_taproot.py", line 347, in run_test
    1
  File "/home/meshcollider/bitcoin/test/functional/wallet_taproot.py", line 314, in do_test
    self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys])
  File "/home/meshcollider/bitcoin/test/functional/wallet_taproot.py", line 262, in do_test_sendtoaddress
    addr_g = self.rpc_online.getnewaddress(address_type='bech32')
  File "/home/meshcollider/bitcoin/test/functional/test_framework/coverage.py", line 47, in __call__
    return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
  File "/home/meshcollider/bitcoin/test/functional/test_framework/authproxy.py", line 146, in __call__
    raise JSONRPCException(response['error'], status)
test_framework.authproxy.JSONRPCException: Error: No bech32 addresses available. (-12)
2021-06-23T01:47:26.821000Z TestFramework (INFO): Stopping nodes
2021-06-23T01:47:27.028000Z TestFramework (WARNING): Not cleaning up dir /tmp/test_runner__🏃_20210623_134725/wallet_taproot_0
2021-06-23T01:47:27.028000Z TestFramework (ERROR): Test failed. Test logging available at /tmp/test_runner__🏃_20210623_134725/wallet_taproot_0/test_framework.log

Make sure that LegacyScriptPubKeyMan can only be used for legacy,
p2sh-segwit, and bech32 address types.
Bech32m addresses need their own OutputType

We are not ready to create DescriptorScriptPubKeyMans which produce
bech32m addresses. So don't allow generating them.
If a transaction as a segwit output, use a bech32m change address if
they are available. If not, fallback to bech32. If bech32 change
addresses are unavailable, fallback to the default address type.
The tr() descriptor, WitnessV1Taproot CTxDestination, and
WitnessUnknown CTxDestination are OutputType::BECH32M so they should
report as such.
We don't want the legacy wallet to ever have bech32m addresses so don't
allow importing them. This includes addmultisigaddress as that is a
legacy wallet only RPC

Additionally, bech32m multisigs are not available yet, so disallow them
in createmultisig.
Adds an error output parameter to all GetReservedDestination functions
so that callers can get the actual reason that a change address could
not be fetched. This more closely matches GetNewDestination. This allows
for more granular error messages, such as one that indicates that
bech32m addresses cannot be generated yet.
@achow101
Copy link
Member Author

Silent merge conflict, rebased and fixed.

@Sjors
Copy link
Member

Sjors commented Jun 23, 2021

re-utACK 754f134: only change is switching to bech32m in two wallet_taproot.py test cases.

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

ACK 754f134

When should bech32m be added to the -addresstype and -changetype wallet config option helps?

@@ -640,20 +640,6 @@ class DescriptorImpl : public Descriptor
std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
};

static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
Copy link
Member

@jonatack jonatack Jun 23, 2021

Choose a reason for hiding this comment

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

In commit 87a0e7a, src/script/descriptor.cpp should include outputtype.h where OutputTypeFromDestination() is moved.

--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -5,6 +5,7 @@
 #include <script/descriptor.h>
 
 #include <key_io.h>
+#include <outputtype.h>
 #include <pubkey.h>
 #include <script/script.h>
 #include <script/standard.h>

@@ -373,5 +373,15 @@ def run_test(self):
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32')

if self.options.descriptors:
self.log.info("Descriptor wallets do not have bech32m addresses by default yet")
Copy link
Member

Choose a reason for hiding this comment

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

s/addreses/addresses/ should be fixed one commit earlier in 87a0e7a

@achow101
Copy link
Member Author

When should bech32m be added to the -addresstype and -changetype wallet config option helps?

When we make bech32m descriptors by default.

@fjahr
Copy link
Contributor

fjahr commented Jun 23, 2021

re-ACK 754f134

Confirmed only changes were rebasing and fixing wallet_taproot.py.

@laanwj laanwj merged commit 0553d75 into bitcoin:master Jun 24, 2021
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Jun 24, 2021
@Sjors
Copy link
Member

Sjors commented Jun 24, 2021

I rebased my followup in #22260 (which probably works best if you don't set -addresstype)

gwillen pushed a commit to ElementsProject/elements that referenced this pull request Jun 1, 2022
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Aug 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants