aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/feature_abortnode.py8
-rwxr-xr-xtest/functional/feature_addrman.py2
-rwxr-xr-xtest/functional/feature_anchors.py4
-rwxr-xr-xtest/functional/feature_asmap.py2
-rwxr-xr-xtest/functional/feature_block.py14
-rwxr-xr-xtest/functional/feature_blocksdir.py4
-rwxr-xr-xtest/functional/feature_config_args.py4
-rwxr-xr-xtest/functional/feature_fee_estimation.py2
-rwxr-xr-xtest/functional/feature_filelock.py5
-rwxr-xr-xtest/functional/feature_includeconf.py24
-rwxr-xr-xtest/functional/feature_index_prune.py4
-rwxr-xr-xtest/functional/feature_init.py2
-rwxr-xr-xtest/functional/feature_loadblock.py2
-rwxr-xr-xtest/functional/feature_logging.py2
-rwxr-xr-xtest/functional/feature_posix_fs_permissions.py6
-rwxr-xr-xtest/functional/feature_pruning.py4
-rwxr-xr-xtest/functional/feature_reindex.py3
-rwxr-xr-xtest/functional/feature_remove_pruned_files_on_startup.py8
-rwxr-xr-xtest/functional/feature_settings.py2
-rwxr-xr-xtest/functional/feature_startupnotify.py9
-rwxr-xr-xtest/functional/feature_taproot.py4
-rwxr-xr-xtest/functional/feature_txindex_compatibility.py6
-rwxr-xr-xtest/functional/interface_rest.py3
-rwxr-xr-xtest/functional/interface_rpc.py2
-rwxr-xr-xtest/functional/interface_usdt_mempool.py5
-rwxr-xr-xtest/functional/mempool_compatibility.py4
-rwxr-xr-xtest/functional/mempool_persist.py4
-rwxr-xr-xtest/functional/p2p_message_capture.py4
-rwxr-xr-xtest/functional/p2p_segwit.py8
-rwxr-xr-xtest/functional/rpc_blockchain.py12
-rwxr-xr-xtest/functional/rpc_createmultisig.py2
-rwxr-xr-xtest/functional/rpc_dumptxoutset.py2
-rwxr-xr-xtest/functional/rpc_users.py15
-rwxr-xr-xtest/functional/rpc_whitelist.py12
-rw-r--r--test/functional/test_framework/authproxy.py13
-rw-r--r--test/functional/test_framework/coverage.py3
-rw-r--r--test/functional/test_framework/ellswift.py163
-rw-r--r--test/functional/test_framework/ellswift_decode_test_vectors.csv77
-rw-r--r--test/functional/test_framework/key.py345
-rw-r--r--test/functional/test_framework/script.py10
-rw-r--r--test/functional/test_framework/secp256k1.py346
-rwxr-xr-xtest/functional/test_framework/test_framework.py2
-rwxr-xr-xtest/functional/test_framework/test_node.py47
-rw-r--r--test/functional/test_framework/util.py8
-rw-r--r--test/functional/test_framework/wallet.py17
-rw-r--r--test/functional/test_framework/xswiftec_inv_test_vectors.csv33
-rwxr-xr-xtest/functional/test_runner.py4
-rwxr-xr-xtest/functional/tool_wallet.py26
-rwxr-xr-xtest/functional/wallet_backup.py30
-rwxr-xr-xtest/functional/wallet_backwards_compatibility.py17
-rwxr-xr-xtest/functional/wallet_descriptor.py2
-rwxr-xr-xtest/functional/wallet_hd.py12
-rwxr-xr-xtest/functional/wallet_inactive_hdchains.py5
-rwxr-xr-xtest/functional/wallet_keypool_topup.py2
-rwxr-xr-xtest/functional/wallet_listtransactions.py4
-rwxr-xr-xtest/functional/wallet_migration.py157
-rwxr-xr-xtest/functional/wallet_miniscript.py12
-rwxr-xr-xtest/functional/wallet_multiwallet.py10
-rwxr-xr-xtest/functional/wallet_pruning.py2
-rwxr-xr-xtest/functional/wallet_reorgsrestore.py2
-rwxr-xr-xtest/functional/wallet_upgradewallet.py10
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