Skip to content

test: Add feature_taproot.py --previous_release #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ test_fuzz_script_interpreter_SOURCES = test/fuzz/script_interpreter.cpp
test_fuzz_script_assets_test_minimizer_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
test_fuzz_script_assets_test_minimizer_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
test_fuzz_script_assets_test_minimizer_LDADD = $(FUZZ_SUITE_LD_COMMON)
test_fuzz_script_assets_test_minimizer_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS)
test_fuzz_script_assets_test_minimizer_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON)
test_fuzz_script_assets_test_minimizer_SOURCES = test/fuzz/script_assets_test_minimizer.cpp

test_fuzz_script_ops_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
Expand Down
4 changes: 3 additions & 1 deletion src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
/**
* Check if the transaction is over standard P2WSH resources limit:
* 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements
* These limits are adequate for multi-signature up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL,
* These limits are adequate for multi-signature up to n-of-100 using OP_CHECKSIG, OP_ADD, and OP_EQUAL.
*
* Also enforce a maximum stack size limit and no annexes for P2TR spends.
*/
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);

Expand Down
9 changes: 9 additions & 0 deletions src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,15 @@ class CPubKey
/*
* Check syntactic correctness.
*
* When setting a pubkey (Set()) or deserializing fails (its header bytes
* don't match the length of the data), the size is set to 0. Thus,
* by checking size, one can observe whether Set() or deserialization has
* failed.
*
* This does not check for more than that. In particular, it does not verify
* that the coordinates correspond to a point on the curve (see IsFullyValid
* for that instead).
*
* Note that this is consensus critical as CheckECDSASignature() calls it!
*/
bool IsValid() const
Expand Down
6 changes: 6 additions & 0 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1834,9 +1834,13 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash)
{
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
//! The inner pubkey (x-only, so no Y coordinate parity).
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
//! The output pubkey (taken from the scriptPubKey).
const XOnlyPubKey q{uint256(program)};
// Compute the tapleaf hash.
tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
// Compute the Merkle root from the leaf and the provided path.
uint256 k = tapleaf_hash;
for (int i = 0; i < path_len; ++i) {
CHashWriter ss_branch{HASHER_TAPBRANCH};
Expand All @@ -1848,7 +1852,9 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
}
k = ss_branch.GetSHA256();
}
// Compute the tweak from the Merkle root and the inner pubkey.
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
// Verify that the output pubkey matches the tweaked inner pubkey, after correcting for parity.
return q.CheckPayToContract(p, k, control[0] & 1);
}

Expand Down
2 changes: 1 addition & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
}

// Check for non-standard witness in P2WSH
// Check for non-standard witness in P2WSH and P2TR.
if (tx.HasWitness() && fRequireStandard && !IsWitnessStandard(tx, m_view))
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");

Expand Down
33 changes: 25 additions & 8 deletions test/functional/feature_taproot.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@ def make_spender(comment, *, tap=None, witv0=False, script=None, pkh=None, p2sh=
* standard: whether the (valid version of) spending is expected to be standard
* err_msg: a string with an expected error message for failure (or None, if not cared about)
* sigops_weight: the pre-taproot sigops weight consumed by a successful spend
* need_vin_vout_mismatch: whether this test requires being tested in a transaction input that has no corresponding
transaction output.
"""

conf = dict()
Expand Down Expand Up @@ -516,9 +518,9 @@ def random_checksig_style(pubkey):
"""Creates a random CHECKSIG* tapscript that would succeed with only the valid signature on witness stack."""
return bytes(CScript([pubkey, OP_CHECKSIG]))
opcode = random.choice([OP_CHECKSIG, OP_CHECKSIGVERIFY, OP_CHECKSIGADD])
if (opcode == OP_CHECKSIGVERIFY):
if opcode == OP_CHECKSIGVERIFY:
ret = CScript([pubkey, opcode, OP_1])
elif (opcode == OP_CHECKSIGADD):
elif opcode == OP_CHECKSIGADD:
num = random.choice([0, 0x7fffffff, -0x7fffffff])
ret = CScript([num, pubkey, opcode, num + 1, OP_EQUAL])
else:
Expand Down Expand Up @@ -1187,19 +1189,36 @@ def dump_witness(wit):
# Data type to keep track of UTXOs, where they were created, and how to spend them.
UTXOData = namedtuple('UTXOData', 'outpoint,output,spender')


class TaprootTest(BitcoinTestFramework):
def add_options(self, parser):
parser.add_argument("--dumptests", dest="dump_tests", default=False, action="store_true",
help="Dump generated test cases to directory set by TEST_DUMP_DIR environment variable")
parser.add_argument("--previous_release", dest="previous_release", default=False, action="store_true",
help="Use a previous release as taproot-inactive node")

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
if self.options.previous_release:
self.skip_if_no_previous_releases()

def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
# Node 0 has Taproot inactive, Node 1 active.
self.extra_args = [["-whitelist=127.0.0.1", "-par=1", "-vbparams=taproot:1:1"], ["-whitelist=127.0.0.1", "-par=1"]]
self.extra_args = [["-par=1"], ["-par=1"]]
if self.options.previous_release:
self.wallet_names = [None, self.default_wallet_name]
else:
self.extra_args[0].append("-vbparams=taproot:1:1")

def setup_nodes(self):
self.add_nodes(self.num_nodes, self.extra_args, versions=[
170200 if self.options.previous_release else None,
None,
])
self.start_nodes()
self.import_deterministic_coinbase_privkeys()

def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False):

Expand All @@ -1221,7 +1240,7 @@ def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_w
block_response = node.submitblock(block.serialize(True).hex())
if err_msg is not None:
assert block_response is not None and err_msg in block_response, "Missing error message '%s' from block response '%s': %s" % (err_msg, "(None)" if block_response is None else block_response, msg)
if (accept):
if accept:
assert node.getbestblockhash() == block.hash, "Failed to accept: %s (response: %s)" % (msg, block_response)
self.tip = block.sha256
self.lastblockhash = block.hash
Expand Down Expand Up @@ -1420,10 +1439,10 @@ def test_spenders(self, node, spenders, input_counts):
tx.rehash()
msg = ','.join(utxo.spender.comment + ("*" if n == fail_input else "") for n, utxo in enumerate(input_utxos))
if is_standard_tx:
node.sendrawtransaction(tx.serialize().hex(), 0)
node.sendrawtransaction(tx.serialize().hex(), True if node.version else 0)
assert node.getmempoolentry(tx.hash) is not None, "Failed to accept into mempool: " + msg
else:
assert_raises_rpc_error(-26, None, node.sendrawtransaction, tx.serialize().hex(), 0)
assert_raises_rpc_error(-26, None, node.sendrawtransaction, tx.serialize().hex(), True if node.version else 0)
# Submit in a block
self.block_submit(node, [tx], msg, witness=True, accept=fail_input is None, cb_pubkey=cb_pubkey, fees=fee, sigops_weight=sigops_weight, err_msg=expected_fail_msg)

Expand All @@ -1436,8 +1455,6 @@ def test_spenders(self, node, spenders, input_counts):
self.log.info(" - Done")

def run_test(self):
self.connect_nodes(0, 1)

# Post-taproot activation tests go first (pre-taproot tests' blocks are invalid post-taproot).
self.log.info("Post-activation tests...")
self.nodes[1].generate(101)
Expand Down
5 changes: 3 additions & 2 deletions test/functional/test_framework/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import hashlib
import os
import random
import sys
import unittest

from .util import modinv
Expand All @@ -22,6 +21,7 @@ def TaggedHash(tag, data):
return hashlib.sha256(ss).digest()

def xor_bytes(b0, b1):
assert len(b0) == len(b1)
return bytes(x ^ y for (x, y) in zip(b0, b1))

def jacobi_symbol(n, k):
Expand Down Expand Up @@ -523,7 +523,8 @@ def test_schnorr(self):
def test_schnorr_testvectors(self):
"""Implement the BIP340 test vectors (read from bip340_test_vectors.csv)."""
num_tests = 0
with open(os.path.join(sys.path[0], 'test_framework', 'bip340_test_vectors.csv'), newline='', encoding='utf8') as csvfile:
vectors_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'bip340_test_vectors.csv')
with open(vectors_file, newline='', encoding='utf8') as csvfile:
reader = csv.reader(csvfile)
next(reader)
for row in reader:
Expand Down
24 changes: 18 additions & 6 deletions test/functional/test_framework/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,21 +824,33 @@ def taproot_tree_helper(scripts):
h = TaggedHash("TapBranch", left_h + right_h)
return (left + right, h)

# A TaprootInfo object has the following fields:
# - scriptPubKey: the scriptPubKey (witness v1 CScript)
# - inner_pubkey: the inner pubkey (32 bytes)
# - negflag: whether the pubkey in the scriptPubKey was negated from inner_pubkey+tweak*G (bool).
# - tweak: the tweak (32 bytes)
# - leaves: a dict of name -> TaprootLeafInfo objects for all known leaves
TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,inner_pubkey,negflag,tweak,leaves")

# A TaprootLeafInfo object has the following fields:
# - script: the leaf script (CScript or bytes)
# - version: the leaf version (0xc0 for BIP342 tapscript)
# - merklebranch: the merkle branch to use for this leaf (32*N bytes)
TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch")

def taproot_construct(pubkey, scripts=None):
"""Construct a tree of Taproot spending conditions

pubkey: an ECPubKey object for the internal pubkey
pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes)
scripts: a list of items; each item is either:
- a (name, CScript) tuple
- a (name, CScript, leaf version) tuple
- a (name, CScript or bytes, leaf version) tuple
- a (name, CScript or bytes) tuple (defaulting to leaf version 0xc0)
- another list of items (with the same structure)
- a function, which specifies how to compute the hashing partner
in function of the hash of whatever it is combined with
- a list of two items; the first of which is an item itself, and the
second is a function. The function takes as input the Merkle root of the
first item, and produces a (ficticious) partner to hash with.

Returns: script (sPK or redeemScript), tweak, {name:(script, leaf version, negation flag, innerkey, merklepath), ...}
Returns: a TaprootInfo object
"""
if scripts is None:
scripts = []
Expand Down
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
'wallet_dump.py',
'wallet_listtransactions.py',
'feature_taproot.py',
'feature_taproot.py --previous_release',
# vv Tests less than 60s vv
'p2p_sendheaders.py',
'wallet_importmulti.py',
Expand Down