mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-05-05 03:18:50 +02:00
Merge bitcoin/bitcoin#22154: Add OutputType::BECH32M and related wallet support for fetching bech32m addresses
754f134a50wallet: Add error message to GetReservedDestination (Andrew Chow)87a0e7a3b7Disallow bech32m addresses for legacy wallet things (Andrew Chow)6dbe4d1072Use BECH32M for tr() desc, WitV1Taproot, and WitUnknown CTxDests (Andrew Chow)699dfcd8adOpportunistically use bech32m change addresses if available (Andrew Chow)0262536c34Add OutputType::BECH32M (Andrew Chow)177c15d2f7Limit 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 ACK754f134a50Sjors: re-utACK754f134: only change is switching to `bech32m` in two `wallet_taproot.py` test cases. fjahr: re-ACK754f134a50jonatack: ACK754f134a50Tree-SHA512: 6ea90867d3631d0d438e2b08ce6ed930f37d01323224661e8e38f183ea5ee2ab65b5891394a3612c7382a1aff907b457616c6725665a10c320174017b998ca9f
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user