diff options
Diffstat (limited to 'test/functional')
61 files changed, 1045 insertions, 508 deletions
diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py index 586722aa65..76b50a5bfc 100755 --- a/test/functional/feature_abortnode.py +++ b/test/functional/feature_abortnode.py @@ -9,10 +9,7 @@ - Mine a fork that requires disconnecting the tip. - Verify that bitcoind AbortNode's. """ - from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import get_datadir_path -import os class AbortNodeTest(BitcoinTestFramework): @@ -26,10 +23,9 @@ class AbortNodeTest(BitcoinTestFramework): def run_test(self): self.generate(self.nodes[0], 3, sync_fun=self.no_op) - datadir = get_datadir_path(self.options.tmpdir, 0) # Deleting the undo file will result in reorg failure - os.unlink(os.path.join(datadir, self.chain, 'blocks', 'rev00000.dat')) + (self.nodes[0].chain_path / "blocks" / "rev00000.dat").unlink() # Connecting to a node with a more work chain will trigger a reorg # attempt. @@ -40,7 +36,7 @@ class AbortNodeTest(BitcoinTestFramework): # Check that node0 aborted self.log.info("Waiting for crash") - self.nodes[0].wait_until_stopped(timeout=5, expect_error=True) + self.nodes[0].wait_until_stopped(timeout=5, expect_error=True, expected_stderr="Error: A fatal internal error occurred, see debug.log for details") self.log.info("Node crashed - now verifying restart fails") self.nodes[0].assert_start_raises_init_error() diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py index 28c3880513..7877f9d302 100755 --- a/test/functional/feature_addrman.py +++ b/test/functional/feature_addrman.py @@ -53,7 +53,7 @@ class AddrmanTest(BitcoinTestFramework): self.num_nodes = 1 def run_test(self): - peers_dat = os.path.join(self.nodes[0].datadir, self.chain, "peers.dat") + peers_dat = os.path.join(self.nodes[0].chain_path, "peers.dat") init_error = lambda reason: ( f"Error: Invalid or corrupt peers.dat \\({reason}\\). If you believe this " f"is a bug, please report it to {self.config['environment']['PACKAGE_BUGREPORT']}. " diff --git a/test/functional/feature_anchors.py b/test/functional/feature_anchors.py index 468ad1eafa..0961f21a40 100755 --- a/test/functional/feature_anchors.py +++ b/test/functional/feature_anchors.py @@ -20,9 +20,7 @@ class AnchorsTest(BitcoinTestFramework): self.disable_autoconnect = False def run_test(self): - node_anchors_path = os.path.join( - self.nodes[0].datadir, "regtest", "anchors.dat" - ) + node_anchors_path = self.nodes[0].chain_path / "anchors.dat" self.log.info("When node starts, check if anchors.dat doesn't exist") assert not os.path.exists(node_anchors_path) diff --git a/test/functional/feature_asmap.py b/test/functional/feature_asmap.py index 9440ba11f5..9cff8042a8 100755 --- a/test/functional/feature_asmap.py +++ b/test/functional/feature_asmap.py @@ -113,7 +113,7 @@ class AsmapTest(BitcoinTestFramework): def run_test(self): self.node = self.nodes[0] - self.datadir = os.path.join(self.node.datadir, self.chain) + self.datadir = self.node.chain_path self.default_asmap = os.path.join(self.datadir, DEFAULT_ASMAP_FILENAME) self.asmap_raw = os.path.join(os.path.dirname(os.path.realpath(__file__)), ASMAP) diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 765db97445..58ef1e761d 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -43,8 +43,7 @@ from test_framework.script import ( OP_INVALIDOPCODE, OP_RETURN, OP_TRUE, - SIGHASH_ALL, - LegacySignatureHash, + sign_input_legacy, ) from test_framework.script_util import ( script_to_p2sh_script, @@ -539,12 +538,8 @@ class FullBlockTest(BitcoinTestFramework): # second input is corresponding P2SH output from b39 tx.vin.append(CTxIn(COutPoint(b39.vtx[i].sha256, 0), b'')) # Note: must pass the redeem_script (not p2sh_script) to the signature hash function - (sighash, err) = LegacySignatureHash(redeem_script, tx, 1, SIGHASH_ALL) - sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) - scriptSig = CScript([sig, redeem_script]) - - tx.vin[1].scriptSig = scriptSig - tx.rehash() + tx.vin[1].scriptSig = CScript([redeem_script]) + sign_input_legacy(tx, 1, redeem_script, self.coinbase_key) new_txs.append(tx) lastOutpoint = COutPoint(tx.sha256, 0) @@ -1338,8 +1333,7 @@ class FullBlockTest(BitcoinTestFramework): if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend tx.vin[0].scriptSig = CScript() return - (sighash, err) = LegacySignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + sign_input_legacy(tx, 0, spend_tx.vout[0].scriptPubKey, self.coinbase_key) def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): tx = self.create_tx(spend_tx, 0, value, script) diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py index e8d2ec3676..99763ab97f 100755 --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -18,7 +18,7 @@ class BlocksdirTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks")) + assert os.path.isdir(os.path.join(self.nodes[0].chain_path, "blocks")) assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks")) shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0, self.chain) @@ -31,7 +31,7 @@ class BlocksdirTest(BitcoinTestFramework): self.log.info("mining blocks..") self.generatetoaddress(self.nodes[0], 10, self.nodes[0].get_deterministic_priv_key().address) assert os.path.isfile(os.path.join(blocksdir_path, self.chain, "blocks", "blk00000.dat")) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks", "index")) + assert os.path.isdir(os.path.join(self.nodes[0].chain_path, "blocks", "index")) if __name__ == '__main__': diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py index b898488924..2927355bda 100755 --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -32,7 +32,7 @@ class ConfArgsTest(BitcoinTestFramework): self.stop_node(0) # Check that startup fails if conf= is set in bitcoin.conf or in an included conf file - bad_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'bitcoin_bad.conf') + bad_conf_file_path = self.nodes[0].datadir_path / "bitcoin_bad.conf" util.write_config(bad_conf_file_path, n=0, chain='', extra_config=f'conf=some.conf\n') conf_in_config_file_err = 'Error: Error reading configuration file: conf cannot be set in the configuration file; use includeconf= if you want to include additional config files' self.nodes[0].assert_start_raises_init_error( @@ -75,7 +75,7 @@ class ConfArgsTest(BitcoinTestFramework): conf.write("wallet=foo\n") self.nodes[0].assert_start_raises_init_error(expected_msg=f'Error: Config setting for -wallet only applied on {self.chain} network when in [{self.chain}] section.') - main_conf_file_path = os.path.join(self.options.tmpdir, 'node0', 'bitcoin_main.conf') + main_conf_file_path = self.nodes[0].datadir_path / "bitcoin_main.conf" util.write_config(main_conf_file_path, n=0, chain='', extra_config=f'includeconf={inc_conf_file_path}\n') with open(inc_conf_file_path, 'w', encoding='utf-8') as conf: conf.write('acceptnonstdtxn=1\n') diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 03970415ac..4f56d585d3 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -421,7 +421,7 @@ class EstimateFeeTest(BitcoinTestFramework): self.log.info("Restarting node with fresh estimation") self.stop_node(0) - fee_dat = os.path.join(self.nodes[0].datadir, self.chain, "fee_estimates.dat") + fee_dat = os.path.join(self.nodes[0].chain_path, "fee_estimates.dat") os.remove(fee_dat) self.start_node(0) self.connect_nodes(0, 1) diff --git a/test/functional/feature_filelock.py b/test/functional/feature_filelock.py index bb4104bf8e..cf2f21d553 100755 --- a/test/functional/feature_filelock.py +++ b/test/functional/feature_filelock.py @@ -3,7 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Check that it's not possible to start a second bitcoind instance using the same datadir or wallet.""" -import os import random import string @@ -24,7 +23,7 @@ class FilelockTest(BitcoinTestFramework): self.nodes[0].wait_for_rpc_connection() def run_test(self): - datadir = os.path.join(self.nodes[0].datadir, self.chain) + datadir = self.nodes[0].chain_path self.log.info(f"Using datadir {datadir}") self.log.info("Check that we can't start a second bitcoind instance using the same datadir") @@ -35,7 +34,7 @@ class FilelockTest(BitcoinTestFramework): def check_wallet_filelock(descriptors): wallet_name = ''.join([random.choice(string.ascii_lowercase) for _ in range(6)]) self.nodes[0].createwallet(wallet_name=wallet_name, descriptors=descriptors) - wallet_dir = os.path.join(datadir, 'wallets') + wallet_dir = self.nodes[0].wallets_path self.log.info("Check that we can't start a second bitcoind instance using the same wallet") if descriptors: expected_msg = f"Error: SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of {self.config['environment']['PACKAGE_NAME']}?" diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index 818e4c923b..58ab063e71 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -14,27 +14,25 @@ Verify that: 4. multiple includeconf arguments can be specified in the main config file. """ -import os - from test_framework.test_framework import BitcoinTestFramework + class IncludeConfTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - def setup_chain(self): - super().setup_chain() + def run_test(self): # Create additional config files # - tmpdir/node0/relative.conf - with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + with open(self.nodes[0].datadir_path / "relative.conf", "w", encoding="utf8") as f: f.write("uacomment=relative\n") # - tmpdir/node0/relative2.conf - with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f: + with open(self.nodes[0].datadir_path / "relative2.conf", "w", encoding="utf8") as f: f.write("uacomment=relative2\n") - with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: + with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f: f.write("uacomment=main\nincludeconf=relative.conf\n") + self.restart_node(0) - def run_test(self): self.log.info("-includeconf works from config file. subversion should end with 'main; relative)/'") subversion = self.nodes[0].getnetworkinfo()["subversion"] @@ -52,7 +50,7 @@ class IncludeConfTest(BitcoinTestFramework): ) self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") - with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: + with open(self.nodes[0].datadir_path / "relative.conf", "a", encoding="utf8") as f: f.write("includeconf=relative2.conf\n") self.start_node(0) @@ -63,20 +61,20 @@ class IncludeConfTest(BitcoinTestFramework): self.log.info("-includeconf cannot contain invalid arg") # Commented out as long as we ignore invalid arguments in configuration files - #with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + #with open(self.nodes[0].datadir_path / "relative.conf", "w", encoding="utf8") as f: # f.write("foo=bar\n") #self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Invalid configuration value foo") self.log.info("-includeconf cannot be invalid path") - os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) + (self.nodes[0].datadir_path / "relative.conf").unlink() self.nodes[0].assert_start_raises_init_error(expected_msg="Error: Error reading configuration file: Failed to include configuration file relative.conf") self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") - with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + with open(self.nodes[0].datadir_path / "relative.conf", "w", encoding="utf8") as f: # Restore initial file contents f.write("uacomment=relative\n") - with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: + with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f: f.write("includeconf=relative2.conf\n") self.start_node(0) diff --git a/test/functional/feature_index_prune.py b/test/functional/feature_index_prune.py index 77a056346a..d6e802b399 100755 --- a/test/functional/feature_index_prune.py +++ b/test/functional/feature_index_prune.py @@ -3,6 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test indices in conjunction with prune.""" +import os from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -127,8 +128,9 @@ class FeatureIndexPruneTest(BitcoinTestFramework): self.log.info("make sure we get an init error when starting the nodes again with the indices") filter_msg = "Error: basic block filter index best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" stats_msg = "Error: coinstatsindex best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again)" + end_msg = f"{os.linesep}Error: Failed to start indexes, shutting down.." for i, msg in enumerate([filter_msg, stats_msg, filter_msg]): - self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg) + self.nodes[i].assert_start_raises_init_error(extra_args=self.extra_args[i], expected_msg=msg+end_msg) self.log.info("make sure the nodes start again with the indices and an additional -reindex arg") for i in range(3): diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index 7af67730bd..64ca312b84 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -71,7 +71,7 @@ class InitStressTest(BitcoinTestFramework): b'init message: Starting network threads', b'net thread start', b'addcon thread start', - b'loadblk thread start', + b'initload thread start', b'txindex thread start', b'block filter index thread start', b'coinstatsindex thread start', diff --git a/test/functional/feature_loadblock.py b/test/functional/feature_loadblock.py index 7f030c6773..c90ccc4936 100755 --- a/test/functional/feature_loadblock.py +++ b/test/functional/feature_loadblock.py @@ -37,7 +37,7 @@ class LoadblockTest(BitcoinTestFramework): cfg_file = os.path.join(data_dir, "linearize.cfg") bootstrap_file = os.path.join(self.options.tmpdir, "bootstrap.dat") genesis_block = self.nodes[0].getblockhash(0) - blocks_dir = os.path.join(data_dir, self.chain, "blocks") + blocks_dir = self.nodes[0].chain_path / "blocks" hash_list = tempfile.NamedTemporaryFile(dir=data_dir, mode='w', delete=False, diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py index b0788e2a2d..0e9aca358d 100755 --- a/test/functional/feature_logging.py +++ b/test/functional/feature_logging.py @@ -16,7 +16,7 @@ class LoggingTest(BitcoinTestFramework): self.setup_clean_chain = True def relative_log_path(self, name): - return os.path.join(self.nodes[0].datadir, self.chain, name) + return os.path.join(self.nodes[0].chain_path, name) def run_test(self): # test default log file name diff --git a/test/functional/feature_posix_fs_permissions.py b/test/functional/feature_posix_fs_permissions.py index c5a543e97a..40528779e6 100755 --- a/test/functional/feature_posix_fs_permissions.py +++ b/test/functional/feature_posix_fs_permissions.py @@ -31,11 +31,11 @@ class PosixFsPermissionsTest(BitcoinTestFramework): def run_test(self): self.stop_node(0) - datadir = os.path.join(self.nodes[0].datadir, self.chain) + datadir = self.nodes[0].chain_path self.check_directory_permissions(datadir) - walletsdir = os.path.join(datadir, "wallets") + walletsdir = self.nodes[0].wallets_path self.check_directory_permissions(walletsdir) - debuglog = os.path.join(datadir, "debug.log") + debuglog = self.nodes[0].debug_log_path self.check_file_permissions(debuglog) diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index b0c6138bcf..15dd4827ae 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -91,7 +91,7 @@ class PruneTest(BitcoinTestFramework): def setup_network(self): self.setup_nodes() - self.prunedir = os.path.join(self.nodes[2].datadir, self.chain, 'blocks', '') + self.prunedir = os.path.join(self.nodes[2].chain_path, 'blocks', '') self.connect_nodes(0, 1) self.connect_nodes(1, 2) @@ -290,7 +290,7 @@ class PruneTest(BitcoinTestFramework): assert_equal(ret + 1, node.getblockchaininfo()['pruneheight']) def has_block(index): - return os.path.isfile(os.path.join(self.nodes[node_number].datadir, self.chain, "blocks", f"blk{index:05}.dat")) + return os.path.isfile(os.path.join(self.nodes[node_number].chain_path, "blocks", f"blk{index:05}.dat")) # should not prune because chain tip of node 3 (995) < PruneAfterHeight (1000) assert_raises_rpc_error(-1, "Blockchain is too short for pruning", node.pruneblockchain, height(500)) diff --git a/test/functional/feature_reindex.py b/test/functional/feature_reindex.py index 0f6a8fd0d2..fcbb49d420 100755 --- a/test/functional/feature_reindex.py +++ b/test/functional/feature_reindex.py @@ -10,7 +10,6 @@ - Verify that out-of-order blocks are correctly processed, see LoadExternalBlockFile() """ -import os from test_framework.test_framework import BitcoinTestFramework from test_framework.p2p import MAGIC_BYTES from test_framework.util import assert_equal @@ -39,7 +38,7 @@ class ReindexTest(BitcoinTestFramework): # In this test environment, blocks will always be in order (since # we're generating them rather than getting them from peers), so to # test out-of-order handling, swap blocks 1 and 2 on disk. - blk0 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'blk00000.dat') + blk0 = self.nodes[0].chain_path / "blocks" / "blk00000.dat" with open(blk0, 'r+b') as bf: # Read at least the first few blocks (including genesis) b = bf.read(2000) diff --git a/test/functional/feature_remove_pruned_files_on_startup.py b/test/functional/feature_remove_pruned_files_on_startup.py index ca0e5ace9f..a55e08ef1a 100755 --- a/test/functional/feature_remove_pruned_files_on_startup.py +++ b/test/functional/feature_remove_pruned_files_on_startup.py @@ -20,10 +20,10 @@ class FeatureRemovePrunedFilesOnStartupTest(BitcoinTestFramework): self.sync_blocks() def run_test(self): - blk0 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'blk00000.dat') - rev0 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'rev00000.dat') - blk1 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'blk00001.dat') - rev1 = os.path.join(self.nodes[0].datadir, self.nodes[0].chain, 'blocks', 'rev00001.dat') + blk0 = self.nodes[0].chain_path / "blocks" / "blk00000.dat" + rev0 = self.nodes[0].chain_path / "blocks" / "rev00000.dat" + blk1 = self.nodes[0].chain_path / "blocks" / "blk00001.dat" + rev1 = self.nodes[0].chain_path / "blocks" / "rev00001.dat" self.mine_batches(800) fo1 = os.open(blk0, os.O_RDONLY) fo2 = os.open(rev1, os.O_RDONLY) diff --git a/test/functional/feature_settings.py b/test/functional/feature_settings.py index 20018f010f..bcae963428 100755 --- a/test/functional/feature_settings.py +++ b/test/functional/feature_settings.py @@ -21,7 +21,7 @@ class SettingsTest(BitcoinTestFramework): def run_test(self): node, = self.nodes - settings = Path(node.datadir, self.chain, "settings.json") + settings = Path(node.chain_path, "settings.json") conf = Path(node.datadir, "bitcoin.conf") # Assert empty settings file was created diff --git a/test/functional/feature_startupnotify.py b/test/functional/feature_startupnotify.py index 5c00984404..a8e62c6244 100755 --- a/test/functional/feature_startupnotify.py +++ b/test/functional/feature_startupnotify.py @@ -3,9 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test -startupnotify.""" - -import os - from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, @@ -20,12 +17,12 @@ class StartupNotifyTest(BitcoinTestFramework): self.num_nodes = 1 def run_test(self): - tmpdir_file = os.path.join(self.options.tmpdir, NODE_DIR, FILE_NAME) - assert not os.path.exists(tmpdir_file) + tmpdir_file = self.nodes[0].datadir_path / FILE_NAME + assert not tmpdir_file.exists() self.log.info("Test -startupnotify command is run when node starts") self.restart_node(0, extra_args=[f"-startupnotify=echo '{FILE_NAME}' >> {NODE_DIR}/{FILE_NAME}"]) - self.wait_until(lambda: os.path.exists(tmpdir_file)) + self.wait_until(lambda: tmpdir_file.exists()) self.log.info("Test -startupnotify is executed once") diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index ec1e580369..e32319961e 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -104,8 +104,8 @@ from test_framework.key import ( sign_schnorr, tweak_add_privkey, ECKey, - SECP256K1 ) +from test_framework import secp256k1 from test_framework.address import ( hash160, program_to_witness, @@ -695,7 +695,7 @@ def spenders_taproot_active(): # Generate an invalid public key while True: invalid_pub = random_bytes(32) - if not SECP256K1.is_x_coord(int.from_bytes(invalid_pub, 'big')): + if not secp256k1.GE.is_valid_x(int.from_bytes(invalid_pub, 'big')): break # Implement a test case that detects validation logic which maps invalid public keys to the diff --git a/test/functional/feature_txindex_compatibility.py b/test/functional/feature_txindex_compatibility.py index 48fefaa0ba..a5b25cbd71 100755 --- a/test/functional/feature_txindex_compatibility.py +++ b/test/functional/feature_txindex_compatibility.py @@ -50,10 +50,10 @@ class TxindexCompatibilityTest(BitcoinTestFramework): self.nodes[0].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex self.stop_nodes() - legacy_chain_dir = os.path.join(self.nodes[0].datadir, self.chain) + legacy_chain_dir = self.nodes[0].chain_path self.log.info("Migrate legacy txindex") - migrate_chain_dir = os.path.join(self.nodes[2].datadir, self.chain) + migrate_chain_dir = self.nodes[2].chain_path shutil.rmtree(migrate_chain_dir) shutil.copytree(legacy_chain_dir, migrate_chain_dir) with self.nodes[2].assert_debug_log([ @@ -64,7 +64,7 @@ class TxindexCompatibilityTest(BitcoinTestFramework): self.nodes[2].getrawtransaction(txid=spend_utxo["txid"]) # Requires -txindex self.log.info("Drop legacy txindex") - drop_index_chain_dir = os.path.join(self.nodes[1].datadir, self.chain) + drop_index_chain_dir = self.nodes[1].chain_path shutil.rmtree(drop_index_chain_dir) shutil.copytree(legacy_chain_dir, drop_index_chain_dir) self.nodes[1].assert_start_raises_init_error( diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 1ba8f60d99..c0679c5ba9 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -26,6 +26,7 @@ from test_framework.wallet import ( MiniWallet, getnewdestination, ) +from typing import Optional INVALID_PARAM = "abc" @@ -64,7 +65,7 @@ class RESTTest (BitcoinTestFramework): body: str = '', status: int = 200, ret_type: RetType = RetType.JSON, - query_params: typing.Dict[str, typing.Any] = None, + query_params: Optional[typing.Dict[str, typing.Any]] = None, ) -> typing.Union[http.client.HTTPResponse, bytes, str, None]: rest_uri = '/rest' + uri if req_type in ReqType: diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py index 3725c89719..e873e2da0b 100755 --- a/test/functional/interface_rpc.py +++ b/test/functional/interface_rpc.py @@ -46,7 +46,7 @@ class RPCInterfaceTest(BitcoinTestFramework): command = info['active_commands'][0] assert_equal(command['method'], 'getrpcinfo') assert_greater_than_or_equal(command['duration'], 0) - assert_equal(info['logpath'], os.path.join(self.nodes[0].datadir, self.chain, 'debug.log')) + assert_equal(info['logpath'], os.path.join(self.nodes[0].chain_path, 'debug.log')) def test_batch_request(self): self.log.info("Testing basic JSON-RPC batch request...") diff --git a/test/functional/interface_usdt_mempool.py b/test/functional/interface_usdt_mempool.py index f138fa44cc..67cf649830 100755 --- a/test/functional/interface_usdt_mempool.py +++ b/test/functional/interface_usdt_mempool.py @@ -307,7 +307,10 @@ class MempoolTracepointTest(BitcoinTestFramework): self.log.info("Ensuring mempool:rejected event was handled successfully...") assert_equal(EXPECTED_REJECTED_EVENTS, handled_rejected_events) assert_equal(bytes(event.hash)[::-1].hex(), tx["tx"].hash) - assert_equal(event.reason.decode("UTF-8"), "min relay fee not met") + # The next test is already known to fail, so disable it to avoid + # wasting CPU time and developer time. See + # https://github.com/bitcoin/bitcoin/issues/27380 + #assert_equal(event.reason.decode("UTF-8"), "min relay fee not met") bpf.cleanup() self.generate(self.wallet, 1) diff --git a/test/functional/mempool_compatibility.py b/test/functional/mempool_compatibility.py index 7337802aea..3f632d3d56 100755 --- a/test/functional/mempool_compatibility.py +++ b/test/functional/mempool_compatibility.py @@ -55,8 +55,8 @@ class MempoolCompatibilityTest(BitcoinTestFramework): self.stop_node(1) self.log.info("Move mempool.dat from old to new node") - old_node_mempool = os.path.join(old_node.datadir, self.chain, 'mempool.dat') - new_node_mempool = os.path.join(new_node.datadir, self.chain, 'mempool.dat') + old_node_mempool = os.path.join(old_node.chain_path, 'mempool.dat') + new_node_mempool = os.path.join(new_node.chain_path, 'mempool.dat') os.rename(old_node_mempool, new_node_mempool) self.log.info("Start new node and verify mempool contains the tx") diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index 8f74d9de20..a1335ff069 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -143,8 +143,8 @@ class MempoolPersistTest(BitcoinTestFramework): self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet assert_equal(node2_balance, wallet_watch.getbalance()) - mempooldat0 = os.path.join(self.nodes[0].datadir, self.chain, 'mempool.dat') - mempooldat1 = os.path.join(self.nodes[1].datadir, self.chain, 'mempool.dat') + mempooldat0 = os.path.join(self.nodes[0].chain_path, 'mempool.dat') + mempooldat1 = os.path.join(self.nodes[1].chain_path, 'mempool.dat') self.log.debug("Force -persistmempool=0 node1 to savemempool to disk via RPC") assert not os.path.exists(mempooldat1) diff --git a/test/functional/p2p_message_capture.py b/test/functional/p2p_message_capture.py index 3ab0b79ba2..691a0b6409 100755 --- a/test/functional/p2p_message_capture.py +++ b/test/functional/p2p_message_capture.py @@ -19,7 +19,7 @@ TIME_SIZE = 8 LENGTH_SIZE = 4 MSGTYPE_SIZE = 12 -def mini_parser(dat_file): +def mini_parser(dat_file: str) -> None: """Parse a data file created by CaptureMessageToFile. From the data file we'll only check the structure. @@ -58,7 +58,7 @@ class MessageCaptureTest(BitcoinTestFramework): self.setup_clean_chain = True def run_test(self): - capturedir = os.path.join(self.nodes[0].datadir, "regtest/message_capture") + capturedir = self.nodes[0].chain_path / "message_capture" # Connect a node so that the handshake occurs self.nodes[0].add_p2p_connection(P2PDataStore()) self.nodes[0].disconnect_p2ps() diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index bfae190c66..1e7bc95a63 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -71,8 +71,8 @@ from test_framework.script import ( SIGHASH_NONE, SIGHASH_SINGLE, SegwitV0SignatureHash, - LegacySignatureHash, hash160, + sign_input_legacy, ) from test_framework.script_util import ( key_to_p2pk_script, @@ -1529,10 +1529,8 @@ class SegWitTest(BitcoinTestFramework): tx5 = CTransaction() tx5.vin.append(CTxIn(COutPoint(tx4.sha256, 0), b"")) tx5.vout.append(CTxOut(tx4.vout[0].nValue - 1000, CScript([OP_TRUE]))) - (sig_hash, err) = LegacySignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) - signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL - tx5.vin[0].scriptSig = CScript([signature, pubkey]) - tx5.rehash() + tx5.vin[0].scriptSig = CScript([pubkey]) + sign_input_legacy(tx5, 0, script_pubkey, key) # Should pass policy and consensus. test_transaction_acceptance(self.nodes[0], self.test_node, tx5, True, True) block = self.build_next_block() diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 6022042c11..5f2bece733 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -49,7 +49,6 @@ from test_framework.util import ( assert_raises_rpc_error, assert_is_hex_string, assert_is_hash_string, - get_datadir_path, ) from test_framework.wallet import MiniWallet @@ -572,16 +571,15 @@ class BlockchainTest(BitcoinTestFramework): self.log.info("Test that getblock with verbosity 3 includes prevout") assert_vin_contains_prevout(3) - self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data") - datadir = get_datadir_path(self.options.tmpdir, 0) - self.log.info("Test getblock with invalid verbosity type returns proper error message") assert_raises_rpc_error(-3, "JSON value of type string is not of expected type number", node.getblock, blockhash, "2") + self.log.info("Test that getblock with verbosity 2 and 3 still works with pruned Undo data") + def move_block_file(old, new): - old_path = os.path.join(datadir, self.chain, 'blocks', old) - new_path = os.path.join(datadir, self.chain, 'blocks', new) - os.rename(old_path, new_path) + old_path = self.nodes[0].chain_path / "blocks" / old + new_path = self.nodes[0].chain_path / "blocks" / new + old_path.rename(new_path) # Move instead of deleting so we can restore chain state afterwards move_block_file('rev00000.dat', 'rev_wrong') diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index 34e60d70f0..65d7b4c422 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -157,7 +157,7 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): try: node1.loadwallet('wmulti') except JSONRPCException as e: - path = os.path.join(self.options.tmpdir, "node1", "regtest", "wallets", "wmulti") + path = self.nodes[1].wallets_path / "wmulti" if e.error['code'] == -18 and "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path) in e.error['message']: node1.createwallet(wallet_name='wmulti', disable_private_keys=True) else: diff --git a/test/functional/rpc_dumptxoutset.py b/test/functional/rpc_dumptxoutset.py index 39a931be03..4260e95629 100755 --- a/test/functional/rpc_dumptxoutset.py +++ b/test/functional/rpc_dumptxoutset.py @@ -52,7 +52,7 @@ class DumptxoutsetTest(BitcoinTestFramework): # Specifying a path to an existing or invalid file will fail. assert_raises_rpc_error( -8, '{} already exists'.format(FILENAME), node.dumptxoutset, FILENAME) - invalid_path = str(Path(node.datadir) / "invalid" / "path") + invalid_path = node.datadir_path / "invalid" / "path" assert_raises_rpc_error( -8, "Couldn't open file {}.incomplete for writing".format(invalid_path), node.dumptxoutset, invalid_path) diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py index 8cc3ec401e..66cdd7cf9a 100755 --- a/test/functional/rpc_users.py +++ b/test/functional/rpc_users.py @@ -7,11 +7,9 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, - get_datadir_path, str_to_b64str, ) -import os import http.client import urllib.parse import subprocess @@ -38,8 +36,7 @@ class HTTPBasicsTest(BitcoinTestFramework): self.num_nodes = 2 self.supports_cli = False - def setup_chain(self): - super().setup_chain() + def conf_setup(self): #Append rpcauth to bitcoin.conf before initialization self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM=" rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144" @@ -64,13 +61,15 @@ class HTTPBasicsTest(BitcoinTestFramework): rpcauth3 = lines[1] self.password = lines[3] - with open(os.path.join(get_datadir_path(self.options.tmpdir, 0), "bitcoin.conf"), 'a', encoding='utf8') as f: + with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f: f.write(rpcauth + "\n") f.write(rpcauth2 + "\n") f.write(rpcauth3 + "\n") - with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f: + with open(self.nodes[1].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f: f.write("rpcuser={}\n".format(self.rpcuser)) f.write("rpcpassword={}\n".format(self.rpcpassword)) + self.restart_node(0) + self.restart_node(1) def test_auth(self, node, user, password): self.log.info('Correct...') @@ -86,6 +85,7 @@ class HTTPBasicsTest(BitcoinTestFramework): assert_equal(401, call_with_auth(node, user + 'wrong', password + 'wrong').status) def run_test(self): + self.conf_setup() self.log.info('Check correctness of the rpcauth config option') url = urllib.parse.urlparse(self.nodes[0].url) @@ -112,8 +112,7 @@ class HTTPBasicsTest(BitcoinTestFramework): self.nodes[0].assert_start_raises_init_error(expected_msg=init_error, extra_args=['-rpcauth=foo$bar$baz']) self.log.info('Check that failure to write cookie file will abort the node gracefully') - cookie_file = os.path.join(get_datadir_path(self.options.tmpdir, 0), self.chain, '.cookie.tmp') - os.mkdir(cookie_file) + (self.nodes[0].chain_path / ".cookie.tmp").mkdir() self.nodes[0].assert_start_raises_init_error(expected_msg=init_error) diff --git a/test/functional/rpc_whitelist.py b/test/functional/rpc_whitelist.py index 219132410b..fb404fb479 100755 --- a/test/functional/rpc_whitelist.py +++ b/test/functional/rpc_whitelist.py @@ -6,11 +6,9 @@ A test for RPC users with restricted permissions """ from test_framework.test_framework import BitcoinTestFramework -import os from test_framework.util import ( - get_datadir_path, assert_equal, - str_to_b64str + str_to_b64str, ) import http.client import urllib.parse @@ -30,8 +28,7 @@ class RPCWhitelistTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - def setup_chain(self): - super().setup_chain() + def run_test(self): # 0 => Username # 1 => Password (Hashed) # 2 => Permissions @@ -55,7 +52,7 @@ class RPCWhitelistTest(BitcoinTestFramework): ] # These commands shouldn't be allowed for any user to test failures self.never_allowed = ["getnetworkinfo"] - with open(os.path.join(get_datadir_path(self.options.tmpdir, 0), "bitcoin.conf"), 'a', encoding='utf8') as f: + with open(self.nodes[0].datadir_path / "bitcoin.conf", "a", encoding="utf8") as f: f.write("\nrpcwhitelistdefault=0\n") for user in self.users: f.write("rpcauth=" + user[0] + ":" + user[1] + "\n") @@ -64,9 +61,8 @@ class RPCWhitelistTest(BitcoinTestFramework): for strangedude in self.strange_users: f.write("rpcauth=" + strangedude[0] + ":" + strangedude[1] + "\n") f.write("rpcwhitelist=" + strangedude[0] + strangedude[2] + "\n") + self.restart_node(0) - - def run_test(self): for user in self.users: permissions = user[2].replace(" ", "").split(",") # Pop all empty items diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py index f7765a9dfa..03042877b2 100644 --- a/test/functional/test_framework/authproxy.py +++ b/test/functional/test_framework/authproxy.py @@ -39,6 +39,7 @@ from http import HTTPStatus import http.client import json import logging +import pathlib import socket import time import urllib.parse @@ -59,9 +60,11 @@ class JSONRPCException(Exception): self.http_status = http_status -def EncodeDecimal(o): +def serialization_fallback(o): if isinstance(o, decimal.Decimal): return str(o) + if isinstance(o, pathlib.Path): + return str(o) raise TypeError(repr(o) + " is not JSON serializable") class AuthServiceProxy(): @@ -108,7 +111,7 @@ class AuthServiceProxy(): log.debug("-{}-> {} {}".format( AuthServiceProxy.__id_count, self._service_name, - json.dumps(args or argsn, default=EncodeDecimal, ensure_ascii=self.ensure_ascii), + json.dumps(args or argsn, default=serialization_fallback, ensure_ascii=self.ensure_ascii), )) if args and argsn: params = dict(args=args, **argsn) @@ -120,7 +123,7 @@ class AuthServiceProxy(): 'id': AuthServiceProxy.__id_count} def __call__(self, *args, **argsn): - postdata = json.dumps(self.get_request(*args, **argsn), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) + postdata = json.dumps(self.get_request(*args, **argsn), default=serialization_fallback, ensure_ascii=self.ensure_ascii) response, status = self._request('POST', self.__url.path, postdata.encode('utf-8')) if response['error'] is not None: raise JSONRPCException(response['error'], status) @@ -134,7 +137,7 @@ class AuthServiceProxy(): return response['result'] def batch(self, rpc_call_list): - postdata = json.dumps(list(rpc_call_list), default=EncodeDecimal, ensure_ascii=self.ensure_ascii) + postdata = json.dumps(list(rpc_call_list), default=serialization_fallback, ensure_ascii=self.ensure_ascii) log.debug("--> " + postdata) response, status = self._request('POST', self.__url.path, postdata.encode('utf-8')) if status != HTTPStatus.OK: @@ -167,7 +170,7 @@ class AuthServiceProxy(): response = json.loads(responsedata, parse_float=decimal.Decimal) elapsed = time.time() - req_start_time if "error" in response and response["error"] is None: - log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=EncodeDecimal, ensure_ascii=self.ensure_ascii))) + log.debug("<-%s- [%.6f] %s" % (response["id"], elapsed, json.dumps(response["result"], default=serialization_fallback, ensure_ascii=self.ensure_ascii))) else: log.debug("<-- [%.6f] %s" % (elapsed, responsedata)) return response, http_response.status diff --git a/test/functional/test_framework/coverage.py b/test/functional/test_framework/coverage.py index 4fb4f8bb82..912a945d95 100644 --- a/test/functional/test_framework/coverage.py +++ b/test/functional/test_framework/coverage.py @@ -11,6 +11,7 @@ testing. import os from .authproxy import AuthServiceProxy +from typing import Optional REFERENCE_FILENAME = 'rpc_interface.txt' @@ -20,7 +21,7 @@ class AuthServiceProxyWrapper(): An object that wraps AuthServiceProxy to record specific RPC calls. """ - def __init__(self, auth_service_proxy_instance: AuthServiceProxy, rpc_url: str, coverage_logfile: str=None): + def __init__(self, auth_service_proxy_instance: AuthServiceProxy, rpc_url: str, coverage_logfile: Optional[str]=None): """ Kwargs: auth_service_proxy_instance: the instance being wrapped. diff --git a/test/functional/test_framework/ellswift.py b/test/functional/test_framework/ellswift.py new file mode 100644 index 0000000000..97b10118e6 --- /dev/null +++ b/test/functional/test_framework/ellswift.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only Elligator Swift implementation + +WARNING: This code is slow and uses bad randomness. +Do not use for anything but tests.""" + +import csv +import os +import random +import unittest + +from test_framework.secp256k1 import FE, G, GE + +# Precomputed constant square root of -3 (mod p). +MINUS_3_SQRT = FE(-3).sqrt() + +def xswiftec(u, t): + """Decode field elements (u, t) to an X coordinate on the curve.""" + if u == 0: + u = FE(1) + if t == 0: + t = FE(1) + if u**3 + t**2 + 7 == 0: + t = 2 * t + X = (u**3 + 7 - t**2) / (2 * t) + Y = (X + t) / (MINUS_3_SQRT * u) + for x in (u + 4 * Y**2, (-X / Y - u) / 2, (X / Y - u) / 2): + if GE.is_valid_x(x): + return x + assert False + +def xswiftec_inv(x, u, case): + """Given x and u, find t such that xswiftec(u, t) = x, or return None. + + Case selects which of the up to 8 results to return.""" + + if case & 2 == 0: + if GE.is_valid_x(-x - u): + return None + v = x + s = -(u**3 + 7) / (u**2 + u*v + v**2) + else: + s = x - u + if s == 0: + return None + r = (-s * (4 * (u**3 + 7) + 3 * s * u**2)).sqrt() + if r is None: + return None + if case & 1 and r == 0: + return None + v = (-u + r / s) / 2 + w = s.sqrt() + if w is None: + return None + if case & 5 == 0: + return -w * (u * (1 - MINUS_3_SQRT) / 2 + v) + if case & 5 == 1: + return w * (u * (1 + MINUS_3_SQRT) / 2 + v) + if case & 5 == 4: + return w * (u * (1 - MINUS_3_SQRT) / 2 + v) + if case & 5 == 5: + return -w * (u * (1 + MINUS_3_SQRT) / 2 + v) + +def xelligatorswift(x): + """Given a field element X on the curve, find (u, t) that encode them.""" + assert GE.is_valid_x(x) + while True: + u = FE(random.randrange(1, FE.SIZE)) + case = random.randrange(0, 8) + t = xswiftec_inv(x, u, case) + if t is not None: + return u, t + +def ellswift_create(): + """Generate a (privkey, ellswift_pubkey) pair.""" + priv = random.randrange(1, GE.ORDER) + u, t = xelligatorswift((priv * G).x) + return priv.to_bytes(32, 'big'), u.to_bytes() + t.to_bytes() + +def ellswift_ecdh_xonly(pubkey_theirs, privkey): + """Compute X coordinate of shared ECDH point between ellswift pubkey and privkey.""" + u = FE(int.from_bytes(pubkey_theirs[:32], 'big')) + t = FE(int.from_bytes(pubkey_theirs[32:], 'big')) + d = int.from_bytes(privkey, 'big') + return (d * GE.lift_x(xswiftec(u, t))).x.to_bytes() + + +class TestFrameworkEllSwift(unittest.TestCase): + def test_xswiftec(self): + """Verify that xswiftec maps all inputs to the curve.""" + for _ in range(32): + u = FE(random.randrange(0, FE.SIZE)) + t = FE(random.randrange(0, FE.SIZE)) + x = xswiftec(u, t) + self.assertTrue(GE.is_valid_x(x)) + + # Check that inputs which are considered undefined in the original + # SwiftEC paper can also be decoded successfully (by remapping) + undefined_inputs = [ + (FE(0), FE(23)), # u = 0 + (FE(42), FE(0)), # t = 0 + (FE(5), FE(-132).sqrt()), # u^3 + t^2 + 7 = 0 + ] + assert undefined_inputs[-1][0]**3 + undefined_inputs[-1][1]**2 + 7 == 0 + for u, t in undefined_inputs: + x = xswiftec(u, t) + self.assertTrue(GE.is_valid_x(x)) + + def test_elligator_roundtrip(self): + """Verify that encoding using xelligatorswift decodes back using xswiftec.""" + for _ in range(32): + while True: + # Loop until we find a valid X coordinate on the curve. + x = FE(random.randrange(1, FE.SIZE)) + if GE.is_valid_x(x): + break + # Encoding it to (u, t), decode it back, and compare. + u, t = xelligatorswift(x) + x2 = xswiftec(u, t) + self.assertEqual(x2, x) + + def test_ellswift_ecdh_xonly(self): + """Verify that shared secret computed by ellswift_ecdh_xonly match.""" + for _ in range(32): + privkey1, encoding1 = ellswift_create() + privkey2, encoding2 = ellswift_create() + shared_secret1 = ellswift_ecdh_xonly(encoding1, privkey2) + shared_secret2 = ellswift_ecdh_xonly(encoding2, privkey1) + self.assertEqual(shared_secret1, shared_secret2) + + def test_elligator_encode_testvectors(self): + """Implement the BIP324 test vectors for ellswift encoding (read from xswiftec_inv_test_vectors.csv).""" + vectors_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'xswiftec_inv_test_vectors.csv') + with open(vectors_file, newline='', encoding='utf8') as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + u = FE.from_bytes(bytes.fromhex(row['u'])) + x = FE.from_bytes(bytes.fromhex(row['x'])) + for case in range(8): + ret = xswiftec_inv(x, u, case) + if ret is None: + self.assertEqual(row[f"case{case}_t"], "") + else: + self.assertEqual(row[f"case{case}_t"], ret.to_bytes().hex()) + self.assertEqual(xswiftec(u, ret), x) + + def test_elligator_decode_testvectors(self): + """Implement the BIP324 test vectors for ellswift decoding (read from ellswift_decode_test_vectors.csv).""" + vectors_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ellswift_decode_test_vectors.csv') + with open(vectors_file, newline='', encoding='utf8') as csvfile: + reader = csv.DictReader(csvfile) + for row in reader: + encoding = bytes.fromhex(row['ellswift']) + assert len(encoding) == 64 + expected_x = FE(int(row['x'], 16)) + u = FE(int.from_bytes(encoding[:32], 'big')) + t = FE(int.from_bytes(encoding[32:], 'big')) + x = xswiftec(u, t) + self.assertEqual(x, expected_x) + self.assertTrue(GE.is_valid_x(x)) diff --git a/test/functional/test_framework/ellswift_decode_test_vectors.csv b/test/functional/test_framework/ellswift_decode_test_vectors.csv new file mode 100644 index 0000000000..1bab96b721 --- /dev/null +++ b/test/functional/test_framework/ellswift_decode_test_vectors.csv @@ -0,0 +1,77 @@ +ellswift,x,comment
+00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2)
+000000000000000000000000000000000000000000000000000000000000000001d3475bf7655b0fb2d852921035b2ef607f49069b97454e6795251062741771,b5da00b73cd6560520e7c364086e7cd23a34bf60d0e707be9fc34d4cd5fdfa2c,u%p=0;valid_x(x1)
+000000000000000000000000000000000000000000000000000000000000000082277c4a71f9d22e66ece523f8fa08741a7c0912c66a69ce68514bfd3515b49f,f482f2e241753ad0fb89150d8491dc1e34ff0b8acfbb442cfe999e2e5e6fd1d2,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+00000000000000000000000000000000000000000000000000000000000000008421cc930e77c9f514b6915c3dbe2a94c6d8f690b5b739864ba6789fb8a55dd0,9f59c40275f5085a006f05dae77eb98c6fd0db1ab4a72ac47eae90a4fc9e57e0,u%p=0;valid_x(x2)
+0000000000000000000000000000000000000000000000000000000000000000bde70df51939b94c9c24979fa7dd04ebd9b3572da7802290438af2a681895441,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd6b,u%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3)
+0000000000000000000000000000000000000000000000000000000000000000d19c182d2759cd99824228d94799f8c6557c38a1c0d6779b9d4b729c6f1ccc42,70720db7e238d04121f5b1afd8cc5ad9d18944c6bdc94881f502b7a3af3aecff,u%p=0;valid_x(x3)
+0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2);t>=p
+0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5,50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d,1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e,u%p=0;valid_x(x2);t>=p
+0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7,12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e,u%p=0;valid_x(x1);t>=p
+0000000000000000000000000000000000000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9,7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783,u%p=0;valid_x(x3);t>=p
+0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f8530000000000000000000000000000000000000000000000000000000000000000,532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688,t%p=0;(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+0a2d2ba93507f1df233770c2a797962cc61f6d15da14ecd47d8d27ae1cd5f853fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,532167c11200b08c0e84a354e74dcc40f8b25f4fe686e30869526366278a0688,t%p=0;(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646,74e880b3ffd18fe3cddf7902522551ddf97fa4a35a3cfda8197f947081a57b8f,valid_x(x3)
+0ffde9ca81d751e9cdaffc1a50779245320b28996dbaf32f822f20117c22fbd6ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896,377b643fce2271f64e5c8101566107c1be4980745091783804f654781ac9217c,valid_x(x2);t>=p
+123658444f32be8f02ea2034afa7ef4bbe8adc918ceb49b12773b625f490b368ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8dc5fe11,ed16d65cf3a9538fcb2c139f1ecbc143ee14827120cbc2659e667256800b8142,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+146f92464d15d36e35382bd3ca5b0f976c95cb08acdcf2d5b3570617990839d7ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3145e93b,0d5cd840427f941f65193079ab8e2e83024ef2ee7ca558d88879ffd879fb6657,(u'^3+t'^2+7)%p=0;valid_x(x3);t>=p
+15fdf5cf09c90759add2272d574d2bb5fe1429f9f3c14c65e3194bf61b82aa73ffffffffffffffffffffffffffffffffffffffffffffffffffffffff04cfd906,16d0e43946aec93f62d57eb8cde68951af136cf4b307938dd1447411e07bffe1,(u'^3+t'^2+7)%p=0;valid_x(x2);t>=p
+1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d50000000000000000000000000000000000000000000000000000000000000000,025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c,t%p=0;valid_x(x2)
+1f67edf779a8a649d6def60035f2fa22d022dd359079a1a144073d84f19b92d5fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,025661f9aba9d15c3118456bbe980e3e1b8ba2e047c737a4eb48a040bb566f6c,t%p=0;valid_x(x2);t>=p
+1fe1e5ef3fceb5c135ab7741333ce5a6e80d68167653f6b2b24bcbcfaaaff507fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,98bec3b2a351fa96cfd191c1778351931b9e9ba9ad1149f6d9eadca80981b801,t%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+4056a34a210eec7892e8820675c860099f857b26aad85470ee6d3cf1304a9dcf375e70374271f20b13c9986ed7d3c17799698cfc435dbed3a9f34b38c823c2b4,868aac2003b29dbcad1a3e803855e078a89d16543ac64392d122417298cec76e,(u'^3-t'^2+7)%p=0;valid_x(x3)
+4197ec3723c654cfdd32ab075506648b2ff5070362d01a4fff14b336b78f963fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb3ab1e95,ba5a6314502a8952b8f456e085928105f665377a8ce27726a5b0eb7ec1ac0286,(u'^3+t'^2+7)%p=0;valid_x(x1);t>=p
+47eb3e208fedcdf8234c9421e9cd9a7ae873bfbdbc393723d1ba1e1e6a8e6b24ffffffffffffffffffffffffffffffffffffffffffffffffffffffff7cd12cb1,d192d52007e541c9807006ed0468df77fd214af0a795fe119359666fdcf08f7c,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+5eb9696a2336fe2c3c666b02c755db4c0cfd62825c7b589a7b7bb442e141c1d693413f0052d49e64abec6d5831d66c43612830a17df1fe4383db896468100221,ef6e1da6d6c7627e80f7a7234cb08a022c1ee1cf29e4d0f9642ae924cef9eb38,(u'^3+t'^2+7)%p=0;valid_x(x1)
+7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0e0000000000000000000000000000000000000000000000000000000000000000,50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff,t%p=0;valid_x(x1)
+7bf96b7b6da15d3476a2b195934b690a3a3de3e8ab8474856863b0de3af90b0efffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,50851dfc9f418c314a437295b24feeea27af3d0cd2308348fda6e21c463e46ff,t%p=0;valid_x(x1);t>=p
+851b1ca94549371c4f1f7187321d39bf51c6b7fb61f7cbf027c9da62021b7a65fc54c96837fb22b362eda63ec52ec83d81bedd160c11b22d965d9f4a6d64d251,3e731051e12d33237eb324f2aa5b16bb868eb49a1aa1fadc19b6e8761b5a5f7b,(u'^3+t'^2+7)%p=0;valid_x(x2)
+943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f91250000000000000000000000000000000000000000000000000000000000000000,311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+943c2f775108b737fe65a9531e19f2fc2a197f5603e3a2881d1d83e4008f9125fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,311c61f0ab2f32b7b1f0223fa72f0a78752b8146e46107f8876dd9c4f92b2942,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+a0f18492183e61e8063e573606591421b06bc3513631578a73a39c1c3306239f2f32904f0d2a33ecca8a5451705bb537d3bf44e071226025cdbfd249fe0f7ad6,97a09cf1a2eae7c494df3c6f8a9445bfb8c09d60832f9b0b9d5eabe25fbd14b9,valid_x(x1)
+a1ed0a0bd79d8a23cfe4ec5fef5ba5cccfd844e4ff5cb4b0f2e71627341f1c5b17c499249e0ac08d5d11ea1c2c8ca7001616559a7994eadec9ca10fb4b8516dc,65a89640744192cdac64b2d21ddf989cdac7500725b645bef8e2200ae39691f2,valid_x(x2)
+ba94594a432721aa3580b84c161d0d134bc354b690404d7cd4ec57c16d3fbe98ffffffffffffffffffffffffffffffffffffffffffffffffffffffffea507dd7,5e0d76564aae92cb347e01a62afd389a9aa401c76c8dd227543dc9cd0efe685a,valid_x(x1);t>=p
+bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a,2d97f96cac882dfe73dc44db6ce0f1d31d6241358dd5d74eb3d3b50003d24c2b,valid_x(x3);valid_x(x2);valid_x(x1)
+bcaf7219f2f6fbf55fe5e062dce0e48c18f68103f10b8198e974c184750e1be3ffffffffffffffffffffffffffffffffffffffffffffffffffffffff6507d09a,e7008afe6e8cbd5055df120bd748757c686dadb41cce75e4addcc5e02ec02b44,valid_x(x3);valid_x(x2);valid_x(x1);t>=p
+c5981bae27fd84401c72a155e5707fbb811b2b620645d1028ea270cbe0ee225d4b62aa4dca6506c1acdbecc0552569b4b21436a5692e25d90d3bc2eb7ce24078,948b40e7181713bc018ec1702d3d054d15746c59a7020730dd13ecf985a010d7,(u'^3+t'^2+7)%p=0;valid_x(x3)
+c894ce48bfec433014b931a6ad4226d7dbd8eaa7b6e3faa8d0ef94052bcf8cff336eeb3919e2b4efb746c7f71bbca7e9383230fbbc48ffafe77e8bcc69542471,f1c91acdc2525330f9b53158434a4d43a1c547cff29f15506f5da4eb4fe8fa5a,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+cbb0deab125754f1fdb2038b0434ed9cb3fb53ab735391129994a535d925f6730000000000000000000000000000000000000000000000000000000000000000,872d81ed8831d9998b67cb7105243edbf86c10edfebb786c110b02d07b2e67cd,t%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+d917b786dac35670c330c9c5ae5971dfb495c8ae523ed97ee2420117b171f41effffffffffffffffffffffffffffffffffffffffffffffffffffffff2001f6f6,e45b71e110b831f2bdad8651994526e58393fde4328b1ec04d59897142584691,valid_x(x3);t>=p
+e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb4260000000000000000000000000000000000000000000000000000000000000000,66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5,t%p=0;valid_x(x3)
+e28bd8f5929b467eb70e04332374ffb7e7180218ad16eaa46b7161aa679eb426fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,66b8c980a75c72e598d383a35a62879f844242ad1e73ff12edaa59f4e58632b5,t%p=0;valid_x(x3);t>=p
+e7ee5814c1706bf8a89396a9b032bc014c2cac9c121127dbf6c99278f8bb53d1dfd04dbcda8e352466b6fcd5f2dea3e17d5e133115886eda20db8a12b54de71b,e842c6e3529b234270a5e97744edc34a04d7ba94e44b6d2523c9cf0195730a50,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1)
+f292e46825f9225ad23dc057c1d91c4f57fcb1386f29ef10481cb1d22518593fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7011c989,3cea2c53b8b0170166ac7da67194694adacc84d56389225e330134dab85a4d55,(u'^3-t'^2+7)%p=0;valid_x(x3);t>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f0000000000000000000000000000000000000000000000000000000000000000,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f01d3475bf7655b0fb2d852921035b2ef607f49069b97454e6795251062741771,b5da00b73cd6560520e7c364086e7cd23a34bf60d0e707be9fc34d4cd5fdfa2c,u%p=0;valid_x(x1);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f4218f20ae6c646b363db68605822fb14264ca8d2587fdd6fbc750d587e76a7ee,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa9fffffd6b,u%p=0;(u'^3-t'^2+7)%p=0;valid_x(x3);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f82277c4a71f9d22e66ece523f8fa08741a7c0912c66a69ce68514bfd3515b49f,f482f2e241753ad0fb89150d8491dc1e34ff0b8acfbb442cfe999e2e5e6fd1d2,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f8421cc930e77c9f514b6915c3dbe2a94c6d8f690b5b739864ba6789fb8a55dd0,9f59c40275f5085a006f05dae77eb98c6fd0db1ab4a72ac47eae90a4fc9e57e0,u%p=0;valid_x(x2);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fd19c182d2759cd99824228d94799f8c6557c38a1c0d6779b9d4b729c6f1ccc42,70720db7e238d04121f5b1afd8cc5ad9d18944c6bdc94881f502b7a3af3aecff,u%p=0;valid_x(x3);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,edd1fd3e327ce90cc7a3542614289aee9682003e9cf7dcc9cf2ca9743be5aa0c,u%p=0;t%p=0;valid_x(x2);u>=p;t>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2664bbd5,50873db31badcc71890e4f67753a65757f97aaa7dd5f1e82b753ace32219064b,u%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffff7028de7d,1eea9cc59cfcf2fa151ac6c274eea4110feb4f7b68c5965732e9992e976ef68e,u%p=0;valid_x(x2);u>=p;t>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fffffffffffffffffffffffffffffffffffffffffffffffffffffffffcbcfb7e7,12303941aedc208880735b1f1795c8e55be520ea93e103357b5d2adb7ed59b8e,u%p=0;valid_x(x1);u>=p;t>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3113ad9,7eed6b70e7b0767c7d7feac04e57aa2a12fef5e0f48f878fcbb88b3b6b5e0783,u%p=0;valid_x(x3);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a70000000000000000000000000000000000000000000000000000000000000000,649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb,t%p=0;valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff13cea4a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,649984435b62b4a25d40c6133e8d9ab8c53d4b059ee8a154a3be0fcf4e892edb,t%p=0;valid_x(x1);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff15028c590063f64d5a7f1c14915cd61eac886ab295bebd91992504cf77edb028bdd6267f,3fde5713f8282eead7d39d4201f44a7c85a5ac8a0681f35e54085c6b69543374,(u'^3+t'^2+7)%p=0;valid_x(x2);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de860000000000000000000000000000000000000000000000000000000000000000,3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2715de86fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,3524f77fa3a6eb4389c3cb5d27f1f91462086429cd6c0cb0df43ea8f1e7b3fb4,t%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff2c2c5709e7156c417717f2feab147141ec3da19fb759575cc6e37b2ea5ac9309f26f0f66,d2469ab3e04acbb21c65a1809f39caafe7a77c13d10f9dd38f391c01dc499c52,(u'^3-t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3a08cc1efffffffffffffffffffffffffffffffffffffffffffffffffffffffff760e9f0,38e2a5ce6a93e795e16d2c398bc99f0369202ce21e8f09d56777b40fc512bccc,valid_x(x3);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff3e91257d932016cbf69c4471bd1f656c6a107f1973de4af7086db897277060e25677f19a,864b3dc902c376709c10a93ad4bbe29fce0012f3dc8672c6286bba28d7d6d6fc,valid_x(x3);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff795d6c1c322cadf599dbb86481522b3cc55f15a67932db2afa0111d9ed6981bcd124bf44,766dfe4a700d9bee288b903ad58870e3d4fe2f0ef780bcac5c823f320d9a9bef,(u'^3+t'^2+7)%p=0;valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff8e426f0392389078c12b1a89e9542f0593bc96b6bfde8224f8654ef5d5cda935a3582194,faec7bc1987b63233fbc5f956edbf37d54404e7461c58ab8631bc68e451a0478,valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff91192139ffffffffffffffffffffffffffffffffffffffffffffffffffffffff45f0f1eb,ec29a50bae138dbf7d8e24825006bb5fc1a2cc1243ba335bc6116fb9e498ec1f,valid_x(x2);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff98eb9ab76e84499c483b3bf06214abfe065dddf43b8601de596d63b9e45a166a580541fe,1e0ff2dee9b09b136292a9e910f0d6ac3e552a644bba39e64e9dd3e3bbd3d4d4,(u'^3-t'^2+7)%p=0;valid_x(x3);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2c74d99efceaa550f1ad1c0f43f46e7ff1ee3bd0162b7bf55f2965da9c3450646,8b7dd5c3edba9ee97b70eff438f22dca9849c8254a2f3345a0a572ffeaae0928,valid_x(x2);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffff9b77b7f2ffffffffffffffffffffffffffffffffffffffffffffffffffffffff156ca896,0881950c8f51d6b9a6387465d5f12609ef1bb25412a08a74cb2dfb200c74bfbf,valid_x(x3);valid_x(x2);valid_x(x1);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffa2f5cd838816c16c4fe8a1661d606fdb13cf9af04b979a2e159a09409ebc8645d58fde02,2f083207b9fd9b550063c31cd62b8746bd543bdc5bbf10e3a35563e927f440c8,(u'^3+t'^2+7)%p=0;valid_x(x3);valid_x(x2);valid_x(x1);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c00000000000000000000000000000000000000000000000000000000000000000,4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0,t%p=0;valid_x(x3);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffb13f75c0fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,4f51e0be078e0cddab2742156adba7e7a148e73157072fd618cd60942b146bd0,t%p=0;valid_x(x3);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8d0000000000000000000000000000000000000000000000000000000000000000,16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2,t%p=0;valid_x(x2);u>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffe7bc1f8dfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f,16c2ccb54352ff4bd794f6efd613c72197ab7082da5b563bdf9cb3edaafe74c2,t%p=0;valid_x(x2);u>=p;t>=p
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffef64d162750546ce42b0431361e52d4f5242d8f24f33e6b1f99b591647cbc808f462af51,d41244d11ca4f65240687759f95ca9efbab767ededb38fd18c36e18cd3b6f6a9,(u'^3+t'^2+7)%p=0;valid_x(x3);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffff0e5be52372dd6e894b2a326fc3605a6e8f3c69c710bf27d630dfe2004988b78eb6eab36,64bf84dd5e03670fdb24c0f5d3c2c365736f51db6c92d95010716ad2d36134c8,valid_x(x3);valid_x(x2);valid_x(x1);u>=p
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffffefbb982fffffffffffffffffffffffffffffffffffffffffffffffffffffffff6d6db1f,1c92ccdfcf4ac550c28db57cff0c8515cb26936c786584a70114008d6c33a34b,valid_x(x1);u>=p;t>=p
diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index efb4934ff0..c250fc6fe8 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -1,7 +1,7 @@ # Copyright (c) 2019-2020 Pieter Wuille # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test-only secp256k1 elliptic curve implementation +"""Test-only secp256k1 elliptic curve protocols implementation WARNING: This code is slow, uses bad randomness, does not properly protect keys, and is trivially vulnerable to side channel attacks. Do not use for @@ -13,9 +13,13 @@ import os import random import unittest +from test_framework import secp256k1 + # Point with no known discrete log. H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" +# Order of the secp256k1 curve +ORDER = secp256k1.GE.ORDER def TaggedHash(tag, data): ss = hashlib.sha256(tag.encode('utf-8')).digest() @@ -23,233 +27,18 @@ def TaggedHash(tag, data): ss += data return hashlib.sha256(ss).digest() -def jacobi_symbol(n, k): - """Compute the Jacobi symbol of n modulo k - - See https://en.wikipedia.org/wiki/Jacobi_symbol - - For our application k is always prime, so this is the same as the Legendre symbol.""" - assert k > 0 and k & 1, "jacobi symbol is only defined for positive odd k" - n %= k - t = 0 - while n != 0: - while n & 1 == 0: - n >>= 1 - r = k & 7 - t ^= (r == 3 or r == 5) - n, k = k, n - t ^= (n & k & 3 == 3) - n = n % k - if k == 1: - return -1 if t else 1 - return 0 - -def modsqrt(a, p): - """Compute the square root of a modulo p when p % 4 = 3. - - The Tonelli-Shanks algorithm can be used. See https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm - - Limiting this function to only work for p % 4 = 3 means we don't need to - iterate through the loop. The highest n such that p - 1 = 2^n Q with Q odd - is n = 1. Therefore Q = (p-1)/2 and sqrt = a^((Q+1)/2) = a^((p+1)/4) - - secp256k1's is defined over field of size 2**256 - 2**32 - 977, which is 3 mod 4. - """ - if p % 4 != 3: - raise NotImplementedError("modsqrt only implemented for p % 4 = 3") - sqrt = pow(a, (p + 1)//4, p) - if pow(sqrt, 2, p) == a % p: - return sqrt - return None - -class EllipticCurve: - def __init__(self, p, a, b): - """Initialize elliptic curve y^2 = x^3 + a*x + b over GF(p).""" - self.p = p - self.a = a % p - self.b = b % p - - def affine(self, p1): - """Convert a Jacobian point tuple p1 to affine form, or None if at infinity. - - An affine point is represented as the Jacobian (x, y, 1)""" - x1, y1, z1 = p1 - if z1 == 0: - return None - inv = pow(z1, -1, self.p) - inv_2 = (inv**2) % self.p - inv_3 = (inv_2 * inv) % self.p - return ((inv_2 * x1) % self.p, (inv_3 * y1) % self.p, 1) - - def has_even_y(self, p1): - """Whether the point p1 has an even Y coordinate when expressed in affine coordinates.""" - return not (p1[2] == 0 or self.affine(p1)[1] & 1) - - def negate(self, p1): - """Negate a Jacobian point tuple p1.""" - x1, y1, z1 = p1 - return (x1, (self.p - y1) % self.p, z1) - - def on_curve(self, p1): - """Determine whether a Jacobian tuple p is on the curve (and not infinity)""" - x1, y1, z1 = p1 - z2 = pow(z1, 2, self.p) - z4 = pow(z2, 2, self.p) - return z1 != 0 and (pow(x1, 3, self.p) + self.a * x1 * z4 + self.b * z2 * z4 - pow(y1, 2, self.p)) % self.p == 0 - - def is_x_coord(self, x): - """Test whether x is a valid X coordinate on the curve.""" - x_3 = pow(x, 3, self.p) - return jacobi_symbol(x_3 + self.a * x + self.b, self.p) != -1 - - def lift_x(self, x): - """Given an X coordinate on the curve, return a corresponding affine point for which the Y coordinate is even.""" - x_3 = pow(x, 3, self.p) - v = x_3 + self.a * x + self.b - y = modsqrt(v, self.p) - if y is None: - return None - return (x, self.p - y if y & 1 else y, 1) - - def double(self, p1): - """Double a Jacobian tuple p1 - - See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Doubling""" - x1, y1, z1 = p1 - if z1 == 0: - return (0, 1, 0) - y1_2 = (y1**2) % self.p - y1_4 = (y1_2**2) % self.p - x1_2 = (x1**2) % self.p - s = (4*x1*y1_2) % self.p - m = 3*x1_2 - if self.a: - m += self.a * pow(z1, 4, self.p) - m = m % self.p - x2 = (m**2 - 2*s) % self.p - y2 = (m*(s - x2) - 8*y1_4) % self.p - z2 = (2*y1*z1) % self.p - return (x2, y2, z2) - - def add_mixed(self, p1, p2): - """Add a Jacobian tuple p1 and an affine tuple p2 - - See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition (with affine point)""" - x1, y1, z1 = p1 - x2, y2, z2 = p2 - assert z2 == 1 - # Adding to the point at infinity is a no-op - if z1 == 0: - return p2 - z1_2 = (z1**2) % self.p - z1_3 = (z1_2 * z1) % self.p - u2 = (x2 * z1_2) % self.p - s2 = (y2 * z1_3) % self.p - if x1 == u2: - if (y1 != s2): - # p1 and p2 are inverses. Return the point at infinity. - return (0, 1, 0) - # p1 == p2. The formulas below fail when the two points are equal. - return self.double(p1) - h = u2 - x1 - r = s2 - y1 - h_2 = (h**2) % self.p - h_3 = (h_2 * h) % self.p - u1_h_2 = (x1 * h_2) % self.p - x3 = (r**2 - h_3 - 2*u1_h_2) % self.p - y3 = (r*(u1_h_2 - x3) - y1*h_3) % self.p - z3 = (h*z1) % self.p - return (x3, y3, z3) - - def add(self, p1, p2): - """Add two Jacobian tuples p1 and p2 - - See https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates - Point Addition""" - x1, y1, z1 = p1 - x2, y2, z2 = p2 - # Adding the point at infinity is a no-op - if z1 == 0: - return p2 - if z2 == 0: - return p1 - # Adding an Affine to a Jacobian is more efficient since we save field multiplications and squarings when z = 1 - if z1 == 1: - return self.add_mixed(p2, p1) - if z2 == 1: - return self.add_mixed(p1, p2) - z1_2 = (z1**2) % self.p - z1_3 = (z1_2 * z1) % self.p - z2_2 = (z2**2) % self.p - z2_3 = (z2_2 * z2) % self.p - u1 = (x1 * z2_2) % self.p - u2 = (x2 * z1_2) % self.p - s1 = (y1 * z2_3) % self.p - s2 = (y2 * z1_3) % self.p - if u1 == u2: - if (s1 != s2): - # p1 and p2 are inverses. Return the point at infinity. - return (0, 1, 0) - # p1 == p2. The formulas below fail when the two points are equal. - return self.double(p1) - h = u2 - u1 - r = s2 - s1 - h_2 = (h**2) % self.p - h_3 = (h_2 * h) % self.p - u1_h_2 = (u1 * h_2) % self.p - x3 = (r**2 - h_3 - 2*u1_h_2) % self.p - y3 = (r*(u1_h_2 - x3) - s1*h_3) % self.p - z3 = (h*z1*z2) % self.p - return (x3, y3, z3) - - def mul(self, ps): - """Compute a (multi) point multiplication - - ps is a list of (Jacobian tuple, scalar) pairs. - """ - r = (0, 1, 0) - for i in range(255, -1, -1): - r = self.double(r) - for (p, n) in ps: - if ((n >> i) & 1): - r = self.add(r, p) - return r - -SECP256K1_FIELD_SIZE = 2**256 - 2**32 - 977 -SECP256K1 = EllipticCurve(SECP256K1_FIELD_SIZE, 0, 7) -SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) -SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 -SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 - -class ECPubKey(): + +class ECPubKey: """A secp256k1 public key""" def __init__(self): """Construct an uninitialized public key""" - self.valid = False + self.p = None def set(self, data): """Construct a public key from a serialization in compressed or uncompressed format""" - if (len(data) == 65 and data[0] == 0x04): - p = (int.from_bytes(data[1:33], 'big'), int.from_bytes(data[33:65], 'big'), 1) - self.valid = SECP256K1.on_curve(p) - if self.valid: - self.p = p - self.compressed = False - elif (len(data) == 33 and (data[0] == 0x02 or data[0] == 0x03)): - x = int.from_bytes(data[1:33], 'big') - if SECP256K1.is_x_coord(x): - p = SECP256K1.lift_x(x) - # Make the Y coordinate odd if required (lift_x always produces - # a point with an even Y coordinate). - if data[0] & 1: - p = SECP256K1.negate(p) - self.p = p - self.valid = True - self.compressed = True - else: - self.valid = False - else: - self.valid = False + self.p = secp256k1.GE.from_bytes(data) + self.compressed = len(data) == 33 @property def is_compressed(self): @@ -257,24 +46,21 @@ class ECPubKey(): @property def is_valid(self): - return self.valid + return self.p is not None def get_bytes(self): - assert self.valid - p = SECP256K1.affine(self.p) - if p is None: - return None + assert self.is_valid if self.compressed: - return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') + return self.p.to_bytes_compressed() else: - return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') + return self.p.to_bytes_uncompressed() def verify_ecdsa(self, sig, msg, low_s=True): """Verify a strictly DER-encoded ECDSA signature against this pubkey. See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the ECDSA verifier algorithm""" - assert self.valid + assert self.is_valid # Extract r and s from the DER formatted signature. Return false for # any DER encoding errors. @@ -310,24 +96,22 @@ class ECPubKey(): s = int.from_bytes(sig[6+rlen:6+rlen+slen], 'big') # Verify that r and s are within the group order - if r < 1 or s < 1 or r >= SECP256K1_ORDER or s >= SECP256K1_ORDER: + if r < 1 or s < 1 or r >= ORDER or s >= ORDER: return False - if low_s and s >= SECP256K1_ORDER_HALF: + if low_s and s >= secp256k1.GE.ORDER_HALF: return False z = int.from_bytes(msg, 'big') # Run verifier algorithm on r, s - w = pow(s, -1, SECP256K1_ORDER) - u1 = z*w % SECP256K1_ORDER - u2 = r*w % SECP256K1_ORDER - R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, u1), (self.p, u2)])) - if R is None or (R[0] % SECP256K1_ORDER) != r: + w = pow(s, -1, ORDER) + R = secp256k1.GE.mul((z * w, secp256k1.G), (r * w, self.p)) + if R.infinity or (int(R.x) % ORDER) != r: return False return True def generate_privkey(): """Generate a valid random 32-byte private key.""" - return random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big') + return random.randrange(1, ORDER).to_bytes(32, 'big') def rfc6979_nonce(key): """Compute signing nonce using RFC6979.""" @@ -339,7 +123,7 @@ def rfc6979_nonce(key): v = hmac.new(k, v, 'sha256').digest() return hmac.new(k, v, 'sha256').digest() -class ECKey(): +class ECKey: """A secp256k1 private key""" def __init__(self): @@ -349,7 +133,7 @@ class ECKey(): """Construct a private key object with given 32-byte secret and compressed flag.""" assert len(secret) == 32 secret = int.from_bytes(secret, 'big') - self.valid = (secret > 0 and secret < SECP256K1_ORDER) + self.valid = (secret > 0 and secret < ORDER) if self.valid: self.secret = secret self.compressed = compressed @@ -375,9 +159,7 @@ class ECKey(): """Compute an ECPubKey object for this secret key.""" assert self.valid ret = ECPubKey() - p = SECP256K1.mul([(SECP256K1_G, self.secret)]) - ret.p = p - ret.valid = True + ret.p = self.secret * secp256k1.G ret.compressed = self.compressed return ret @@ -392,12 +174,12 @@ class ECKey(): if rfc6979: k = int.from_bytes(rfc6979_nonce(self.secret.to_bytes(32, 'big') + msg), 'big') else: - k = random.randrange(1, SECP256K1_ORDER) - R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) - r = R[0] % SECP256K1_ORDER - s = (pow(k, -1, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER - if low_s and s > SECP256K1_ORDER_HALF: - s = SECP256K1_ORDER - s + k = random.randrange(1, ORDER) + R = k * secp256k1.G + r = int(R.x) % ORDER + s = (pow(k, -1, ORDER) * (z + self.secret * r)) % ORDER + if low_s and s > secp256k1.GE.ORDER_HALF: + s = ORDER - s # Represent in DER format. The byte representations of r and s have # length rounded up (255 bits becomes 32 bytes and 256 bits becomes 33 # bytes). @@ -413,10 +195,10 @@ def compute_xonly_pubkey(key): assert len(key) == 32 x = int.from_bytes(key, 'big') - if x == 0 or x >= SECP256K1_ORDER: + if x == 0 or x >= ORDER: return (None, None) - P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, x)])) - return (P[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(P)) + P = x * secp256k1.G + return (P.to_bytes_xonly(), not P.y.is_even()) def tweak_add_privkey(key, tweak): """Tweak a private key (after negating it if needed).""" @@ -425,14 +207,14 @@ def tweak_add_privkey(key, tweak): assert len(tweak) == 32 x = int.from_bytes(key, 'big') - if x == 0 or x >= SECP256K1_ORDER: + if x == 0 or x >= ORDER: return None - if not SECP256K1.has_even_y(SECP256K1.mul([(SECP256K1_G, x)])): - x = SECP256K1_ORDER - x + if not (x * secp256k1.G).y.is_even(): + x = ORDER - x t = int.from_bytes(tweak, 'big') - if t >= SECP256K1_ORDER: + if t >= ORDER: return None - x = (x + t) % SECP256K1_ORDER + x = (x + t) % ORDER if x == 0: return None return x.to_bytes(32, 'big') @@ -443,19 +225,16 @@ def tweak_add_pubkey(key, tweak): assert len(key) == 32 assert len(tweak) == 32 - x_coord = int.from_bytes(key, 'big') - if x_coord >= SECP256K1_FIELD_SIZE: - return None - P = SECP256K1.lift_x(x_coord) + P = secp256k1.GE.from_bytes_xonly(key) if P is None: return None t = int.from_bytes(tweak, 'big') - if t >= SECP256K1_ORDER: + if t >= ORDER: return None - Q = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, t), (P, 1)])) - if Q is None: + Q = t * secp256k1.G + P + if Q.infinity: return None - return (Q[0].to_bytes(32, 'big'), not SECP256K1.has_even_y(Q)) + return (Q.to_bytes_xonly(), not Q.y.is_even()) def verify_schnorr(key, sig, msg): """Verify a Schnorr signature (see BIP 340). @@ -468,23 +247,20 @@ def verify_schnorr(key, sig, msg): assert len(msg) == 32 assert len(sig) == 64 - x_coord = int.from_bytes(key, 'big') - if x_coord == 0 or x_coord >= SECP256K1_FIELD_SIZE: - return False - P = SECP256K1.lift_x(x_coord) + P = secp256k1.GE.from_bytes_xonly(key) if P is None: return False r = int.from_bytes(sig[0:32], 'big') - if r >= SECP256K1_FIELD_SIZE: + if r >= secp256k1.FE.SIZE: return False s = int.from_bytes(sig[32:64], 'big') - if s >= SECP256K1_ORDER: + if s >= ORDER: return False - e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % SECP256K1_ORDER - R = SECP256K1.mul([(SECP256K1_G, s), (P, SECP256K1_ORDER - e)]) - if not SECP256K1.has_even_y(R): + e = int.from_bytes(TaggedHash("BIP0340/challenge", sig[0:32] + key + msg), 'big') % ORDER + R = secp256k1.GE.mul((s, secp256k1.G), (-e, P)) + if R.infinity or not R.y.is_even(): return False - if ((r * R[2] * R[2]) % SECP256K1_FIELD_SIZE) != R[0]: + if r != R.x: return False return True @@ -499,23 +275,24 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): assert len(aux) == 32 sec = int.from_bytes(key, 'big') - if sec == 0 or sec >= SECP256K1_ORDER: + if sec == 0 or sec >= ORDER: return None - P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)])) - if SECP256K1.has_even_y(P) == flip_p: - sec = SECP256K1_ORDER - sec + P = sec * secp256k1.G + if P.y.is_even() == flip_p: + sec = ORDER - sec t = (sec ^ int.from_bytes(TaggedHash("BIP0340/aux", aux), 'big')).to_bytes(32, 'big') - kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER + kp = int.from_bytes(TaggedHash("BIP0340/nonce", t + P.to_bytes_xonly() + msg), 'big') % ORDER assert kp != 0 - R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) - k = kp if SECP256K1.has_even_y(R) != flip_r else SECP256K1_ORDER - kp - e = int.from_bytes(TaggedHash("BIP0340/challenge", R[0].to_bytes(32, 'big') + P[0].to_bytes(32, 'big') + msg), 'big') % SECP256K1_ORDER - return R[0].to_bytes(32, 'big') + ((k + e * sec) % SECP256K1_ORDER).to_bytes(32, 'big') + R = kp * secp256k1.G + k = kp if R.y.is_even() != flip_r else ORDER - kp + e = int.from_bytes(TaggedHash("BIP0340/challenge", R.to_bytes_xonly() + P.to_bytes_xonly() + msg), 'big') % ORDER + return R.to_bytes_xonly() + ((k + e * sec) % ORDER).to_bytes(32, 'big') + class TestFrameworkKey(unittest.TestCase): def test_schnorr(self): """Test the Python Schnorr implementation.""" - byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, SECP256K1_ORDER - 1, SECP256K1_ORDER, 2**256 - 1]] + byte_arrays = [generate_privkey() for _ in range(3)] + [v.to_bytes(32, 'big') for v in [0, ORDER - 1, ORDER, 2**256 - 1]] keys = {} for privkey in byte_arrays: # build array of key/pubkey pairs pubkey, _ = compute_xonly_pubkey(privkey) diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 443cae86a1..78f58cf11f 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -689,6 +689,16 @@ def LegacySignatureHash(*args, **kwargs): else: return (hash256(msg), err) +def sign_input_legacy(tx, input_index, input_scriptpubkey, privkey, sighash_type=SIGHASH_ALL): + """Add legacy ECDSA signature for a given transaction input. Note that the signature + is prepended to the scriptSig field, i.e. additional data pushes necessary for more + complex spends than P2PK (e.g. pubkey for P2PKH) can be already set before.""" + (sighash, err) = LegacySignatureHash(input_scriptpubkey, tx, input_index, sighash_type) + assert err is None + der_sig = privkey.sign_ecdsa(sighash) + tx.vin[input_index].scriptSig = bytes(CScript([der_sig + bytes([sighash_type])])) + tx.vin[input_index].scriptSig + tx.rehash() + # TODO: Allow cached hashPrevouts/hashSequence/hashOutputs to be provided. # Performance optimization probably not necessary for python tests, however. # Note that this corresponds to sigversion == 1 in EvalScript, which is used diff --git a/test/functional/test_framework/secp256k1.py b/test/functional/test_framework/secp256k1.py new file mode 100644 index 0000000000..2e9e419da5 --- /dev/null +++ b/test/functional/test_framework/secp256k1.py @@ -0,0 +1,346 @@ +# Copyright (c) 2022-2023 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test-only implementation of low-level secp256k1 field and group arithmetic + +It is designed for ease of understanding, not performance. + +WARNING: This code is slow and trivially vulnerable to side channel attacks. Do not use for +anything but tests. + +Exports: +* FE: class for secp256k1 field elements +* GE: class for secp256k1 group elements +* G: the secp256k1 generator point +""" + + +class FE: + """Objects of this class represent elements of the field GF(2**256 - 2**32 - 977). + + They are represented internally in numerator / denominator form, in order to delay inversions. + """ + + # The size of the field (also its modulus and characteristic). + SIZE = 2**256 - 2**32 - 977 + + def __init__(self, a=0, b=1): + """Initialize a field element a/b; both a and b can be ints or field elements.""" + if isinstance(a, FE): + num = a._num + den = a._den + else: + num = a % FE.SIZE + den = 1 + if isinstance(b, FE): + den = (den * b._num) % FE.SIZE + num = (num * b._den) % FE.SIZE + else: + den = (den * b) % FE.SIZE + assert den != 0 + if num == 0: + den = 1 + self._num = num + self._den = den + + def __add__(self, a): + """Compute the sum of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self._num * a._den + self._den * a._num, self._den * a._den) + return FE(self._num + self._den * a, self._den) + + def __radd__(self, a): + """Compute the sum of an integer and a field element.""" + return FE(a) + self + + def __sub__(self, a): + """Compute the difference of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self._num * a._den - self._den * a._num, self._den * a._den) + return FE(self._num - self._den * a, self._den) + + def __rsub__(self, a): + """Compute the difference of an integer and a field element.""" + return FE(a) - self + + def __mul__(self, a): + """Compute the product of two field elements (second may be int).""" + if isinstance(a, FE): + return FE(self._num * a._num, self._den * a._den) + return FE(self._num * a, self._den) + + def __rmul__(self, a): + """Compute the product of an integer with a field element.""" + return FE(a) * self + + def __truediv__(self, a): + """Compute the ratio of two field elements (second may be int).""" + return FE(self, a) + + def __pow__(self, a): + """Raise a field element to an integer power.""" + return FE(pow(self._num, a, FE.SIZE), pow(self._den, a, FE.SIZE)) + + def __neg__(self): + """Negate a field element.""" + return FE(-self._num, self._den) + + def __int__(self): + """Convert a field element to an integer in range 0..p-1. The result is cached.""" + if self._den != 1: + self._num = (self._num * pow(self._den, -1, FE.SIZE)) % FE.SIZE + self._den = 1 + return self._num + + def sqrt(self): + """Compute the square root of a field element if it exists (None otherwise). + + Due to the fact that our modulus is of the form (p % 4) == 3, the Tonelli-Shanks + algorithm (https://en.wikipedia.org/wiki/Tonelli-Shanks_algorithm) is simply + raising the argument to the power (p + 1) / 4. + + To see why: (p-1) % 2 = 0, so 2 divides the order of the multiplicative group, + and thus only half of the non-zero field elements are squares. An element a is + a (nonzero) square when Euler's criterion, a^((p-1)/2) = 1 (mod p), holds. We're + looking for x such that x^2 = a (mod p). Given a^((p-1)/2) = 1, that is equivalent + to x^2 = a^(1 + (p-1)/2) mod p. As (1 + (p-1)/2) is even, this is equivalent to + x = a^((1 + (p-1)/2)/2) mod p, or x = a^((p+1)/4) mod p.""" + v = int(self) + s = pow(v, (FE.SIZE + 1) // 4, FE.SIZE) + if s**2 % FE.SIZE == v: + return FE(s) + return None + + def is_square(self): + """Determine if this field element has a square root.""" + # A more efficient algorithm is possible here (Jacobi symbol). + return self.sqrt() is not None + + def is_even(self): + """Determine whether this field element, represented as integer in 0..p-1, is even.""" + return int(self) & 1 == 0 + + def __eq__(self, a): + """Check whether two field elements are equal (second may be an int).""" + if isinstance(a, FE): + return (self._num * a._den - self._den * a._num) % FE.SIZE == 0 + return (self._num - self._den * a) % FE.SIZE == 0 + + def to_bytes(self): + """Convert a field element to a 32-byte array (BE byte order).""" + return int(self).to_bytes(32, 'big') + + @staticmethod + def from_bytes(b): + """Convert a 32-byte array to a field element (BE byte order, no overflow allowed).""" + v = int.from_bytes(b, 'big') + if v >= FE.SIZE: + return None + return FE(v) + + def __str__(self): + """Convert this field element to a 64 character hex string.""" + return f"{int(self):064x}" + + def __repr__(self): + """Get a string representation of this field element.""" + return f"FE(0x{int(self):x})" + + +class GE: + """Objects of this class represent secp256k1 group elements (curve points or infinity) + + Normal points on the curve have fields: + * x: the x coordinate (a field element) + * y: the y coordinate (a field element, satisfying y^2 = x^3 + 7) + * infinity: False + + The point at infinity has field: + * infinity: True + """ + + # Order of the group (number of points on the curve, plus 1 for infinity) + ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + + # Number of valid distinct x coordinates on the curve. + ORDER_HALF = ORDER // 2 + + def __init__(self, x=None, y=None): + """Initialize a group element with specified x and y coordinates, or infinity.""" + if x is None: + # Initialize as infinity. + assert y is None + self.infinity = True + else: + # Initialize as point on the curve (and check that it is). + fx = FE(x) + fy = FE(y) + assert fy**2 == fx**3 + 7 + self.infinity = False + self.x = fx + self.y = fy + + def __add__(self, a): + """Add two group elements together.""" + # Deal with infinity: a + infinity == infinity + a == a. + if self.infinity: + return a + if a.infinity: + return self + if self.x == a.x: + if self.y != a.y: + # A point added to its own negation is infinity. + assert self.y + a.y == 0 + return GE() + else: + # For identical inputs, use the tangent (doubling formula). + lam = (3 * self.x**2) / (2 * self.y) + else: + # For distinct inputs, use the line through both points (adding formula). + lam = (self.y - a.y) / (self.x - a.x) + # Determine point opposite to the intersection of that line with the curve. + x = lam**2 - (self.x + a.x) + y = lam * (self.x - x) - self.y + return GE(x, y) + + @staticmethod + def mul(*aps): + """Compute a (batch) scalar group element multiplication. + + GE.mul((a1, p1), (a2, p2), (a3, p3)) is identical to a1*p1 + a2*p2 + a3*p3, + but more efficient.""" + # Reduce all the scalars modulo order first (so we can deal with negatives etc). + naps = [(a % GE.ORDER, p) for a, p in aps] + # Start with point at infinity. + r = GE() + # Iterate over all bit positions, from high to low. + for i in range(255, -1, -1): + # Double what we have so far. + r = r + r + # Add then add the points for which the corresponding scalar bit is set. + for (a, p) in naps: + if (a >> i) & 1: + r += p + return r + + def __rmul__(self, a): + """Multiply an integer with a group element.""" + if self == G: + return FAST_G.mul(a) + return GE.mul((a, self)) + + def __neg__(self): + """Compute the negation of a group element.""" + if self.infinity: + return self + return GE(self.x, -self.y) + + def to_bytes_compressed(self): + """Convert a non-infinite group element to 33-byte compressed encoding.""" + assert not self.infinity + return bytes([3 - self.y.is_even()]) + self.x.to_bytes() + + def to_bytes_uncompressed(self): + """Convert a non-infinite group element to 65-byte uncompressed encoding.""" + assert not self.infinity + return b'\x04' + self.x.to_bytes() + self.y.to_bytes() + + def to_bytes_xonly(self): + """Convert (the x coordinate of) a non-infinite group element to 32-byte xonly encoding.""" + assert not self.infinity + return self.x.to_bytes() + + @staticmethod + def lift_x(x): + """Return group element with specified field element as x coordinate (and even y).""" + y = (FE(x)**3 + 7).sqrt() + if y is None: + return None + if not y.is_even(): + y = -y + return GE(x, y) + + @staticmethod + def from_bytes(b): + """Convert a compressed or uncompressed encoding to a group element.""" + assert len(b) in (33, 65) + if len(b) == 33: + if b[0] != 2 and b[0] != 3: + return None + x = FE.from_bytes(b[1:]) + if x is None: + return None + r = GE.lift_x(x) + if r is None: + return None + if b[0] == 3: + r = -r + return r + else: + if b[0] != 4: + return None + x = FE.from_bytes(b[1:33]) + y = FE.from_bytes(b[33:]) + if y**2 != x**3 + 7: + return None + return GE(x, y) + + @staticmethod + def from_bytes_xonly(b): + """Convert a point given in xonly encoding to a group element.""" + assert len(b) == 32 + x = FE.from_bytes(b) + if x is None: + return None + return GE.lift_x(x) + + @staticmethod + def is_valid_x(x): + """Determine whether the provided field element is a valid X coordinate.""" + return (FE(x)**3 + 7).is_square() + + def __str__(self): + """Convert this group element to a string.""" + if self.infinity: + return "(inf)" + return f"({self.x},{self.y})" + + def __repr__(self): + """Get a string representation for this group element.""" + if self.infinity: + return "GE()" + return f"GE(0x{int(self.x):x},0x{int(self.y):x})" + +# The secp256k1 generator point +G = GE.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + + +class FastGEMul: + """Table for fast multiplication with a constant group element. + + Speed up scalar multiplication with a fixed point P by using a precomputed lookup table with + its powers of 2: + + table = [P, 2*P, 4*P, (2^3)*P, (2^4)*P, ..., (2^255)*P] + + During multiplication, the points corresponding to each bit set in the scalar are added up, + i.e. on average ~128 point additions take place. + """ + + def __init__(self, p): + self.table = [p] # table[i] = (2^i) * p + for _ in range(255): + p = p + p + self.table.append(p) + + def mul(self, a): + result = GE() + a = a % GE.ORDER + for bit in range(a.bit_length()): + if a & (1 << bit): + result += self.table[bit] + return result + +# Precomputed table with multiples of G for fast multiplication +FAST_G = FastGEMul(G) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index d3aae3fb9a..73e7516ea7 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -92,7 +92,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): This class also contains various public and private helper methods.""" - def __init__(self): + def __init__(self) -> None: """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.chain: str = 'regtest' self.setup_clean_chain: bool = False diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 13d83a9cac..1fcef6ce1c 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -22,7 +22,10 @@ import shlex import sys from pathlib import Path -from .authproxy import JSONRPCException +from .authproxy import ( + JSONRPCException, + serialization_fallback, +) from .descriptors import descsum_create from .p2p import P2P_SUBVERSION from .util import ( @@ -35,7 +38,6 @@ from .util import ( rpc_url, wait_until_helper, p2p_port, - EncodeDecimal, ) BITCOIND_PROC_WAIT_TIMEOUT = 60 @@ -248,7 +250,7 @@ class TestNode(): # Wait for the node to finish reindex, block import, and # loading the mempool. Usually importing happens fast or # even "immediate" when the node is started. However, there - # is no guarantee and sometimes ThreadImport might finish + # is no guarantee and sometimes ImportBlocks might finish # later. This is going to cause intermittent test failures, # because generally the tests assume the node is fully # ready after being started. @@ -351,21 +353,13 @@ class TestNode(): for profile_name in tuple(self.perf_subprocesses.keys()): self._stop_perf(profile_name) - # Check that stderr is as expected - self.stderr.seek(0) - stderr = self.stderr.read().decode('utf-8').strip() - if stderr != expected_stderr: - raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) - - self.stdout.close() - self.stderr.close() - del self.p2ps[:] + assert (not expected_stderr) or wait_until_stopped # Must wait to check stderr if wait_until_stopped: - self.wait_until_stopped() + self.wait_until_stopped(expected_stderr=expected_stderr) - def is_node_stopped(self, expected_ret_code=0): + def is_node_stopped(self, *, expected_stderr="", expected_ret_code=0): """Checks whether the node has stopped. Returns True if the node has stopped. False otherwise. @@ -379,6 +373,15 @@ class TestNode(): # process has stopped. Assert that it didn't return an error code. assert return_code == expected_ret_code, self._node_msg( f"Node returned unexpected exit code ({return_code}) vs ({expected_ret_code}) when stopping") + # Check that stderr is as expected + self.stderr.seek(0) + stderr = self.stderr.read().decode('utf-8').strip() + if stderr != expected_stderr: + raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) + + self.stdout.close() + self.stderr.close() + self.running = False self.process = None self.rpc_connected = False @@ -386,9 +389,9 @@ class TestNode(): self.log.debug("Node stopped") return True - def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False): + def wait_until_stopped(self, *, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False, **kwargs): expected_ret_code = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS - wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code), timeout=timeout, timeout_factor=self.timeout_factor) + wait_until_helper(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout, timeout_factor=self.timeout_factor) def replace_in_config(self, replacements): """ @@ -406,13 +409,21 @@ class TestNode(): conf.write(conf_data) @property + def datadir_path(self) -> Path: + return Path(self.datadir) + + @property def chain_path(self) -> Path: - return Path(self.datadir) / self.chain + return self.datadir_path / self.chain @property def debug_log_path(self) -> Path: return self.chain_path / 'debug.log' + @property + def wallets_path(self) -> Path: + return self.chain_path / "wallets" + def debug_log_bytes(self) -> int: with open(self.debug_log_path, encoding='utf-8') as dl: dl.seek(0, 2) @@ -707,7 +718,7 @@ def arg_to_cli(arg): elif arg is None: return 'null' elif isinstance(arg, dict) or isinstance(arg, list): - return json.dumps(arg, default=EncodeDecimal) + return json.dumps(arg, default=serialization_fallback) else: return str(arg) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index d3b3e4d536..9143397042 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -211,12 +211,6 @@ def check_json_precision(): raise RuntimeError("JSON encode/decode loses precision") -def EncodeDecimal(o): - if isinstance(o, Decimal): - return str(o) - raise TypeError(repr(o) + " is not JSON serializable") - - def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) @@ -315,7 +309,7 @@ class PortSeed: n = None -def get_rpc_proxy(url: str, node_number: int, *, timeout: int=None, coveragedir: str=None) -> coverage.AuthServiceProxyWrapper: +def get_rpc_proxy(url: str, node_number: int, *, timeout: Optional[int]=None, coveragedir: Optional[str]=None) -> coverage.AuthServiceProxyWrapper: """ Args: url: URL of the RPC server to call diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index 271095ea21..4d75194353 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -36,12 +36,11 @@ from test_framework.messages import ( ) from test_framework.script import ( CScript, - LegacySignatureHash, LEAF_VERSION_TAPSCRIPT, OP_NOP, OP_RETURN, OP_TRUE, - SIGHASH_ALL, + sign_input_legacy, taproot_construct, ) from test_framework.script_util import ( @@ -166,18 +165,16 @@ class MiniWallet: def sign_tx(self, tx, fixed_length=True): if self._mode == MiniWalletMode.RAW_P2PK: - (sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL) - assert err is None # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability): # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes) - # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes - der_sig = b'' - while not len(der_sig) == 71: - der_sig = self._priv_key.sign_ecdsa(sighash) + # with the DER header/skeleton data of 6 bytes added, plus 2 bytes scriptSig overhead + # (OP_PUSHn and SIGHASH_ALL), this leads to a scriptSig target size of 73 bytes + tx.vin[0].scriptSig = b'' + while not len(tx.vin[0].scriptSig) == 73: + tx.vin[0].scriptSig = b'' + sign_input_legacy(tx, 0, self._scriptPubKey, self._priv_key) if not fixed_length: break - tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))]) - tx.rehash() elif self._mode == MiniWalletMode.RAW_OP_TRUE: for i in tx.vin: i.scriptSig = CScript([OP_NOP] * 43) # pad to identical size diff --git a/test/functional/test_framework/xswiftec_inv_test_vectors.csv b/test/functional/test_framework/xswiftec_inv_test_vectors.csv new file mode 100644 index 0000000000..138c4cf85c --- /dev/null +++ b/test/functional/test_framework/xswiftec_inv_test_vectors.csv @@ -0,0 +1,33 @@ +u,x,case0_t,case1_t,case2_t,case3_t,case4_t,case5_t,case6_t,case7_t,comment
+05ff6bdad900fc3261bc7fe34e2fb0f569f06e091ae437d3a52e9da0cbfb9590,80cdf63774ec7022c89a5a8558e373a279170285e0ab27412dbce510bdfe23fc,,,45654798ece071ba79286d04f7f3eb1c3f1d17dd883610f2ad2efd82a287466b,0aeaa886f6b76c7158452418cbf5033adc5747e9e9b5d3b2303db96936528557,,,ba9ab867131f8e4586d792fb080c14e3c0e2e82277c9ef0d52d1027c5d78b5c4,f51557790948938ea7badbe7340afcc523a8b816164a2c4dcfc24695c9ad76d8,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:info[v=0]&ok;case3:ok;case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:info[v=0]&ok;case7:ok
+1737a85f4c8d146cec96e3ffdca76d9903dcf3bd53061868d478c78c63c2aa9e,39e48dd150d2f429be088dfd5b61882e7e8407483702ae9a5ab35927b15f85ea,1be8cc0b04be0c681d0c6a68f733f82c6c896e0c8a262fcd392918e303a7abf4,605b5814bf9b8cb066667c9e5480d22dc5b6c92f14b4af3ee0a9eb83b03685e3,,,e41733f4fb41f397e2f3959708cc07d3937691f375d9d032c6d6e71bfc58503b,9fa4a7eb4064734f99998361ab7f2dd23a4936d0eb4b50c11f56147b4fc9764c,,,case0:ok;case1:ok;case2:info[v=0]&bad[non_square(s)];case3:bad[non_square(s)];case4:ok;case5:ok;case6:info[v=0]&bad[non_square(s)];case7:bad[non_square(s)]
+1aaa1ccebf9c724191033df366b36f691c4d902c228033ff4516d122b2564f68,c75541259d3ba98f207eaa30c69634d187d0b6da594e719e420f4898638fc5b0,,,,,,,,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:bad[non_square(q)];case3:bad[non_square(q)];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:bad[non_square(q)];case7:bad[non_square(q)]
+2323a1d079b0fd72fc8bb62ec34230a815cb0596c2bfac998bd6b84260f5dc26,239342dfb675500a34a196310b8d87d54f49dcac9da50c1743ceab41a7b249ff,f63580b8aa49c4846de56e39e1b3e73f171e881eba8c66f614e67e5c975dfc07,b6307b332e699f1cf77841d90af25365404deb7fed5edb3090db49e642a156b6,,,09ca7f4755b63b7b921a91c61e4c18c0e8e177e145739909eb1981a268a20028,49cf84ccd19660e30887be26f50dac9abfb2148012a124cf6f24b618bd5ea579,,,case0:ok;case1:ok;case2:bad[non_square(q)];case3:bad[non_square(q)];case4:ok;case5:ok;case6:bad[non_square(q)];case7:bad[non_square(q)]
+2dc90e640cb646ae9164c0b5a9ef0169febe34dc4437d6e46acb0e27e219d1e8,d236f19bf349b9516e9b3f4a5610fe960141cb23bbc8291b9534f1d71de62a47,e69df7d9c026c36600ebdf588072675847c0c431c8eb730682533e964b6252c9,4f18bbdf7c2d6c5f818c18802fa35cd069eaa79fff74e4fc837c80d93fece2f8,,,196208263fd93c99ff1420a77f8d98a7b83f3bce37148cf97dacc168b49da966,b0e7442083d293a07e73e77fd05ca32f96155860008b1b037c837f25c0131937,,,case0:ok;case1:info[v=0]&ok;case2:bad[non_square(q)];case3:bad[non_square(q)];case4:ok;case5:info[v=0]&ok;case6:bad[non_square(q)];case7:bad[non_square(q)]
+3edd7b3980e2f2f34d1409a207069f881fda5f96f08027ac4465b63dc278d672,053a98de4a27b1961155822b3a3121f03b2a14458bd80eb4a560c4c7a85c149c,,,b3dae4b7dcf858e4c6968057cef2b156465431526538199cf52dc1b2d62fda30,4aa77dd55d6b6d3cfa10cc9d0fe42f79232e4575661049ae36779c1d0c666d88,,,4c251b482307a71b39697fa8310d4ea9b9abcead9ac7e6630ad23e4c29d021ff,b558822aa29492c305ef3362f01bd086dcd1ba8a99efb651c98863e1f3998ea7,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:ok;case3:ok;case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:ok;case7:ok
+4295737efcb1da6fb1d96b9ca7dcd1e320024b37a736c4948b62598173069f70,fa7ffe4f25f88362831c087afe2e8a9b0713e2cac1ddca6a383205a266f14307,,,,,,,,,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:bad[non_square(s)];case3:bad[non_square(s)];case4:bad[non_square(s)];case5:bad[non_square(s)];case6:bad[non_square(s)];case7:bad[non_square(s)]
+587c1a0cee91939e7f784d23b963004a3bf44f5d4e32a0081995ba20b0fca59e,2ea988530715e8d10363907ff25124524d471ba2454d5ce3be3f04194dfd3a3c,cfd5a094aa0b9b8891b76c6ab9438f66aa1c095a65f9f70135e8171292245e74,a89057d7c6563f0d6efa19ae84412b8a7b47e791a191ecdfdf2af84fd97bc339,475d0ae9ef46920df07b34117be5a0817de1023e3cc32689e9be145b406b0aef,a0759178ad80232454f827ef05ea3e72ad8d75418e6d4cc1cd4f5306c5e7c453,302a5f6b55f464776e48939546bc709955e3f6a59a0608feca17e8ec6ddb9dbb,576fa82839a9c0f29105e6517bbed47584b8186e5e6e132020d507af268438f6,b8a2f51610b96df20f84cbee841a5f7e821efdc1c33cd9761641eba3bf94f140,5f8a6e87527fdcdbab07d810fa15c18d52728abe7192b33e32b0acf83a1837dc,case0:ok;case1:ok;case2:ok;case3:ok;case4:ok;case5:ok;case6:ok;case7:ok
+5fa88b3365a635cbbcee003cce9ef51dd1a310de277e441abccdb7be1e4ba249,79461ff62bfcbcac4249ba84dd040f2cec3c63f725204dc7f464c16bf0ff3170,,,6bb700e1f4d7e236e8d193ff4a76c1b3bcd4e2b25acac3d51c8dac653fe909a0,f4c73410633da7f63a4f1d55aec6dd32c4c6d89ee74075edb5515ed90da9e683,,,9448ff1e0b281dc9172e6c00b5893e4c432b1d4da5353c2ae3725399c016f28f,0b38cbef9cc25809c5b0e2aa513922cd3b39276118bf8a124aaea125f25615ac,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:ok;case3:info[v=0]&ok;case4:bad[non_square(s)];case5:bad[non_square(s)];case6:ok;case7:info[v=0]&ok
+6fb31c7531f03130b42b155b952779efbb46087dd9807d241a48eac63c3d96d6,56f81be753e8d4ae4940ea6f46f6ec9fda66a6f96cc95f506cb2b57490e94260,,,59059774795bdb7a837fbe1140a5fa59984f48af8df95d57dd6d1c05437dcec1,22a644db79376ad4e7b3a009e58b3f13137c54fdf911122cc93667c47077d784,,,a6fa688b86a424857c8041eebf5a05a667b0b7507206a2a82292e3f9bc822d6e,dd59bb2486c8952b184c5ff61a74c0ecec83ab0206eeedd336c9983a8f8824ab,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:ok;case3:info[v=0]&ok;case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:ok;case7:info[v=0]&ok
+704cd226e71cb6826a590e80dac90f2d2f5830f0fdf135a3eae3965bff25ff12,138e0afa68936ee670bd2b8db53aedbb7bea2a8597388b24d0518edd22ad66ec,,,,,,,,,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:bad[non_square(q)];case3:bad[non_square(q)];case4:bad[non_square(s)];case5:bad[non_square(s)];case6:bad[non_square(q)];case7:bad[non_square(q)]
+725e914792cb8c8949e7e1168b7cdd8a8094c91c6ec2202ccd53a6a18771edeb,8da16eb86d347376b6181ee9748322757f6b36e3913ddfd332ac595d788e0e44,dd357786b9f6873330391aa5625809654e43116e82a5a5d82ffd1d6624101fc4,a0b7efca01814594c59c9aae8e49700186ca5d95e88bcc80399044d9c2d8613d,,,22ca8879460978cccfc6e55a9da7f69ab1bcee917d5a5a27d002e298dbefdc6b,5f481035fe7eba6b3a63655171b68ffe7935a26a1774337fc66fbb253d279af2,,,case0:ok;case1:info[v=0]&ok;case2:bad[non_square(s)];case3:bad[non_square(s)];case4:ok;case5:info[v=0]&ok;case6:bad[non_square(s)];case7:bad[non_square(s)]
+78fe6b717f2ea4a32708d79c151bf503a5312a18c0963437e865cc6ed3f6ae97,8701948e80d15b5cd8f72863eae40afc5aced5e73f69cbc8179a33902c094d98,,,,,,,,,case0:bad[non_square(s)];case1:info[v=0]&bad[non_square(s)];case2:bad[non_square(q)];case3:bad[non_square(q)];case4:bad[non_square(s)];case5:info[v=0]&bad[non_square(s)];case6:bad[non_square(q)];case7:bad[non_square(q)]
+7c37bb9c5061dc07413f11acd5a34006e64c5c457fdb9a438f217255a961f50d,5c1a76b44568eb59d6789a7442d9ed7cdc6226b7752b4ff8eaf8e1a95736e507,,,b94d30cd7dbff60b64620c17ca0fafaa40b3d1f52d077a60a2e0cafd145086c2,,,,46b2cf32824009f49b9df3e835f05055bf4c2e0ad2f8859f5d1f3501ebaf756d,,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:info[q=0]&info[X=0]&ok;case3:info[q=0]&bad[r=0];case4:bad[non_square(s)];case5:bad[non_square(s)];case6:info[q=0]&info[X=0]&ok;case7:info[q=0]&bad[r=0]
+82388888967f82a6b444438a7d44838e13c0d478b9ca060da95a41fb94303de6,29e9654170628fec8b4972898b113cf98807f4609274f4f3140d0674157c90a0,,,,,,,,,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:bad[non_square(s)];case3:info[v=0]&bad[non_square(s)];case4:bad[non_square(s)];case5:bad[non_square(s)];case6:bad[non_square(s)];case7:info[v=0]&bad[non_square(s)]
+91298f5770af7a27f0a47188d24c3b7bf98ab2990d84b0b898507e3c561d6472,144f4ccbd9a74698a88cbf6fd00ad886d339d29ea19448f2c572cac0a07d5562,e6a0ffa3807f09dadbe71e0f4be4725f2832e76cad8dc1d943ce839375eff248,837b8e68d4917544764ad0903cb11f8615d2823cefbb06d89049dbabc69befda,,,195f005c7f80f6252418e1f0b41b8da0d7cd189352723e26bc317c6b8a1009e7,7c8471972b6e8abb89b52f6fc34ee079ea2d7dc31044f9276fb6245339640c55,,,case0:ok;case1:ok;case2:bad[non_square(s)];case3:info[v=0]&bad[non_square(s)];case4:ok;case5:ok;case6:bad[non_square(s)];case7:info[v=0]&bad[non_square(s)]
+b682f3d03bbb5dee4f54b5ebfba931b4f52f6a191e5c2f483c73c66e9ace97e1,904717bf0bc0cb7873fcdc38aa97f19e3a62630972acff92b24cc6dda197cb96,,,,,,,,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:bad[non_square(s)];case3:bad[non_square(s)];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:bad[non_square(s)];case7:bad[non_square(s)]
+c17ec69e665f0fb0dbab48d9c2f94d12ec8a9d7eacb58084833091801eb0b80b,147756e66d96e31c426d3cc85ed0c4cfbef6341dd8b285585aa574ea0204b55e,6f4aea431a0043bdd03134d6d9159119ce034b88c32e50e8e36c4ee45eac7ae9,fd5be16d4ffa2690126c67c3ef7cb9d29b74d397c78b06b3605fda34dc9696a6,5e9c60792a2f000e45c6250f296f875e174efc0e9703e628706103a9dd2d82c7,,90b515bce5ffbc422fcecb2926ea6ee631fcb4773cd1af171c93b11aa1538146,02a41e92b005d96fed93983c1083462d648b2c683874f94c9fa025ca23696589,a1639f86d5d0fff1ba39daf0d69078a1e8b103f168fc19d78f9efc5522d27968,,case0:ok;case1:ok;case2:info[q=0]&info[X=0]&ok;case3:info[q=0]&bad[r=0];case4:ok;case5:ok;case6:info[q=0]&info[X=0]&ok;case7:info[q=0]&bad[r=0]
+c25172fc3f29b6fc4a1155b8575233155486b27464b74b8b260b499a3f53cb14,1ea9cbdb35cf6e0329aa31b0bb0a702a65123ed008655a93b7dcd5280e52e1ab,,,7422edc7843136af0053bb8854448a8299994f9ddcefd3a9a92d45462c59298a,78c7774a266f8b97ea23d05d064f033c77319f923f6b78bce4e20bf05fa5398d,,,8bdd12387bcec950ffac4477abbb757d6666b06223102c5656d2bab8d3a6d2a5,873888b5d990746815dc2fa2f9b0fcc388ce606dc09487431b1df40ea05ac2a2,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:ok;case3:ok;case4:bad[non_square(s)];case5:bad[non_square(s)];case6:ok;case7:ok
+cab6626f832a4b1280ba7add2fc5322ff011caededf7ff4db6735d5026dc0367,2b2bef0852c6f7c95d72ac99a23802b875029cd573b248d1f1b3fc8033788eb6,,,,,,,,,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:info[v=0]&bad[non_square(s)];case3:bad[non_square(s)];case4:bad[non_square(s)];case5:bad[non_square(s)];case6:info[v=0]&bad[non_square(s)];case7:bad[non_square(s)]
+d8621b4ffc85b9ed56e99d8dd1dd24aedcecb14763b861a17112dc771a104fd2,812cabe972a22aa67c7da0c94d8a936296eb9949d70c37cb2b2487574cb3ce58,fbc5febc6fdbc9ae3eb88a93b982196e8b6275a6d5a73c17387e000c711bd0e3,8724c96bd4e5527f2dd195a51c468d2d211ba2fac7cbe0b4b3434253409fb42d,,,043a014390243651c147756c467de691749d8a592a58c3e8c781fff28ee42b4c,78db36942b1aad80d22e6a5ae3b972d2dee45d0538341f4b4cbcbdabbf604802,,,case0:ok;case1:ok;case2:bad[non_square(s)];case3:bad[non_square(s)];case4:ok;case5:ok;case6:bad[non_square(s)];case7:bad[non_square(s)]
+da463164c6f4bf7129ee5f0ec00f65a675a8adf1bd931b39b64806afdcda9a22,25b9ce9b390b408ed611a0f13ff09a598a57520e426ce4c649b7f94f2325620d,,,,,,,,,case0:bad[non_square(s)];case1:info[v=0]&bad[non_square(s)];case2:bad[non_square(s)];case3:bad[non_square(s)];case4:bad[non_square(s)];case5:info[v=0]&bad[non_square(s)];case6:bad[non_square(s)];case7:bad[non_square(s)]
+dafc971e4a3a7b6dcfb42a08d9692d82ad9e7838523fcbda1d4827e14481ae2d,250368e1b5c58492304bd5f72696d27d526187c7adc03425e2b7d81dbb7e4e02,,,370c28f1be665efacde6aa436bf86fe21e6e314c1e53dd040e6c73a46b4c8c49,cd8acee98ffe56531a84d7eb3e48fa4034206ce825ace907d0edf0eaeb5e9ca2,,,c8f3d70e4199a105321955bc9407901de191ceb3e1ac22fbf1938c5a94b36fe6,327531167001a9ace57b2814c1b705bfcbdf9317da5316f82f120f1414a15f8d,case0:bad[non_square(s)];case1:info[v=0]&bad[non_square(s)];case2:ok;case3:ok;case4:bad[non_square(s)];case5:info[v=0]&bad[non_square(s)];case6:ok;case7:ok
+e0294c8bc1a36b4166ee92bfa70a5c34976fa9829405efea8f9cd54dcb29b99e,ae9690d13b8d20a0fbbf37bed8474f67a04e142f56efd78770a76b359165d8a1,,,dcd45d935613916af167b029058ba3a700d37150b9df34728cb05412c16d4182,,,,232ba26ca9ec6e950e984fd6fa745c58ff2c8eaf4620cb8d734fabec3e92baad,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:info[q=0]&info[X=0]&ok;case3:info[q=0]&bad[r=0];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:info[q=0]&info[X=0]&ok;case7:info[q=0]&bad[r=0]
+e148441cd7b92b8b0e4fa3bd68712cfd0d709ad198cace611493c10e97f5394e,164a639794d74c53afc4d3294e79cdb3cd25f99f6df45c000f758aba54d699c0,,,,,,,,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:bad[non_square(s)];case3:info[v=0]&bad[non_square(s)];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:bad[non_square(s)];case7:info[v=0]&bad[non_square(s)]
+e4b00ec97aadcca97644d3b0c8a931b14ce7bcf7bc8779546d6e35aa5937381c,94e9588d41647b3fcc772dc8d83c67ce3be003538517c834103d2cd49d62ef4d,c88d25f41407376bb2c03a7fffeb3ec7811cc43491a0c3aac0378cdc78357bee,51c02636ce00c2345ecd89adb6089fe4d5e18ac924e3145e6669501cd37a00d4,205b3512db40521cb200952e67b46f67e09e7839e0de44004138329ebd9138c5,58aab390ab6fb55c1d1b80897a207ce94a78fa5b4aa61a33398bcae9adb20d3e,3772da0bebf8c8944d3fc5800014c1387ee33bcb6e5f3c553fc8732287ca8041,ae3fd9c931ff3dcba132765249f7601b2a1e7536db1ceba19996afe22c85fb5b,dfa4caed24bfade34dff6ad1984b90981f6187c61f21bbffbec7cd60426ec36a,a7554c6f54904aa3e2e47f7685df8316b58705a4b559e5ccc6743515524deef1,case0:ok;case1:ok;case2:ok;case3:info[v=0]&ok;case4:ok;case5:ok;case6:ok;case7:info[v=0]&ok
+e5bbb9ef360d0a501618f0067d36dceb75f5be9a620232aa9fd5139d0863fde5,e5bbb9ef360d0a501618f0067d36dceb75f5be9a620232aa9fd5139d0863fde5,,,,,,,,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:bad[s=0];case3:bad[s=0];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:bad[s=0];case7:bad[s=0]
+e6bcb5c3d63467d490bfa54fbbc6092a7248c25e11b248dc2964a6e15edb1457,19434a3c29cb982b6f405ab04439f6d58db73da1ee4db723d69b591da124e7d8,67119877832ab8f459a821656d8261f544a553b89ae4f25c52a97134b70f3426,ffee02f5e649c07f0560eff1867ec7b32d0e595e9b1c0ea6e2a4fc70c97cd71f,b5e0c189eb5b4bacd025b7444d74178be8d5246cfa4a9a207964a057ee969992,5746e4591bf7f4c3044609ea372e908603975d279fdef8349f0b08d32f07619d,98ee67887cd5470ba657de9a927d9e0abb5aac47651b0da3ad568eca48f0c809,0011fd0a19b63f80fa9f100e7981384cd2f1a6a164e3f1591d5b038e36832510,4a1f3e7614a4b4532fda48bbb28be874172adb9305b565df869b5fa71169629d,a8b91ba6e4080b3cfbb9f615c8d16f79fc68a2d8602107cb60f4f72bd0f89a92,case0:ok;case1:info[v=0]&ok;case2:ok;case3:ok;case4:ok;case5:info[v=0]&ok;case6:ok;case7:ok
+f28fba64af766845eb2f4302456e2b9f8d80affe57e7aae42738d7cddb1c2ce6,f28fba64af766845eb2f4302456e2b9f8d80affe57e7aae42738d7cddb1c2ce6,4f867ad8bb3d840409d26b67307e62100153273f72fa4b7484becfa14ebe7408,5bbc4f59e452cc5f22a99144b10ce8989a89a995ec3cea1c91ae10e8f721bb5d,,,b079852744c27bfbf62d9498cf819deffeacd8c08d05b48b7b41305db1418827,a443b0a61bad33a0dd566ebb4ef317676576566a13c315e36e51ef1608de40d2,,,case0:ok;case1:ok;case2:bad[s=0];case3:bad[s=0];case4:ok;case5:ok;case6:bad[s=0];case7:bad[s=0]
+f455605bc85bf48e3a908c31023faf98381504c6c6d3aeb9ede55f8dd528924d,d31fbcd5cdb798f6c00db6692f8fe8967fa9c79dd10958f4a194f01374905e99,,,0c00c5715b56fe632d814ad8a77f8e66628ea47a6116834f8c1218f3a03cbd50,df88e44fac84fa52df4d59f48819f18f6a8cd4151d162afaf773166f57c7ff46,,,f3ff3a8ea4a9019cd27eb527588071999d715b859ee97cb073ede70b5fc33edf,20771bb0537b05ad20b2a60b77e60e7095732beae2e9d505088ce98fa837fce9,case0:bad[non_square(s)];case1:bad[non_square(s)];case2:info[v=0]&ok;case3:ok;case4:bad[non_square(s)];case5:bad[non_square(s)];case6:info[v=0]&ok;case7:ok
+f58cd4d9830bad322699035e8246007d4be27e19b6f53621317b4f309b3daa9d,78ec2b3dc0948de560148bbc7c6dc9633ad5df70a5a5750cbed721804f082a3b,6c4c580b76c7594043569f9dae16dc2801c16a1fbe12860881b75f8ef929bce5,94231355e7385c5f25ca436aa64191471aea4393d6e86ab7a35fe2afacaefd0d,dff2a1951ada6db574df834048149da3397a75b829abf58c7e69db1b41ac0989,a52b66d3c907035548028bf804711bf422aba95f1a666fc86f4648e05f29caae,93b3a7f48938a6bfbca9606251e923d7fe3e95e041ed79f77e48a07006d63f4a,6bdcecaa18c7a3a0da35bc9559be6eb8e515bc6c291795485ca01d4f5350ff22,200d5e6ae525924a8b207cbfb7eb625cc6858a47d6540a73819624e3be53f2a6,5ad4992c36f8fcaab7fd7407fb8ee40bdd5456a0e599903790b9b71ea0d63181,case0:ok;case1:ok;case2:info[v=0]&ok;case3:ok;case4:ok;case5:ok;case6:info[v=0]&ok;case7:ok
+fd7d912a40f182a3588800d69ebfb5048766da206fd7ebc8d2436c81cbef6421,8d37c862054debe731694536ff46b273ec122b35a9bf1445ac3c4ff9f262c952,,,,,,,,,case0:bad[valid_x(-x-u)];case1:bad[valid_x(-x-u)];case2:info[v=0]&bad[non_square(s)];case3:bad[non_square(s)];case4:bad[valid_x(-x-u)];case5:bad[valid_x(-x-u)];case6:info[v=0]&bad[non_square(s)];case7:bad[non_square(s)]
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index e5df29b135..9762476a5d 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -74,12 +74,12 @@ TEST_EXIT_SKIPPED = 77 TEST_FRAMEWORK_MODULES = [ "address", "blocktools", - "muhash", + "ellswift", "key", + "muhash", "ripemd160", "script", "segwit_addr", - "util", ] EXTENDED_SCRIPTS = [ diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index 95999649b4..327dd43e5a 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -173,12 +173,12 @@ class ToolWalletTest(BitcoinTestFramework): if file_format is not None and file_format != dump_data["format"]: load_output += "Warning: Dumpfile wallet format \"{}\" does not match command line specified format \"{}\".\n".format(dump_data["format"], file_format) self.assert_tool_output(load_output, *args) - assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", wallet_name)) + assert (self.nodes[0].wallets_path / wallet_name).is_dir() self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", '-wallet={}'.format(wallet_name), '-dumpfile={}'.format(rt_dumppath), 'dump') rt_dump_data = self.read_dump(rt_dumppath) - wallet_dat = os.path.join(self.nodes[0].datadir, "regtest/wallets/", wallet_name, "wallet.dat") + wallet_dat = self.nodes[0].wallets_path / wallet_name / "wallet.dat" if rt_dump_data["format"] == "bdb": self.assert_is_bdb(wallet_dat) else: @@ -193,7 +193,7 @@ class ToolWalletTest(BitcoinTestFramework): self.assert_raises_tool_error('Error parsing command line arguments: Invalid parameter -foo', '-foo') self.assert_raises_tool_error('No method provided. Run `bitcoin-wallet -help` for valid methods.') self.assert_raises_tool_error('Wallet name must be provided when creating a new wallet.', 'create') - locked_dir = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets") + locked_dir = self.nodes[0].wallets_path error = 'Error initializing wallet database environment "{}"!'.format(locked_dir) if self.options.descriptors: error = f"SQLiteDatabase: Unable to obtain an exclusive lock on the database, is it being used by another instance of {self.config['environment']['PACKAGE_NAME']}?" @@ -202,7 +202,7 @@ class ToolWalletTest(BitcoinTestFramework): '-wallet=' + self.default_wallet_name, 'info', ) - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "nonexistent.dat") + path = self.nodes[0].wallets_path / "nonexistent.dat" self.assert_raises_tool_error("Failed to load database path '{}'. Path does not exist.".format(path), '-wallet=nonexistent.dat', 'info') def test_tool_wallet_info(self): @@ -347,7 +347,7 @@ class ToolWalletTest(BitcoinTestFramework): non_exist_dump = os.path.join(self.nodes[0].datadir, "wallet.nodump") self.assert_raises_tool_error('Unknown wallet file format "notaformat" provided. Please provide one of "bdb" or "sqlite".', '-wallet=todump', '-format=notaformat', '-dumpfile={}'.format(wallet_dump), 'createfromdump') self.assert_raises_tool_error('Dump file {} does not exist.'.format(non_exist_dump), '-wallet=todump', '-dumpfile={}'.format(non_exist_dump), 'createfromdump') - wallet_path = os.path.join(self.nodes[0].datadir, 'regtest', 'wallets', 'todump2') + wallet_path = self.nodes[0].wallets_path / "todump2" self.assert_raises_tool_error('Failed to create database path \'{}\'. Database already exists.'.format(wallet_path), '-wallet=todump2', '-dumpfile={}'.format(wallet_dump), 'createfromdump') self.assert_raises_tool_error("The -descriptors option can only be used with the 'create' command.", '-descriptors', '-wallet=todump2', '-dumpfile={}'.format(wallet_dump), 'createfromdump') @@ -363,18 +363,18 @@ class ToolWalletTest(BitcoinTestFramework): dump_data["BITCOIN_CORE_WALLET_DUMP"] = "0" self.write_dump(dump_data, bad_ver_wallet_dump) self.assert_raises_tool_error('Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version 0', '-wallet=badload', '-dumpfile={}'.format(bad_ver_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() bad_ver_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_ver2.dump") dump_data["BITCOIN_CORE_WALLET_DUMP"] = "2" self.write_dump(dump_data, bad_ver_wallet_dump) self.assert_raises_tool_error('Error: Dumpfile version is not supported. This version of bitcoin-wallet only supports version 1 dumpfiles. Got dumpfile with version 2', '-wallet=badload', '-dumpfile={}'.format(bad_ver_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() bad_magic_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_magic.dump") del dump_data["BITCOIN_CORE_WALLET_DUMP"] dump_data["not_the_right_magic"] = "1" self.write_dump(dump_data, bad_magic_wallet_dump, "not_the_right_magic") self.assert_raises_tool_error('Error: Dumpfile identifier record is incorrect. Got "not_the_right_magic", expected "BITCOIN_CORE_WALLET_DUMP".', '-wallet=badload', '-dumpfile={}'.format(bad_magic_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() self.log.info('Checking createfromdump handling of checksums') bad_sum_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_sum1.dump") @@ -383,25 +383,25 @@ class ToolWalletTest(BitcoinTestFramework): dump_data["checksum"] = "1" * 64 self.write_dump(dump_data, bad_sum_wallet_dump) self.assert_raises_tool_error('Error: Dumpfile checksum does not match. Computed {}, expected {}'.format(checksum, "1" * 64), '-wallet=bad', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() bad_sum_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_sum2.dump") del dump_data["checksum"] self.write_dump(dump_data, bad_sum_wallet_dump, skip_checksum=True) self.assert_raises_tool_error('Error: Missing checksum', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() bad_sum_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_sum3.dump") dump_data["checksum"] = "2" * 10 self.write_dump(dump_data, bad_sum_wallet_dump) self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() dump_data["checksum"] = "3" * 66 self.write_dump(dump_data, bad_sum_wallet_dump) self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') - assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + assert not (self.nodes[0].wallets_path / "badload").is_dir() def run_test(self): - self.wallet_path = os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename) + self.wallet_path = os.path.join(self.nodes[0].wallets_path, self.default_wallet_name, self.wallet_data_filename) self.test_invalid_tool_commands_and_args() # Warning: The following tests are order-dependent. self.test_tool_wallet_info() diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index 4ad25d964e..fa92ebd436 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -109,16 +109,16 @@ class WalletBackupTest(BitcoinTestFramework): self.stop_node(2) def erase_three(self): - os.remove(os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)) - os.remove(os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)) - os.remove(os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)) + os.remove(os.path.join(self.nodes[0].wallets_path, self.default_wallet_name, self.wallet_data_filename)) + os.remove(os.path.join(self.nodes[1].wallets_path, self.default_wallet_name, self.wallet_data_filename)) + os.remove(os.path.join(self.nodes[2].wallets_path, self.default_wallet_name, self.wallet_data_filename)) def restore_invalid_wallet(self): node = self.nodes[3] invalid_wallet_file = os.path.join(self.nodes[0].datadir, 'invalid_wallet_file.bak') open(invalid_wallet_file, 'a', encoding="utf8").write('invald wallet') wallet_name = "res0" - not_created_wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + not_created_wallet_file = os.path.join(node.wallets_path, wallet_name) error_message = "Wallet file verification failed. Failed to load database path '{}'. Data is not in recognized format.".format(not_created_wallet_file) assert_raises_rpc_error(-18, error_message, node.restorewallet, wallet_name, invalid_wallet_file) assert not os.path.exists(not_created_wallet_file) @@ -128,14 +128,14 @@ class WalletBackupTest(BitcoinTestFramework): nonexistent_wallet_file = os.path.join(self.nodes[0].datadir, 'nonexistent_wallet.bak') wallet_name = "res0" assert_raises_rpc_error(-8, "Backup file does not exist", node.restorewallet, wallet_name, nonexistent_wallet_file) - not_created_wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + not_created_wallet_file = os.path.join(node.wallets_path, wallet_name) assert not os.path.exists(not_created_wallet_file) def restore_wallet_existent_name(self): node = self.nodes[3] backup_file = os.path.join(self.nodes[0].datadir, 'wallet.bak') wallet_name = "res0" - wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + wallet_file = os.path.join(node.wallets_path, wallet_name) error_message = "Failed to create database path '{}'. Database already exists.".format(wallet_file) assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file) assert os.path.exists(wallet_file) @@ -206,9 +206,9 @@ class WalletBackupTest(BitcoinTestFramework): self.nodes[3].restorewallet("res1", backup_file_1) self.nodes[3].restorewallet("res2", backup_file_2) - assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res0")) - assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res1")) - assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res2")) + assert os.path.exists(os.path.join(self.nodes[3].wallets_path, "res0")) + assert os.path.exists(os.path.join(self.nodes[3].wallets_path, "res1")) + assert os.path.exists(os.path.join(self.nodes[3].wallets_path, "res2")) res0_rpc = self.nodes[3].get_wallet_rpc("res0") res1_rpc = self.nodes[3].get_wallet_rpc("res1") @@ -226,8 +226,8 @@ class WalletBackupTest(BitcoinTestFramework): self.erase_three() #start node2 with no chain - shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'blocks')) - shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, 'chainstate')) + shutil.rmtree(os.path.join(self.nodes[2].chain_path, 'blocks')) + shutil.rmtree(os.path.join(self.nodes[2].chain_path, 'chainstate')) self.start_three(["-nowallet"]) self.init_three() @@ -248,10 +248,10 @@ class WalletBackupTest(BitcoinTestFramework): # Backup to source wallet file must fail sourcePaths = [ - os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename), - os.path.join(self.nodes[0].datadir, self.chain, '.', 'wallets', self.default_wallet_name, self.wallet_data_filename), - os.path.join(self.nodes[0].datadir, self.chain, 'wallets', self.default_wallet_name), - os.path.join(self.nodes[0].datadir, self.chain, 'wallets')] + os.path.join(self.nodes[0].wallets_path, self.default_wallet_name, self.wallet_data_filename), + os.path.join(self.nodes[0].wallets_path, '.', self.default_wallet_name, self.wallet_data_filename), + os.path.join(self.nodes[0].wallets_path, self.default_wallet_name), + os.path.join(self.nodes[0].wallets_path)] for sourcePath in sourcePaths: assert_raises_rpc_error(-4, "backup failed", self.nodes[0].backupwallet, sourcePath) diff --git a/test/functional/wallet_backwards_compatibility.py b/test/functional/wallet_backwards_compatibility.py index 5088e11eda..49e36b21c5 100755 --- a/test/functional/wallet_backwards_compatibility.py +++ b/test/functional/wallet_backwards_compatibility.py @@ -74,8 +74,8 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): def nodes_wallet_dir(self, node): if node.version < 170000: - return os.path.join(node.datadir, "regtest") - return os.path.join(node.datadir, "regtest/wallets") + return node.chain_path + return node.wallets_path def run_test(self): node_miner = self.nodes[0] @@ -157,10 +157,10 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): assert info['keypoolsize'] == 0 # Unload wallets and copy to older nodes: - node_master_wallets_dir = os.path.join(node_master.datadir, "regtest/wallets") - node_v19_wallets_dir = os.path.join(node_v19.datadir, "regtest/wallets") - node_v17_wallets_dir = os.path.join(node_v17.datadir, "regtest/wallets") - node_v16_wallets_dir = os.path.join(node_v16.datadir, "regtest") + node_master_wallets_dir = node_master.wallets_path + node_v19_wallets_dir = node_v19.wallets_path + node_v17_wallets_dir = node_v17.wallets_path + node_v16_wallets_dir = node_v16.chain_path node_master.unloadwallet("w1") node_master.unloadwallet("w2") node_master.unloadwallet("w3") @@ -264,10 +264,11 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): os.path.join(node_master_wallets_dir, "u1_v16") ) load_res = node_master.loadwallet("u1_v16") - # Make sure this wallet opens without warnings. See https://github.com/bitcoin/bitcoin/pull/19054 + # Make sure this wallet opens with only the migration warning. See https://github.com/bitcoin/bitcoin/pull/19054 if int(node_master.getnetworkinfo()["version"]) >= 249900: # loadwallet#warnings (added in v25) -- only present if there is a warning - assert "warnings" not in load_res + # Legacy wallets will have only a deprecation warning + assert_equal(load_res["warnings"], ["Wallet loaded successfully. The legacy wallet type is being deprecated and support for creating and opening legacy wallets will be removed in the future. Legacy wallets can be migrated to a descriptor wallet with migratewallet."]) else: # loadwallet#warning (deprecated in v25) -- always present, but empty string if no warning assert_equal(load_res["warning"], '') diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index 4673eb091c..f4b67bae1b 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -234,7 +234,7 @@ class WalletDescriptorTest(BitcoinTestFramework): self.log.info("Test that loading descriptor wallet containing legacy key types throws error") self.nodes[0].createwallet(wallet_name="crashme", descriptors=True) self.nodes[0].unloadwallet("crashme") - wallet_db = os.path.join(self.nodes[0].datadir, self.chain, "wallets", "crashme", self.wallet_data_filename) + wallet_db = os.path.join(self.nodes[0].wallets_path, "crashme", self.wallet_data_filename) with sqlite3.connect(wallet_db) as conn: # add "cscript" entry: key type is uint160 (20 bytes), value type is CScript (zero-length here) conn.execute('INSERT INTO main VALUES(?, ?)', (b'\x07cscript' + b'\x00'*20, b'\x00')) diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 8f84d8ed60..0fb0d7ea97 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -87,11 +87,11 @@ class WalletHDTest(BitcoinTestFramework): self.stop_node(1) # we need to delete the complete chain directory # otherwise node1 would auto-recover all funds in flag the keypool keys as used - shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) - shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) + shutil.rmtree(os.path.join(self.nodes[1].chain_path, "blocks")) + shutil.rmtree(os.path.join(self.nodes[1].chain_path, "chainstate")) shutil.copyfile( os.path.join(self.nodes[1].datadir, "hd.bak"), - os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename), + os.path.join(self.nodes[1].wallets_path, self.default_wallet_name, self.wallet_data_filename), ) self.start_node(1) @@ -115,11 +115,11 @@ class WalletHDTest(BitcoinTestFramework): # Try a RPC based rescan self.stop_node(1) - shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) - shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) + shutil.rmtree(os.path.join(self.nodes[1].chain_path, "blocks")) + shutil.rmtree(os.path.join(self.nodes[1].chain_path, "chainstate")) shutil.copyfile( os.path.join(self.nodes[1].datadir, "hd.bak"), - os.path.join(self.nodes[1].datadir, self.chain, "wallets", self.default_wallet_name, self.wallet_data_filename), + os.path.join(self.nodes[1].wallets_path, self.default_wallet_name, self.wallet_data_filename), ) self.start_node(1, extra_args=self.extra_args[1]) self.connect_nodes(0, 1) diff --git a/test/functional/wallet_inactive_hdchains.py b/test/functional/wallet_inactive_hdchains.py index c0b3fea1c0..c6d22ab90b 100755 --- a/test/functional/wallet_inactive_hdchains.py +++ b/test/functional/wallet_inactive_hdchains.py @@ -5,7 +5,6 @@ """ Test Inactive HD Chains. """ -import os import shutil import time @@ -130,8 +129,8 @@ class InactiveHDChainsTest(BitcoinTestFramework): # Copy test wallet to node 0 test_wallet.unloadwallet() - test_wallet_dir = os.path.join(self.nodes[1].datadir, "regtest/wallets/keymeta_test") - new_test_wallet_dir = os.path.join(self.nodes[0].datadir, "regtest/wallets/keymeta_test") + test_wallet_dir = self.nodes[1].wallets_path / "keymeta_test" + new_test_wallet_dir = self.nodes[0].wallets_path / "keymeta_test" shutil.copytree(test_wallet_dir, new_test_wallet_dir) self.nodes[0].loadwallet("keymeta_test") test_wallet = self.nodes[0].get_wallet_rpc("keymeta_test") diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index f1458bb374..0f1c33a0c2 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -33,7 +33,7 @@ class KeypoolRestoreTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): - wallet_path = os.path.join(self.nodes[1].datadir, self.chain, "wallets", self.default_wallet_name, self.wallet_data_filename) + wallet_path = os.path.join(self.nodes[1].wallets_path, self.default_wallet_name, self.wallet_data_filename) wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak") self.generate(self.nodes[0], COINBASE_MATURITY + 1) diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index a44c129c87..18bb8a0cd8 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -234,8 +234,8 @@ class ListTransactionsTest(BitcoinTestFramework): # refill keypool otherwise the second node wouldn't recognize addresses generated on the first nodes self.nodes[0].keypoolrefill(1000) self.stop_nodes() - wallet0 = os.path.join(self.nodes[0].datadir, self.chain, self.default_wallet_name, "wallet.dat") - wallet2 = os.path.join(self.nodes[2].datadir, self.chain, self.default_wallet_name, "wallet.dat") + wallet0 = os.path.join(self.nodes[0].chain_path, self.default_wallet_name, "wallet.dat") + wallet2 = os.path.join(self.nodes[2].chain_path, self.default_wallet_name, "wallet.dat") shutil.copyfile(wallet0, wallet2) self.start_nodes() # reconnect nodes diff --git a/test/functional/wallet_migration.py b/test/functional/wallet_migration.py index 320f5dd9df..278ef1ec87 100755 --- a/test/functional/wallet_migration.py +++ b/test/functional/wallet_migration.py @@ -4,7 +4,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test Migrating a wallet from legacy to descriptor.""" -import os import random import shutil from test_framework.descriptors import descsum_create @@ -35,7 +34,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.skip_if_no_bdb() def assert_is_sqlite(self, wallet_name): - wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest/wallets", wallet_name, self.wallet_data_filename) + wallet_file_path = self.nodes[0].wallets_path / wallet_name / self.wallet_data_filename with open(wallet_file_path, 'rb') as f: file_magic = f.read(16) assert_equal(file_magic, b'SQLite format 3\x00') @@ -68,6 +67,15 @@ class WalletMigrationTest(BitcoinTestFramework): del d["parent_descs"] assert_equal(received_list_txs, expected_list_txs) + def check_address(self, wallet, addr, is_mine, is_change, label): + addr_info = wallet.getaddressinfo(addr) + assert_equal(addr_info['ismine'], is_mine) + assert_equal(addr_info['ischange'], is_change) + if label is not None: + assert_equal(addr_info['labels'], [label]), + else: + assert_equal(addr_info['labels'], []), + def test_basic(self): default = self.nodes[0].get_wallet_rpc(self.default_wallet_name) @@ -458,11 +466,11 @@ class WalletMigrationTest(BitcoinTestFramework): wallet.unloadwallet() - wallet_file_path = os.path.join(self.nodes[0].datadir, "regtest", "wallets", "notloaded2") + wallet_file_path = self.nodes[0].wallets_path / "notloaded2" self.nodes[0].migratewallet(wallet_file_path) # Because we gave the name by full path, the loaded wallet's name is that path too. - wallet = self.nodes[0].get_wallet_rpc(wallet_file_path) + wallet = self.nodes[0].get_wallet_rpc(str(wallet_file_path)) info = wallet.getwalletinfo() assert_equal(info["descriptors"], True) @@ -485,12 +493,12 @@ class WalletMigrationTest(BitcoinTestFramework): wallet = self.create_legacy_wallet("plainfile") wallet.unloadwallet() - wallets_dir = os.path.join(self.nodes[0].datadir, "regtest", "wallets") - wallet_path = os.path.join(wallets_dir, "plainfile") - wallet_dat_path = os.path.join(wallet_path, "wallet.dat") - shutil.copyfile(wallet_dat_path, os.path.join(wallets_dir, "plainfile.bak")) + wallets_dir = self.nodes[0].wallets_path + wallet_path = wallets_dir / "plainfile" + wallet_dat_path = wallet_path / "wallet.dat" + shutil.copyfile(wallet_dat_path, wallets_dir / "plainfile.bak") shutil.rmtree(wallet_path) - shutil.move(os.path.join(wallets_dir, "plainfile.bak"), wallet_path) + shutil.move(wallets_dir / "plainfile.bak", wallet_path) self.nodes[0].loadwallet("plainfile") info = wallet.getwalletinfo() @@ -502,8 +510,134 @@ class WalletMigrationTest(BitcoinTestFramework): assert_equal(info["descriptors"], True) assert_equal(info["format"], "sqlite") - assert os.path.isdir(wallet_path) - assert os.path.isfile(wallet_dat_path) + assert wallet_path.is_dir() + assert wallet_dat_path.is_file() + + def test_addressbook(self): + df_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + + self.log.info("Test migration of address book data") + wallet = self.create_legacy_wallet("legacy_addrbook") + df_wallet.sendtoaddress(wallet.getnewaddress(), 3) + + # Import watch-only script to create a watch-only wallet after migration + watch_addr = df_wallet.getnewaddress() + wallet.importaddress(watch_addr) + df_wallet.sendtoaddress(watch_addr, 2) + + # Import solvable script + multi_addr1 = wallet.getnewaddress() + multi_addr2 = wallet.getnewaddress() + multi_addr3 = df_wallet.getnewaddress() + wallet.importpubkey(df_wallet.getaddressinfo(multi_addr3)["pubkey"]) + ms_addr_info = wallet.addmultisigaddress(2, [multi_addr1, multi_addr2, multi_addr3]) + + self.generate(self.nodes[0], 1) + + # Test vectors + addr_external = { + "addr": df_wallet.getnewaddress(), + "is_mine": False, + "is_change": False, + "label": "" + } + addr_external_with_label = { + "addr": df_wallet.getnewaddress(), + "is_mine": False, + "is_change": False, + "label": "external" + } + addr_internal = { + "addr": wallet.getnewaddress(), + "is_mine": True, + "is_change": False, + "label": "" + } + addr_internal_with_label = { + "addr": wallet.getnewaddress(), + "is_mine": True, + "is_change": False, + "label": "internal" + } + change_address = { + "addr": wallet.getrawchangeaddress(), + "is_mine": True, + "is_change": True, + "label": None + } + watch_only_addr = { + "addr": watch_addr, + "is_mine": False, + "is_change": False, + "label": "imported" + } + ms_addr = { + "addr": ms_addr_info['address'], + "is_mine": False, + "is_change": False, + "label": "multisig" + } + + # To store the change address in the addressbook need to send coins to it + wallet.send(outputs=[{wallet.getnewaddress(): 2}], options={"change_address": change_address['addr']}) + self.generate(self.nodes[0], 1) + + # Util wrapper func for 'addr_info' + def check(info, node): + self.check_address(node, info['addr'], info['is_mine'], info['is_change'], info["label"]) + + # Pre-migration: set label and perform initial checks + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, watch_only_addr, ms_addr]: + if not addr_info['is_change']: + wallet.setlabel(addr_info['addr'], addr_info["label"]) + check(addr_info, wallet) + + # Migrate wallet + info_migration = wallet.migratewallet() + wallet_wo = self.nodes[0].get_wallet_rpc(info_migration["watchonly_name"]) + wallet_solvables = self.nodes[0].get_wallet_rpc(info_migration["solvables_name"]) + + ######################### + # Post migration checks # + ######################### + + # First check the main wallet + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]: + check(addr_info, wallet) + + # Watch-only wallet will contain the watch-only entry (with 'is_mine=True') and all external addresses ('send') + self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"]) + for addr_info in [addr_external, addr_external_with_label, ms_addr]: + check(addr_info, wallet_wo) + + # Solvables wallet will contain the multisig entry (with 'is_mine=True') and all external addresses ('send') + self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"]) + for addr_info in [addr_external, addr_external_with_label]: + check(addr_info, wallet_solvables) + + ######################################################################################## + # Now restart migrated wallets and verify that the addressbook entries are still there # + ######################################################################################## + + # First the main wallet + self.nodes[0].unloadwallet("legacy_addrbook") + self.nodes[0].loadwallet("legacy_addrbook") + for addr_info in [addr_external, addr_external_with_label, addr_internal, addr_internal_with_label, change_address, ms_addr]: + check(addr_info, wallet) + + # Watch-only wallet + self.nodes[0].unloadwallet(info_migration["watchonly_name"]) + self.nodes[0].loadwallet(info_migration["watchonly_name"]) + self.check_address(wallet_wo, watch_only_addr['addr'], is_mine=True, is_change=watch_only_addr['is_change'], label=watch_only_addr["label"]) + for addr_info in [addr_external, addr_external_with_label, ms_addr]: + check(addr_info, wallet_wo) + + # Solvables wallet + self.nodes[0].unloadwallet(info_migration["solvables_name"]) + self.nodes[0].loadwallet(info_migration["solvables_name"]) + self.check_address(wallet_solvables, ms_addr['addr'], is_mine=True, is_change=ms_addr['is_change'], label=ms_addr["label"]) + for addr_info in [addr_external, addr_external_with_label]: + check(addr_info, wallet_solvables) def run_test(self): self.generate(self.nodes[0], 101) @@ -519,6 +653,7 @@ class WalletMigrationTest(BitcoinTestFramework): self.test_unloaded_by_path() self.test_default_wallet() self.test_direct_file() + self.test_addressbook() if __name__ == '__main__': WalletMigrationTest().main() diff --git a/test/functional/wallet_miniscript.py b/test/functional/wallet_miniscript.py index 7bc3424bf4..45f0df1c76 100755 --- a/test/functional/wallet_miniscript.py +++ b/test/functional/wallet_miniscript.py @@ -277,6 +277,18 @@ class WalletMiniscriptTest(BitcoinTestFramework): assert not res["success"] assert "is not sane: witnesses without signature exist" in res["error"]["message"] + # Sanity check we wouldn't let an unspendable Miniscript descriptor in + res = self.ms_wo_wallet.importdescriptors( + [ + { + "desc": descsum_create("wsh(0)"), + "active": False, + "timestamp": "now", + } + ] + )[0] + assert not res["success"] and "is not satisfiable" in res["error"]["message"] + # Test we can track any type of Miniscript for ms in MINISCRIPTS: self.watchonly_test(ms) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 2faf6cad8b..10bc516d8f 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -62,7 +62,7 @@ class MultiWalletTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] - data_dir = lambda *p: os.path.join(node.datadir, self.chain, *p) + data_dir = lambda *p: os.path.join(node.chain_path, *p) wallet_dir = lambda *p: data_dir('wallets', *p) wallet = lambda name: node.get_wallet_rpc(name) @@ -299,7 +299,7 @@ class MultiWalletTest(BitcoinTestFramework): assert_equal(set(self.nodes[0].listwallets()), set(wallet_names)) # Fail to load if wallet doesn't exist - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallets") + path = wallet_dir("wallets") assert_raises_rpc_error(-18, "Wallet file verification failed. Failed to load database path '{}'. Path does not exist.".format(path), self.nodes[0].loadwallet, 'wallets') # Fail to load duplicate wallets @@ -307,7 +307,7 @@ class MultiWalletTest(BitcoinTestFramework): if not self.options.descriptors: # This tests the default wallet that BDB makes, so SQLite wallet doesn't need to test this # Fail to load duplicate wallets by different ways (directory and filepath) - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "wallet.dat") + path = wallet_dir("wallet.dat") assert_raises_rpc_error(-35, "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(path), self.nodes[0].loadwallet, 'wallet.dat') # Only BDB doesn't open duplicate wallet files. SQLite does not have this limitation. While this may be desired in the future, it is not necessary @@ -322,13 +322,13 @@ class MultiWalletTest(BitcoinTestFramework): # Fail to load if a directory is specified that doesn't contain a wallet os.mkdir(wallet_dir('empty_wallet_dir')) - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "empty_wallet_dir") + path = wallet_dir("empty_wallet_dir") assert_raises_rpc_error(-18, "Wallet file verification failed. Failed to load database path '{}'. Data is not in recognized format.".format(path), self.nodes[0].loadwallet, 'empty_wallet_dir') self.log.info("Test dynamic wallet creation.") # Fail to create a wallet if it already exists. - path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w2") + path = wallet_dir("w2") assert_raises_rpc_error(-4, "Failed to create database path '{}'. Database already exists.".format(path), self.nodes[0].createwallet, 'w2') # Successfully create a wallet with a new name diff --git a/test/functional/wallet_pruning.py b/test/functional/wallet_pruning.py index 1ceceaee93..9e6061287c 100755 --- a/test/functional/wallet_pruning.py +++ b/test/functional/wallet_pruning.py @@ -106,7 +106,7 @@ class WalletPruningTest(BitcoinTestFramework): def has_block(self, block_index): """Checks if the pruned node has the specific blk0000*.dat file""" - return os.path.isfile(os.path.join(self.nodes[1].datadir, self.chain, "blocks", f"blk{block_index:05}.dat")) + return os.path.isfile(os.path.join(self.nodes[1].chain_path, "blocks", f"blk{block_index:05}.dat")) def create_wallet(self, wallet_name, *, unload=False): """Creates and dumps a wallet on the non-pruned node0 to be later import by the pruned node""" diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py index 1c79c6816c..af01b9439f 100755 --- a/test/functional/wallet_reorgsrestore.py +++ b/test/functional/wallet_reorgsrestore.py @@ -89,7 +89,7 @@ class ReorgsRestoreTest(BitcoinTestFramework): # Node0 wallet file is loaded on longest sync'ed node1 self.stop_node(1) self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak')) - shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, self.chain, self.default_wallet_name, self.wallet_data_filename)) + shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].chain_path, self.default_wallet_name, self.wallet_data_filename)) self.start_node(1) tx_after_reorg = self.nodes[1].gettransaction(txid) # Check that normal confirmed tx is confirmed again but with different blockhash diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index 4495a7d778..a4f2a9b74d 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -138,11 +138,11 @@ class UpgradeWalletTest(BitcoinTestFramework): self.log.info("Test upgradewallet RPC...") # Prepare for copying of the older wallet - node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name) - node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename) - v16_3_wallet = os.path.join(v16_3_node.datadir, "regtest/wallets/wallet.dat") - v15_2_wallet = os.path.join(v15_2_node.datadir, "regtest/wallet.dat") - split_hd_wallet = os.path.join(v15_2_node.datadir, "regtest/splithd") + node_master_wallet_dir = node_master.wallets_path / self.default_wallet_name + node_master_wallet = node_master_wallet_dir / self.default_wallet_name / self.wallet_data_filename + v16_3_wallet = v16_3_node.wallets_path / "wallet.dat" + v15_2_wallet = v15_2_node.chain_path / "wallet.dat" + split_hd_wallet = v15_2_node.chain_path / "splithd" self.stop_nodes() # Make split hd wallet |