diff options
author | fanquake <fanquake@gmail.com> | 2023-10-11 12:37:16 +0200 |
---|---|---|
committer | fanquake <fanquake@gmail.com> | 2023-10-11 12:50:43 +0200 |
commit | 744157ef1a0b61ceb714cc27c9ae158907aecdc9 (patch) | |
tree | c20108a70ae9e19bd89d9a8be3329adf79fab08f /test/functional | |
parent | 154404e33fe53e06b13057df314ab1eb2d5ea0f5 (diff) | |
parent | 74c77825e5ab68bfa575dad86444506c43ef6c06 (diff) |
Merge bitcoin/bitcoin#28602: descriptors: Disallow hybrid and uncompressed keys when inferring
74c77825e5ab68bfa575dad86444506c43ef6c06 test: Unit test for inferring scripts with hybrid and uncompressed keys (Andrew Chow)
f895f97014ac5fac46d27725c1ea7decf7ff76d4 test: Scripts with hybrid pubkeys are migrated to watchonly (Andrew Chow)
37b9b734770e855b9beff3b5085125f1420dd072 descriptors: Move InferScript's pubkey validity checks to InferPubkey (Andrew Chow)
b7485f11ab3a0f1860b261f222362af3301e0781 descriptors: Check result of InferPubkey (Andrew Chow)
Pull request description:
`InferDescriptor` was not always checking that the pubkey it was placing into the descriptor was an allowed pubkey. For example, given a P2WPKH script that uses an uncompressed pubkey, it would produce a `wpkh()` with the uncompressed key. Additionally, the hybrid key check was only being done for `pk()` scripts, where it should've been done for all scripts.
This PR moves the key checking into `InferPubkey`. If the key is not valid for the context, then `nullptr` is returned and the inferring will fall through to the defaults of either `raw()` or `addr()`.
This also resolves an issue with migrating legacy wallets that contain hybrid pubkeys as such watchonly scripts will become `raw()` or `addr()` and go to the watchonly wallet. Note that a legacy wallet cannot sign for hybrid pubkeys. A test has been added for the migration case.
Also added unit tests for `InferDescriptor` itself as the edge cases with that function are not covered by the descriptor roundtrip test.
ACKs for top commit:
furszy:
ACK 74c77825
Sjors:
utACK 74c77825e5ab68bfa575dad86444506c43ef6c06
Tree-SHA512: ed5f63e42a2e46120245a6b0288b90d2a6912860814c6c08fe393332add1cb364dc5eca72f16980352143570aef0c07bf1a91acd294099463bd028b6ce2fe40c
Diffstat (limited to 'test/functional')
-rwxr-xr-x | test/functional/wallet_migration.py | 60 |
1 files changed, 59 insertions, 1 deletions
diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index bcd71197bf..286dcb5fda 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -6,8 +6,13 @@ import random import shutil -from test_framework.address import script_to_p2sh +from test_framework.address import ( + script_to_p2sh, + key_to_p2pkh, + key_to_p2wpkh, +) from test_framework.descriptors import descsum_create +from test_framework.key import ECPubKey from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import COIN, CTransaction, CTxOut from test_framework.script_util import key_to_p2pkh_script, script_to_p2sh_script, script_to_p2wsh_script @@ -770,6 +775,58 @@ class WalletMigrationTest(BitcoinTestFramework): wallet.unloadwallet() + def test_hybrid_pubkey(self): + self.log.info("Test migration when wallet contains a hybrid pubkey") + + wallet = self.create_legacy_wallet("hybrid_keys") + + # Get the hybrid pubkey for one of the keys in the wallet + normal_pubkey = wallet.getaddressinfo(wallet.getnewaddress())["pubkey"] + first_byte = bytes.fromhex(normal_pubkey)[0] + 4 # Get the hybrid pubkey first byte + parsed_pubkey = ECPubKey() + parsed_pubkey.set(bytes.fromhex(normal_pubkey)) + parsed_pubkey.compressed = False + hybrid_pubkey_bytes = bytearray(parsed_pubkey.get_bytes()) + hybrid_pubkey_bytes[0] = first_byte # Make it hybrid + hybrid_pubkey = hybrid_pubkey_bytes.hex() + + # Import the hybrid pubkey + wallet.importpubkey(hybrid_pubkey) + p2pkh_addr = key_to_p2pkh(hybrid_pubkey) + p2pkh_addr_info = wallet.getaddressinfo(p2pkh_addr) + assert_equal(p2pkh_addr_info["iswatchonly"], True) + assert_equal(p2pkh_addr_info["ismine"], False) # Things involving hybrid pubkeys are not spendable + + # Also import the p2wpkh for the pubkey to make sure we don't migrate it + p2wpkh_addr = key_to_p2wpkh(hybrid_pubkey) + wallet.importaddress(p2wpkh_addr) + + migrate_info = wallet.migratewallet() + + # Both addresses should only appear in the watchonly wallet + p2pkh_addr_info = wallet.getaddressinfo(p2pkh_addr) + assert_equal(p2pkh_addr_info["iswatchonly"], False) + assert_equal(p2pkh_addr_info["ismine"], False) + p2wpkh_addr_info = wallet.getaddressinfo(p2wpkh_addr) + assert_equal(p2wpkh_addr_info["iswatchonly"], False) + assert_equal(p2wpkh_addr_info["ismine"], False) + + watchonly_wallet = self.nodes[0].get_wallet_rpc(migrate_info["watchonly_name"]) + watchonly_p2pkh_addr_info = watchonly_wallet.getaddressinfo(p2pkh_addr) + assert_equal(watchonly_p2pkh_addr_info["iswatchonly"], False) + assert_equal(watchonly_p2pkh_addr_info["ismine"], True) + watchonly_p2wpkh_addr_info = watchonly_wallet.getaddressinfo(p2wpkh_addr) + assert_equal(watchonly_p2wpkh_addr_info["iswatchonly"], False) + assert_equal(watchonly_p2wpkh_addr_info["ismine"], True) + + # There should only be raw or addr descriptors + for desc in watchonly_wallet.listdescriptors()["descriptors"]: + if desc["desc"].startswith("raw(") or desc["desc"].startswith("addr("): + continue + assert False, "Hybrid pubkey watchonly wallet has more than just raw() and addr()" + + wallet.unloadwallet() + def run_test(self): self.generate(self.nodes[0], 101) @@ -787,6 +844,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_addressbook() self.test_migrate_raw_p2sh() self.test_conflict_txs() + self.test_hybrid_pubkey() if __name__ == '__main__': WalletMigrationTest().main() |