Skip to content

Commit 87a0e7a

Browse files
committed
Disallow bech32m addresses for legacy wallet things
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.
1 parent 6dbe4d1 commit 87a0e7a

File tree

12 files changed

+113
-41
lines changed

12 files changed

+113
-41
lines changed

src/outputtype.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,19 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
108108
} // no default case, so the compiler can warn about missing cases
109109
assert(false);
110110
}
111+
112+
std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
113+
if (std::holds_alternative<PKHash>(dest) ||
114+
std::holds_alternative<ScriptHash>(dest)) {
115+
return OutputType::LEGACY;
116+
}
117+
if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
118+
std::holds_alternative<WitnessV0ScriptHash>(dest)) {
119+
return OutputType::BECH32;
120+
}
121+
if (std::holds_alternative<WitnessV1Taproot>(dest) ||
122+
std::holds_alternative<WitnessUnknown>(dest)) {
123+
return OutputType::BECH32M;
124+
}
125+
return std::nullopt;
126+
}

src/outputtype.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
4747
*/
4848
CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType);
4949

50+
/** Get the OutputType for a CTxDestination */
51+
std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest);
52+
5053
#endif // BITCOIN_OUTPUTTYPE_H

src/rpc/misc.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ static RPCHelpMan createmultisig()
131131
if (!ParseOutputType(request.params[2].get_str(), output_type)) {
132132
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
133133
}
134+
if (output_type == OutputType::BECH32M) {
135+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
136+
}
134137
}
135138

136139
// Construct using pay-to-script-hash:

src/script/descriptor.cpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -640,22 +640,6 @@ class DescriptorImpl : public Descriptor
640640
std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
641641
};
642642

643-
static std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest) {
644-
if (std::holds_alternative<PKHash>(dest) ||
645-
std::holds_alternative<ScriptHash>(dest)) {
646-
return OutputType::LEGACY;
647-
}
648-
if (std::holds_alternative<WitnessV0KeyHash>(dest) ||
649-
std::holds_alternative<WitnessV0ScriptHash>(dest)) {
650-
return OutputType::BECH32;
651-
}
652-
if (std::holds_alternative<WitnessV1Taproot>(dest) ||
653-
std::holds_alternative<WitnessUnknown>(dest)) {
654-
return OutputType::BECH32M;
655-
}
656-
return std::nullopt;
657-
}
658-
659643
/** A parsed addr(A) descriptor. */
660644
class AddressDescriptor final : public DescriptorImpl
661645
{

src/wallet/rpcdump.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ RPCHelpMan importaddress()
286286
if (fP2SH) {
287287
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
288288
}
289+
if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
290+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
291+
}
289292

290293
pwallet->MarkDirty();
291294

@@ -962,6 +965,9 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP
962965
if (!IsValidDestination(dest)) {
963966
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
964967
}
968+
if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
969+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
970+
}
965971
script = GetScriptForDestination(dest);
966972
} else {
967973
if (!IsHex(output)) {
@@ -1086,6 +1092,9 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID
10861092
if (!parsed_desc) {
10871093
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
10881094
}
1095+
if (parsed_desc->GetOutputType() == OutputType::BECH32M) {
1096+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets");
1097+
}
10891098

10901099
have_solving_data = parsed_desc->IsSolvable();
10911100
const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;

src/wallet/rpcwallet.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ static RPCHelpMan getnewaddress()
269269
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
270270
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
271271
}
272+
if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
273+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
274+
}
272275
}
273276

274277
CTxDestination dest;
@@ -313,6 +316,9 @@ static RPCHelpMan getrawchangeaddress()
313316
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
314317
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
315318
}
319+
if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
320+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
321+
}
316322
}
317323

318324
CTxDestination dest;
@@ -1004,6 +1010,9 @@ static RPCHelpMan addmultisigaddress()
10041010
if (!ParseOutputType(request.params[3].get_str(), output_type)) {
10051011
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
10061012
}
1013+
if (output_type == OutputType::BECH32M) {
1014+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets");
1015+
}
10071016
}
10081017

10091018
// Construct using pay-to-script-hash:

src/wallet/scriptpubkeyman.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestinat
2626
error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types").translated;
2727
return false;
2828
}
29+
assert(type != OutputType::BECH32M);
2930

3031
LOCK(cs_KeyStore);
3132
error.clear();
@@ -299,6 +300,7 @@ bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool i
299300
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
300301
return false;
301302
}
303+
assert(type != OutputType::BECH32M);
302304

303305
LOCK(cs_KeyStore);
304306
if (!CanGetAddresses(internal)) {
@@ -1303,6 +1305,7 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const
13031305

13041306
void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type)
13051307
{
1308+
assert(type != OutputType::BECH32M);
13061309
// Remove from key pool
13071310
WalletBatch batch(m_storage.GetDatabase());
13081311
batch.ErasePool(nIndex);
@@ -1336,6 +1339,7 @@ void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, co
13361339

13371340
bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal)
13381341
{
1342+
assert(type != OutputType::BECH32M);
13391343
if (!CanGetAddresses(internal)) {
13401344
return false;
13411345
}
@@ -1404,6 +1408,7 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key
14041408

14051409
void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType type)
14061410
{
1411+
assert(type != OutputType::BECH32M);
14071412
if (key.IsCompressed() && (type == OutputType::P2SH_SEGWIT || type == OutputType::BECH32)) {
14081413
CTxDestination witdest = WitnessV0KeyHash(key.GetID());
14091414
CScript witprog = GetScriptForDestination(witdest);

test/functional/rpc_createmultisig.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ def run_test(self):
9797
sorted_key_desc = descsum_create('sh(multi(2,{}))'.format(sorted_key_str))
9898
assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0], t['address'])
9999

100+
# Check that bech32m is currently not allowed
101+
assert_raises_rpc_error(-5, "createmultisig cannot create bech32m multisig addresses", self.nodes[0].createmultisig, 2, self.pub, "bech32m")
102+
100103
def check_addmultisigaddress_errors(self):
101104
if self.options.descriptors:
102105
return
@@ -108,6 +111,10 @@ def check_addmultisigaddress_errors(self):
108111
self.nodes[0].importaddress(a)
109112
assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses))
110113

114+
# Bech32m address type is disallowed for legacy wallets
115+
pubs = [self.nodes[1].getaddressinfo(addr)["pubkey"] for addr in addresses]
116+
assert_raises_rpc_error(-5, "Bech32m multisig addresses cannot be created with legacy wallets", self.nodes[0].addmultisigaddress, 2, pubs, "", "bech32m")
117+
111118
def checkbalances(self):
112119
node0, node1, node2 = self.nodes
113120
node0.generate(COINBASE_MATURITY)

test/functional/wallet_address_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,5 +373,15 @@ def run_test(self):
373373
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
374374
self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32')
375375

376+
if self.options.descriptors:
377+
self.log.info("Descriptor wallets do not have bech32m addreses by default yet")
378+
# TODO: Remove this when they do
379+
assert_raises_rpc_error(-12, "Error: No bech32m addresses available", self.nodes[0].getnewaddress, "", "bech32m")
380+
assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", self.nodes[0].getrawchangeaddress, "bech32m")
381+
else:
382+
self.log.info("Legacy wallets cannot make bech32m addresses")
383+
assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getnewaddress, "", "bech32m")
384+
assert_raises_rpc_error(-8, "Legacy wallets cannot provide bech32m addresses", self.nodes[0].getrawchangeaddress, "bech32m")
385+
376386
if __name__ == '__main__':
377387
AddressTypeTest().main()

test/functional/wallet_basic.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,9 @@ def run_test(self):
420420
# This will raise an exception for importing an invalid pubkey
421421
assert_raises_rpc_error(-5, "Pubkey is not a valid public key", self.nodes[0].importpubkey, "5361746f736869204e616b616d6f746f")
422422

423+
# Bech32m addresses cannot be imported into a legacy wallet
424+
assert_raises_rpc_error(-5, "Bech32m addresses cannot be imported into legacy wallets", self.nodes[0].importaddress, "bcrt1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqc8gma6")
425+
423426
# Import address and private key to check correct behavior of spendable unspents
424427
# 1. Send some coins to generate new UTXO
425428
address_to_import = self.nodes[2].getnewaddress()

0 commit comments

Comments
 (0)