aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorW. J. van der Laan <laanwj@protonmail.com>2021-06-24 13:44:31 +0200
committerW. J. van der Laan <laanwj@protonmail.com>2021-06-24 14:20:28 +0200
commit0553d75268a94630225fa754841e28d88d751665 (patch)
tree36c9545064ab938751b79ae5b54c0ec9c0e139ff
parentb7565c708dc1c334aea12ab91a7c67695af802f8 (diff)
parent754f134a50cc56cdf0baf996d909c992770fcc97 (diff)
downloadbitcoin-0553d75268a94630225fa754841e28d88d751665.tar.xz
Merge bitcoin/bitcoin#22154: Add OutputType::BECH32M and related wallet support for fetching bech32m addresses
754f134a50cc56cdf0baf996d909c992770fcc97 wallet: Add error message to GetReservedDestination (Andrew Chow) 87a0e7a3b7c0ffd545e537bd497cdc3e67d045f6 Disallow bech32m addresses for legacy wallet things (Andrew Chow) 6dbe4d10728f882986ed0d9ed77bc736f051c662 Use BECH32M for tr() desc, WitV1Taproot, and WitUnknown CTxDests (Andrew Chow) 699dfcd8ad9487a4e04c1ffc68211e84e126b3d2 Opportunistically use bech32m change addresses if available (Andrew Chow) 0262536c34567743e527dad46912c9ba493252cd Add OutputType::BECH32M (Andrew Chow) 177c15d2f7cd5406ddbce8217fc023057539b828 Limit LegacyScriptPubKeyMan address types (Andrew Chow) Pull request description: 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. ACKs for top commit: laanwj: re-review ACK 754f134a50cc56cdf0baf996d909c992770fcc97 Sjors: re-utACK 754f134: only change is switching to `bech32m` in two `wallet_taproot.py` test cases. fjahr: re-ACK 754f134a50cc56cdf0baf996d909c992770fcc97 jonatack: ACK 754f134a50cc56cdf0baf996d909c992770fcc97 Tree-SHA512: 6ea90867d3631d0d438e2b08ce6ed930f37d01323224661e8e38f183ea5ee2ab65b5891394a3612c7382a1aff907b457616c6725665a10c320174017b998ca9f
-rw-r--r--src/outputtype.cpp23
-rw-r--r--src/outputtype.h5
-rw-r--r--src/rpc/misc.cpp3
-rw-r--r--src/script/descriptor.cpp16
-rw-r--r--src/wallet/rpcdump.cpp9
-rw-r--r--src/wallet/rpcwallet.cpp9
-rw-r--r--src/wallet/scriptpubkeyman.cpp29
-rw-r--r--src/wallet/scriptpubkeyman.h13
-rw-r--r--src/wallet/spend.cpp5
-rw-r--r--src/wallet/wallet.cpp25
-rw-r--r--src/wallet/wallet.h2
-rwxr-xr-xtest/functional/rpc_createmultisig.py7
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py2
-rwxr-xr-xtest/functional/wallet_address_types.py10
-rwxr-xr-xtest/functional/wallet_basic.py3
-rwxr-xr-xtest/functional/wallet_importmulti.py21
-rwxr-xr-xtest/functional/wallet_keypool.py2
-rwxr-xr-xtest/functional/wallet_labels.py52
-rwxr-xr-xtest/functional/wallet_taproot.py6
19 files changed, 181 insertions, 61 deletions
diff --git a/src/outputtype.cpp b/src/outputtype.cpp
index d96fb282c5..8ede7b9974 100644
--- a/src/outputtype.cpp
+++ b/src/outputtype.cpp
@@ -18,6 +18,7 @@
static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit";
static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32";
+static const std::string OUTPUT_TYPE_STRING_BECH32M = "bech32m";
bool ParseOutputType(const std::string& type, OutputType& output_type)
{
@@ -30,6 +31,9 @@ bool ParseOutputType(const std::string& type, OutputType& output_type)
} else if (type == OUTPUT_TYPE_STRING_BECH32) {
output_type = OutputType::BECH32;
return true;
+ } else if (type == OUTPUT_TYPE_STRING_BECH32M) {
+ output_type = OutputType::BECH32M;
+ return true;
}
return false;
}
@@ -40,6 +44,7 @@ const std::string& FormatOutputType(OutputType type)
case OutputType::LEGACY: return OUTPUT_TYPE_STRING_LEGACY;
case OutputType::P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT;
case OutputType::BECH32: return OUTPUT_TYPE_STRING_BECH32;
+ case OutputType::BECH32M: return OUTPUT_TYPE_STRING_BECH32M;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -59,6 +64,7 @@ CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type)
return witdest;
}
}
+ case OutputType::BECH32M: {} // This function should never be used with BECH32M, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@@ -98,6 +104,23 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
return ScriptHash(witprog);
}
}
+ case OutputType::BECH32M: {} // This function should not be used for BECH32M, so let it assert
} // no default case, so the compiler can warn about missing cases
assert(false);
}
+
+std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
+ if (std::holds_alternative<PKHash>(dest) ||
+ std::holds_alternative<ScriptHash>(dest)) {
+ return OutputType::LEGACY;
+ }
+ if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
+ std::holds_alternative<WitnessV0ScriptHash>(dest)) {
+ return OutputType::BECH32;
+ }
+ if (std::holds_alternative<WitnessV1Taproot>(dest) ||
+ std::holds_alternative<WitnessUnknown>(dest)) {
+ return OutputType::BECH32M;
+ }
+ return std::nullopt;
+}
diff --git a/src/outputtype.h b/src/outputtype.h
index 88422e5824..2b83235cd0 100644
--- a/src/outputtype.h
+++ b/src/outputtype.h
@@ -18,12 +18,14 @@ enum class OutputType {
LEGACY,
P2SH_SEGWIT,
BECH32,
+ BECH32M,
};
static constexpr auto OUTPUT_TYPES = std::array{
OutputType::LEGACY,
OutputType::P2SH_SEGWIT,
OutputType::BECH32,
+ OutputType::BECH32M,
};
[[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type);
@@ -45,4 +47,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
*/
CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType);
+/** Get the OutputType for a CTxDestination */
+std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest);
+
#endif // BITCOIN_OUTPUTTYPE_H
diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp
index ab239fe79c..5178ce60e8 100644
--- a/src/rpc/misc.cpp
+++ b/src/rpc/misc.cpp
@@ -131,6 +131,9 @@ static RPCHelpMan createmultisig()
if (!ParseOutputType(request.params[2].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
}
+ if (output_type == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
+ }
}
// Construct using pay-to-script-hash:
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index be97a618f3..fdbd2d7fc7 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -640,20 +640,6 @@ public:
std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
};
-static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
- if (std::holds_alternative<PKHash>(dest) ||
- std::holds_alternative<ScriptHash>(dest)) {
- return OutputType::LEGACY;
- }
- if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
- std::holds_alternative<WitnessV0ScriptHash>(dest) ||
- std::holds_alternative<WitnessV1Taproot>(dest) ||
- std::holds_alternative<WitnessUnknown>(dest)) {
- return OutputType::BECH32;
- }
- return std::nullopt;
-}
-
/** A parsed addr(A) descriptor. */
class AddressDescriptor final : public DescriptorImpl
{
@@ -874,7 +860,7 @@ public:
{
assert(m_subdescriptor_args.size() == m_depths.size());
}
- std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32M; }
bool IsSingleType() const final { return true; }
};
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 4e9ba83ead..35649ab02c 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -286,6 +286,9 @@ RPCHelpMan importaddress()
if (fP2SH) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
}
+ if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
+ }
pwallet->MarkDirty();
@@ -962,6 +965,9 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
}
+ if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
+ }
script = GetScriptForDestination(dest);
} else {
if (!IsHex(output)) {
@@ -1086,6 +1092,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
if (!parsed_desc) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
}
+ if (parsed_desc->GetOutputType() == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets");
+ }
have_solving_data = parsed_desc->IsSolvable();
const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index e03835eaff..bc5d771b6e 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -269,6 +269,9 @@ static RPCHelpMan getnewaddress()
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
}
+ if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
+ }
}
CTxDestination dest;
@@ -313,6 +316,9 @@ static RPCHelpMan getrawchangeaddress()
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
}
+ if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
+ }
}
CTxDestination dest;
@@ -1004,6 +1010,9 @@ static RPCHelpMan addmultisigaddress()
if (!ParseOutputType(request.params[3].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
}
+ if (output_type == OutputType::BECH32M) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets");
+ }
}
// Construct using pay-to-script-hash:
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index c8baa0665e..44c3912544 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -22,6 +22,12 @@ 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;
+ return false;
+ }
+ assert(type != OutputType::BECH32M);
+
LOCK(cs_KeyStore);
error.clear();
@@ -289,14 +295,22 @@ bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBat
return true;
}
-bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool)
+bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, 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;
+ return false;
+ }
+ assert(type != OutputType::BECH32M);
+
LOCK(cs_KeyStore);
if (!CanGetAddresses(internal)) {
+ error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
return false;
}
if (!ReserveKeyFromKeyPool(index, keypool, internal)) {
+ error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
return false;
}
address = GetDestinationForKey(keypool.vchPubKey, type);
@@ -1294,6 +1308,7 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const
void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type)
{
+ assert(type != OutputType::BECH32M);
// Remove from key pool
WalletBatch batch(m_storage.GetDatabase());
batch.ErasePool(nIndex);
@@ -1327,6 +1342,7 @@ void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, co
bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal)
{
+ assert(type != OutputType::BECH32M);
if (!CanGetAddresses(internal)) {
return false;
}
@@ -1395,6 +1411,7 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key
void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type)
{
+ assert(type != OutputType::BECH32M);
if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
CScript witprog = GetScriptForDestination(witdest);
@@ -1706,10 +1723,9 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
return true;
}
-bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool)
+bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error)
{
LOCK(cs_desc_man);
- std::string error;
bool result = GetNewDestination(type, address, error);
index = m_wallet_descriptor.next_index - 1;
return result;
@@ -1880,6 +1896,12 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, OutputType addr_type)
{
+ if (addr_type == OutputType::BECH32M) {
+ // Don't allow setting up taproot descriptors yet
+ // TODO: Allow setting up taproot descriptors
+ return false;
+ }
+
LOCK(cs_desc_man);
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -1909,6 +1931,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
desc_prefix = "wpkh(" + xpub + "/84'";
break;
}
+ case OutputType::BECH32M: assert(false); // TODO: Setup taproot descriptor
} // no default case, so the compiler can warn about missing cases
assert(!desc_prefix.empty());
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 3c4603608c..b2ca354b0a 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -181,7 +181,7 @@ public:
virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; }
virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; }
- virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { return false; }
+ virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) { return false; }
virtual void KeepDestination(int64_t index, const OutputType& type) {}
virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {}
@@ -254,6 +254,13 @@ public:
boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
};
+/** OutputTypes supported by the LegacyScriptPubKeyMan */
+static const std::unordered_set<OutputType> LEGACY_OUTPUT_TYPES {
+ OutputType::LEGACY,
+ OutputType::P2SH_SEGWIT,
+ OutputType::BECH32,
+};
+
class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider
{
private:
@@ -357,7 +364,7 @@ public:
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override;
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override;
void KeepDestination(int64_t index, const OutputType& type) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override;
@@ -566,7 +573,7 @@ public:
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override;
- bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override;
+ bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, std::string& error) override;
void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) override;
// Tops up the descriptor cache and m_map_script_pub_keys. The cache is stored in the wallet file
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index c8ded4c51e..6a8df437ae 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -618,8 +618,9 @@ bool CWallet::CreateTransactionInternal(
// Reserve a new key pair from key pool. If it fails, provide a dummy
// destination in case we don't need change.
CTxDestination dest;
- if (!reservedest.GetReservedDestination(dest, true)) {
- error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.");
+ std::string dest_err;
+ if (!reservedest.GetReservedDestination(dest, true, dest_err)) {
+ error = strprintf(_("Transaction needs a change address, but we can't generate it. %s"), dest_err);
}
scriptChange = GetScriptForDestination(dest);
// A valid destination implies a change script (and
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 256faf2b23..c2586b60b8 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1909,7 +1909,13 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang
int witnessversion = 0;
std::vector<unsigned char> witnessprogram;
if (recipient.scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
- return OutputType::BECH32;
+ if (GetScriptPubKeyMan(OutputType::BECH32M, true)) {
+ return OutputType::BECH32M;
+ } else if (GetScriptPubKeyMan(OutputType::BECH32, true)) {
+ return OutputType::BECH32;
+ } else {
+ return m_default_address_type;
+ }
}
}
@@ -2112,7 +2118,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;
}
if (result) {
SetAddressBook(dest, label, "receive");
@@ -2127,8 +2133,7 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des
error.clear();
ReserveDestination reservedest(this, type);
- if (!reservedest.GetReservedDestination(dest, true)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first").translated;
+ if (!reservedest.GetReservedDestination(dest, true, error)) {
return false;
}
@@ -2175,10 +2180,11 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
return result;
}
-bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal)
+bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, std::string& error)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
+ error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type)).translated;
return false;
}
@@ -2188,7 +2194,7 @@ bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool inter
m_spk_man->TopUp();
CKeyPool keypool;
- if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) {
+ if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool, error)) {
return false;
}
fInternal = keypool.fInternal;
@@ -3033,7 +3039,7 @@ void CWallet::SetupLegacyScriptPubKeyMan()
}
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
- for (const auto& type : OUTPUT_TYPES) {
+ for (const auto& type : LEGACY_OUTPUT_TYPES) {
m_internal_spk_managers[type] = spk_manager.get();
m_external_spk_managers[type] = spk_manager.get();
}
@@ -3086,6 +3092,11 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
+ if (t == OutputType::BECH32M) {
+ // Skip taproot (bech32m) for now
+ // TODO: Setup taproot (bech32m) descriptors by default
+ continue;
+ }
auto spk_manager = std::unique_ptr<DescriptorScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, internal));
if (IsCrypted()) {
if (IsLocked()) {
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 66f39edb4d..b63938c5f1 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -181,7 +181,7 @@ public:
}
//! Reserve an address
- bool GetReservedDestination(CTxDestination& pubkey, bool internal);
+ bool GetReservedDestination(CTxDestination& pubkey, bool internal, std::string& error);
//! Return reserved address
void ReturnDestination();
//! Keep the address. Do not return it's key to the keypool when this object goes out of scope
diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py
index af515f3a27..816ec67492 100755
--- a/test/functional/rpc_createmultisig.py
+++ b/test/functional/rpc_createmultisig.py
@@ -97,6 +97,9 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
sorted_key_desc = descsum_create('sh(multi(2,{}))'.format(sorted_key_str))
assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address'])
+ # Check that bech32m is currently not allowed
+ assert_raises_rpc_error(-5, "createmultisig cannot create bech32m multisig addresses", self.nodes[0].createmultisig, 2, self.pub, "bech32m")
+
def check_addmultisigaddress_errors(self):
if self.options.descriptors:
return
@@ -108,6 +111,10 @@ class RpcCreateMultiSigTest(BitcoinTestFramework):
self.nodes[0].importaddress(a)
assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
+ # Bech32m address type is disallowed for legacy wallets
+ pubs = [self.nodes[1].getaddressinfo(addr)["pubkey"] for addr in addresses]
+ assert_raises_rpc_error(-5, "Bech32m multisig addresses cannot be created with legacy wallets", self.nodes[0].addmultisigaddress, 2, pubs, "", "bech32m")
+
def checkbalances(self):
node0, node1, node2 = self.nodes
node0.generate(COINBASE_MATURITY)
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 4b07a32c54..fa98c44152 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -551,7 +551,7 @@ class RawTransactionsTest(BitcoinTestFramework):
# creating the key must be impossible because the wallet is locked
outputs = {self.nodes[0].getnewaddress():1.1}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
- assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", self.nodes[1].fundrawtransaction, rawtx)
+ assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx)
# Refill the keypool.
self.nodes[1].walletpassphrase("test", 100)
diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py
index 6d93cf412f..9b97d08424 100755
--- a/test/functional/wallet_address_types.py
+++ b/test/functional/wallet_address_types.py
@@ -373,5 +373,15 @@ class AddressTypeTest(BitcoinTestFramework):
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")
+ # TODO: Remove this when they do
+ assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getnewaddress, "", "bech32m")
+ assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getrawchangeaddress, "bech32m")
+ else:
+ self.log.info("Legacy wallets cannot make bech32m addresses")
+ assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getnewaddress, "", "bech32m")
+ assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getrawchangeaddress, "bech32m")
+
if __name__ == '__main__':
AddressTypeTest().main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index a052ec7477..b5afc3785e 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -420,6 +420,9 @@ class WalletTest(BitcoinTestFramework):
# This will raise an exception for importing an invalid pubkey
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
+ # Bech32m addresses cannot be imported into a legacy wallet
+ assert_raises_rpc_error(-5, "Bech32m addresses cannot be imported into legacy wallets", self.nodes[0].importaddress, "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6")
+
# Import address and private key to check correct behavior of spendable unspents
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py
index 0a00c5eed9..baeac655df 100755
--- a/test/functional/wallet_importmulti.py
+++ b/test/functional/wallet_importmulti.py
@@ -746,6 +746,27 @@ class ImportMultiTest(BitcoinTestFramework):
assert 'hdmasterfingerprint' not in pub_import_info
assert 'hdkeypath' not in pub_import_info
+ # Bech32m addresses and descriptors cannot be imported
+ self.log.info("Bech32m addresses and descriptors cannot be imported")
+ self.test_importmulti(
+ {
+ "scriptPubKey": {"address": "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6"},
+ "timestamp": "now",
+ },
+ success=False,
+ error_code=-5,
+ error_message="Bech32m addresses cannot be imported into legacy wallets",
+ )
+ self.test_importmulti(
+ {
+ "desc": descsum_create("tr({})".format(pub)),
+ "timestamp": "now",
+ },
+ success=False,
+ error_code=-5,
+ error_message="Bech32m descriptors cannot be imported into legacy wallets",
+ )
+
# Import some public keys to the keypool of a no privkey wallet
self.log.info("Adding pubkey to keypool of disableprivkey wallet")
self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True)
diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py
index 3fe6adeebc..28bfc9116f 100755
--- a/test/functional/wallet_keypool.py
+++ b/test/functional/wallet_keypool.py
@@ -161,7 +161,7 @@ class KeyPoolTest(BitcoinTestFramework):
# Using a fee rate (10 sat / byte) well above the minimum relay rate
# creating a 5,000 sat transaction with change should not be possible
- assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
+ assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
# creating a 10,000 sat transaction without change, with a manual input, should still be possible
res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010})
diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py
index 2d792bac52..a571454acf 100755
--- a/test/functional/wallet_labels.py
+++ b/test/functional/wallet_labels.py
@@ -135,31 +135,33 @@ class WalletLabelsTest(BitcoinTestFramework):
# in the label. This is a no-op.
change_label(node, labels[2].addresses[0], labels[2], labels[2])
- self.log.info('Check watchonly labels')
- node.createwallet(wallet_name='watch_only', disable_private_keys=True)
- wallet_watch_only = node.get_wallet_rpc('watch_only')
- BECH32_VALID = {
- '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn',
- '✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr',
- '✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw',
- }
- BECH32_INVALID = {
- '❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8',
- '❌_VER16_PROB01': 'bcrt1sqq5r4036',
- }
- for l in BECH32_VALID:
- ad = BECH32_VALID[l]
- wallet_watch_only.importaddress(label=l, rescan=False, address=ad)
- node.generatetoaddress(1, ad)
- assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}})
- assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0)
- for l in BECH32_INVALID:
- ad = BECH32_INVALID[l]
- assert_raises_rpc_error(
- -5,
- "Address is not valid" if self.options.descriptors else "Invalid Bitcoin address or script",
- lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad),
- )
+ if self.options.descriptors:
+ # This is a descriptor wallet test because of segwit v1+ addresses
+ self.log.info('Check watchonly labels')
+ node.createwallet(wallet_name='watch_only', disable_private_keys=True)
+ wallet_watch_only = node.get_wallet_rpc('watch_only')
+ BECH32_VALID = {
+ '✔️_VER15_PROG40': 'bcrt10qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxkg7fn',
+ '✔️_VER16_PROG03': 'bcrt1sqqqqq8uhdgr',
+ '✔️_VER16_PROB02': 'bcrt1sqqqq4wstyw',
+ }
+ BECH32_INVALID = {
+ '❌_VER15_PROG41': 'bcrt1sqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqajlxj8',
+ '❌_VER16_PROB01': 'bcrt1sqq5r4036',
+ }
+ for l in BECH32_VALID:
+ ad = BECH32_VALID[l]
+ wallet_watch_only.importaddress(label=l, rescan=False, address=ad)
+ node.generatetoaddress(1, ad)
+ assert_equal(wallet_watch_only.getaddressesbylabel(label=l), {ad: {'purpose': 'receive'}})
+ assert_equal(wallet_watch_only.getreceivedbylabel(label=l), 0)
+ for l in BECH32_INVALID:
+ ad = BECH32_INVALID[l]
+ assert_raises_rpc_error(
+ -5,
+ "Address is not valid" if self.options.descriptors else "Invalid Bitcoin address or script",
+ lambda: wallet_watch_only.importaddress(label=l, rescan=False, address=ad),
+ )
class Label:
diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py
index 0eac814c52..9eb204bf37 100755
--- a/test/functional/wallet_taproot.py
+++ b/test/functional/wallet_taproot.py
@@ -226,7 +226,7 @@ class WalletTaprootTest(BitcoinTestFramework):
result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}])
assert(result[0]['success'])
for i in range(4):
- addr_g = self.addr_gen.getnewaddress(address_type='bech32')
+ addr_g = self.addr_gen.getnewaddress(address_type='bech32m')
if treefn is not None:
addr_r = self.make_addr(treefn, keys, i)
assert_equal(addr_g, addr_r)
@@ -265,7 +265,7 @@ class WalletTaprootTest(BitcoinTestFramework):
result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
assert(result[0]['success'])
for i in range(4):
- addr_g = self.rpc_online.getnewaddress(address_type='bech32')
+ addr_g = self.rpc_online.getnewaddress(address_type='bech32m')
if treefn is not None:
addr_r = self.make_addr(treefn, keys_pay, i)
assert_equal(addr_g, addr_r)
@@ -296,7 +296,7 @@ class WalletTaprootTest(BitcoinTestFramework):
result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
assert(result[0]['success'])
for i in range(4):
- addr_g = self.psbt_online.getnewaddress(address_type='bech32')
+ addr_g = self.psbt_online.getnewaddress(address_type='bech32m')
if treefn is not None:
addr_r = self.make_addr(treefn, keys_pay, i)
assert_equal(addr_g, addr_r)