From 4194b442b8c24d37dfa3653a555ebca378710864 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Fri, 2 Feb 2024 10:43:59 -0600 Subject: [PATCH] test: Add leaf_version parameter to taproot_tree_helper() --- test/functional/feature_taproot.py | 38 +++++++++++------------ test/functional/test_framework/address.py | 3 +- test/functional/test_framework/script.py | 23 +++++++------- test/functional/test_framework/wallet.py | 3 +- test/functional/wallet_taproot.py | 3 +- 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 68c2c2e5015..5193757d5e8 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -657,7 +657,7 @@ def spenders_taproot_active(): # These are primarily tested through the test vectors implemented in libsecp256k1, and in src/tests/key_tests.cpp. # Some things are tested programmatically as well here. - tap = taproot_construct(pubs[0]) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT) # Test with key with bit flipped. add_spender(spenders, "sig/key", tap=tap, key=secs[0], failure={"key_tweaked": bitflipper(default_key_tweaked)}, **ERR_SIG_SCHNORR) # Test with sighash with bit flipped. @@ -700,11 +700,11 @@ def spenders_taproot_active(): # Implement a test case that detects validation logic which maps invalid public keys to the # point at infinity in the tweaking logic. - tap = taproot_construct(invalid_pub, [("true", CScript([OP_1]))], treat_internal_as_infinity=True) + tap = taproot_construct(invalid_pub, LEAF_VERSION_TAPSCRIPT, [("true", CScript([OP_1]))], treat_internal_as_infinity=True) add_spender(spenders, "output/invalid_x", tap=tap, key_tweaked=tap.tweak, failure={"leaf": "true", "inputs": []}, **ERR_WITNESS_PROGRAM_MISMATCH) # Do the same thing without invalid point, to make sure there is no mistake in the test logic. - tap = taproot_construct(pubs[0], [("true", CScript([OP_1]))]) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, [("true", CScript([OP_1]))]) add_spender(spenders, "output/invalid_x_mock", tap=tap, key=secs[0], leaf="true", inputs=[]) # == Tests for signature hashing == @@ -719,12 +719,12 @@ def spenders_taproot_active(): common = {"annex": annex, "hashtype": hashtype, "standard": no_annex} # Pure pubkey - tap = taproot_construct(pubs[0]) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT) add_spender(spenders, "sighash/purepk", tap=tap, key=secs[0], **common, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) # Pubkey/P2PK script combination scripts = [("s0", CScript(random_checksig_style(pubs[1])))] - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) add_spender(spenders, "sighash/keypath_hashtype_%x" % hashtype, tap=tap, key=secs[0], **common, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/scriptpath_hashtype_%x" % hashtype, tap=tap, leaf="s0", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) @@ -746,7 +746,7 @@ def spenders_taproot_active(): # pushes (e.g. `OP_PUSH1 1` instead of `OP_1`), we set a minimum data size of 2 bytes. ] random.shuffle(scripts) - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) add_spender(spenders, "sighash/pk_codesep", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/codesep_pk", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) add_spender(spenders, "sighash/branched_codesep/left", tap=tap, leaf="branched_codesep", key=secs[0], codeseppos=3, **common, inputs=[getter("sign"), b'\x01'], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) @@ -786,7 +786,7 @@ def spenders_taproot_active(): ("csa_neg", CScript([OP_2, pubs[2], OP_CHECKSIGADD, OP_2, OP_EQUAL])) ] random.shuffle(scripts) - tap = taproot_construct(pubs[3], scripts) + tap = taproot_construct(pubs[3], LEAF_VERSION_TAPSCRIPT, scripts) # Empty signatures add_spender(spenders, "siglen/empty_keypath", tap=tap, key=secs[3], hashtype=hashtype, failure={"sign": b""}, **ERR_SIG_SIZE) add_spender(spenders, "siglen/empty_csv", tap=tap, key=secs[2], leaf="csv", hashtype=hashtype, **SINGLE_SIG, failure={"sign": b""}, **ERR_CHECKSIGVERIFY) @@ -826,7 +826,7 @@ def spenders_taproot_active(): prog += bytes([0 for _ in range(witlen - 32)]) return CScript([CScriptOp.encode_op_n(witver), prog]) scripts = [("s0", CScript([pubs[0], OP_CHECKSIG])), ("dummy", CScript([OP_RETURN]))] - tap = taproot_construct(pubs[1], scripts) + tap = taproot_construct(pubs[1], LEAF_VERSION_TAPSCRIPT, scripts) if not p2sh and witver == 1 and witlen == 32: add_spender(spenders, "applic/keypath", p2sh=p2sh, spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[1], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR) add_spender(spenders, "applic/scriptpath", p2sh=p2sh, leaf="s0", spk_mutate_pre_p2sh=mutate, tap=tap, key=secs[0], **SINGLE_SIG, failure={"leaf": "dummy"}, **ERR_OP_RETURN) @@ -862,7 +862,7 @@ def spenders_taproot_active(): # Add 127 nodes on top of that tree, so that "128deep" and "129deep" end up at their designated depths. for _ in range(127): scripts = [scripts, random.choice(PARTNER_MERKLE_FN)] - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) # Test that spends with a depth of 128 work, but 129 doesn't (even with a tree with weird Merkle branches in it). add_spender(spenders, "spendpath/merklelimit", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"leaf": "129deep"}, **ERR_CONTROLBLOCK_SIZE) # Test that flipping the negation bit invalidates spends. @@ -879,7 +879,7 @@ def spenders_taproot_active(): add_spender(spenders, "spendpath/trunclongcontrol", tap=tap, leaf="128deep", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_merklebranch(ctx)[0:random.randrange(1, 32)]}, **ERR_CONTROLBLOCK_SIZE) scripts = [("s", CScript([pubs[0], OP_CHECKSIG]))] - tap = taproot_construct(pubs[1], scripts) + tap = taproot_construct(pubs[1], LEAF_VERSION_TAPSCRIPT, scripts) # Test that adding garbage to the control block invalidates it. add_spender(spenders, "spendpath/padshortcontrol", tap=tap, leaf="s", **SINGLE_SIG, key=secs[0], failure={"controlblock": lambda ctx: default_controlblock(ctx) + random.randbytes(random.randrange(1, 32))}, **ERR_CONTROLBLOCK_SIZE) # Test that truncating the control block invalidates it. @@ -998,7 +998,7 @@ def spenders_taproot_active(): for j in range(100000): scripts.append((None, CScript([OP_RETURN, random.randrange(100000)]))) random.shuffle(scripts) - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) common = { "hashtype": hashtype, "key": secs[1], @@ -1107,7 +1107,7 @@ def spenders_taproot_active(): scripts = [("s", fn(n, pubkey)[0])] for _ in range(merkledepth): scripts = [scripts, random.choice(PARTNER_MERKLE_FN)] - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) standard = annex is None and dummylen <= 80 and len(pubkey) == 32 add_spender(spenders, "tapscript/sigopsratio_%i" % fn_num, tap=tap, leaf="s", annex=annex, hashtype=hashtype, key=secs[1], inputs=[getter("sign"), random.randbytes(dummylen)], standard=standard, failure={"inputs": [getter("sign"), random.randbytes(dummylen - 1)]}, **ERR_SIGOPS_RATIO) @@ -1129,7 +1129,7 @@ def spenders_taproot_active(): ("1001push_unkver", CScript([OP_0] * 1001), leafver), ] random.shuffle(scripts) - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) add_spender(spenders, "unkver/bare", standard=False, tap=tap, leaf="bare_unkver", failure={"leaf": "bare_c0"}, **ERR_CLEANSTACK) add_spender(spenders, "unkver/return", standard=False, tap=tap, leaf="return_unkver", failure={"leaf": "return_c0"}, **ERR_OP_RETURN) add_spender(spenders, "unkver/undecodable", standard=False, tap=tap, leaf="undecodable_unkver", failure={"leaf": "undecodable_c0"}, **ERR_UNDECODABLE) @@ -1159,7 +1159,7 @@ def spenders_taproot_active(): ("1001push_nop", CScript([OP_0] * 1001 + [OP_NOP])), ] random.shuffle(scripts) - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) add_spender(spenders, "opsuccess/bare", standard=False, tap=tap, leaf="bare_success", failure={"leaf": "bare_nop"}, **ERR_CLEANSTACK) add_spender(spenders, "opsuccess/unexecif", standard=False, tap=tap, leaf="unexecif_success", failure={"leaf": "unexecif_nop"}, **ERR_CLEANSTACK) add_spender(spenders, "opsuccess/return", standard=False, tap=tap, leaf="return_success", failure={"leaf": "return_nop"}, **ERR_OP_RETURN) @@ -1178,13 +1178,13 @@ def spenders_taproot_active(): ("normal", CScript([OP_RETURN, opcode] + [OP_NOP] * 75)), ("op_success", CScript([OP_RETURN, CScriptOp(0x50)])) ] - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) add_spender(spenders, "alwaysvalid/notsuccessx", tap=tap, leaf="op_success", inputs=[], standard=False, failure={"leaf": "normal"}) # err_msg differs based on opcode # == Test case for https://github.com/bitcoin/bitcoin/issues/24765 == zero_fn = lambda h: bytes([0 for _ in range(32)]) - tap = taproot_construct(pubs[0], [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn]) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, [("leaf", CScript([pubs[1], OP_CHECKSIG, pubs[1], OP_CHECKSIGADD, OP_2, OP_EQUAL])), zero_fn]) add_spender(spenders, "case24765", tap=tap, leaf="leaf", inputs=[getter("sign"), getter("sign")], key=secs[1], no_fail=True) # == Legacy tests == @@ -1221,7 +1221,7 @@ def spenders_taproot_nonstandard(): ("future_leaf", CScript([pub, OP_CHECKSIG]), 0xc2), ("op_success", CScript([pub, OP_CHECKSIG, OP_0, OP_IF, CScriptOp(0x50), OP_ENDIF])), ] - tap = taproot_construct(pub, scripts) + tap = taproot_construct(pub, LEAF_VERSION_TAPSCRIPT, scripts) # Test that features like annex, leaf versions, or OP_SUCCESS are valid but non-standard add_spender(spenders, "inactive/scriptpath_valid_unkleaf", key=sec, tap=tap, leaf="future_leaf", standard=False, inputs=[getter("sign")]) @@ -1246,7 +1246,7 @@ def sample_spenders(): ] # Build TaprootInfo using scripts and appropriate pubkey for output creation - tap = taproot_construct(pubs[0], scripts) + tap = taproot_construct(pubs[0], LEAF_VERSION_TAPSCRIPT, scripts) # Finally, add spender(s). # Each spender embodies a test with an optional failure condition. @@ -1601,7 +1601,7 @@ class TaprootTest(BitcoinTestFramework): [("1", CScript([pubs[58], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT), ("2", CScript([pubs[59], OP_CHECKSIG]), LEAF_VERSION_TAPSCRIPT)] ], ] - taps = [taproot_construct(inner_keys[i], script_lists[i]) for i in range(len(inner_keys))] + taps = [taproot_construct(inner_keys[i], LEAF_VERSION_TAPSCRIPT, script_lists[i]) for i in range(len(inner_keys))] # Require negated taps[0] assert taps[0].negflag diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 2c754e35aaa..6b3cc950381 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -19,6 +19,7 @@ from .script import ( hash256, sha256, taproot_construct, + LEAF_VERSION_TAPSCRIPT ) from .util import assert_equal from test_framework.script_util import ( @@ -56,7 +57,7 @@ def create_deterministic_address_bcrt1_p2tr_op_true(explicit_internal_key=None): Returns a tuple with the generated address and the TaprootInfo object. """ internal_key = explicit_internal_key or (1).to_bytes(32, 'big') - taproot_info = taproot_construct(internal_key, [("only-path", CScript([OP_TRUE]))]) + taproot_info = taproot_construct(internal_key, LEAF_VERSION_TAPSCRIPT, [("only-path", CScript([OP_TRUE]))]) address = output_key_to_p2tr(taproot_info.output_pubkey) if explicit_internal_key is None: assert_equal(address, 'bcrt1p9yfmy5h72durp7zrhlw9lf7jpwjgvwdg0jr0lqmmjtgg83266lqsekaqka') diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 12bfee7c776..8922be14718 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -855,7 +855,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index=0, *, scriptpa def TaprootSignatureHash(*args, **kwargs): return TaggedHash("TapSighash", TaprootSignatureMsg(*args, **kwargs)) -def taproot_tree_helper(scripts): +def taproot_tree_helper(scripts, leaf_version): if len(scripts) == 0: return ([], bytes()) if len(scripts) == 1: @@ -863,30 +863,29 @@ def taproot_tree_helper(scripts): script = scripts[0] assert not callable(script) if isinstance(script, list): - return taproot_tree_helper(script) + return taproot_tree_helper(script, leaf_version) assert isinstance(script, tuple) - version = LEAF_VERSION_TAPSCRIPT name = script[0] code = script[1] if len(script) == 3: - version = script[2] - assert version & 1 == 0 + leaf_version = script[2] + assert leaf_version & 1 == 0 assert isinstance(code, bytes) - h = TaggedHash("TapLeaf", bytes([version]) + ser_string(code)) + h = TaggedHash("TapLeaf", bytes([leaf_version]) + ser_string(code)) if name is None: return ([], h) - return ([(name, version, code, bytes(), h)], h) + return ([(name, leaf_version, code, bytes(), h)], h) elif len(scripts) == 2 and callable(scripts[1]): # Two entries, and the right one is a function - left, left_h = taproot_tree_helper(scripts[0:1]) + left, left_h = taproot_tree_helper(scripts[0:1], leaf_version) right_h = scripts[1](left_h) left = [(name, version, script, control + right_h, leaf) for name, version, script, control, leaf in left] right = [] else: # Two or more entries: descend into each side split_pos = len(scripts) // 2 - left, left_h = taproot_tree_helper(scripts[0:split_pos]) - right, right_h = taproot_tree_helper(scripts[split_pos:]) + left, left_h = taproot_tree_helper(scripts[0:split_pos], leaf_version) + right, right_h = taproot_tree_helper(scripts[split_pos:], leaf_version) left = [(name, version, script, control + right_h, leaf) for name, version, script, control, leaf in left] right = [(name, version, script, control + left_h, leaf) for name, version, script, control, leaf in right] if right_h < left_h: @@ -909,7 +908,7 @@ TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,internal_pubkey,negflag,tw # - merklebranch: the merkle branch to use for this leaf (32*N bytes) TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch,leaf_hash") -def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False): +def taproot_construct(pubkey, leaf_version, scripts=None, treat_internal_as_infinity=False): """Construct a tree of Taproot spending conditions pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes) @@ -926,7 +925,7 @@ def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False): if scripts is None: scripts = [] - ret, h = taproot_tree_helper(scripts) + ret, h = taproot_tree_helper(scripts, leaf_version) tweak = TaggedHash("TapTweak", pubkey + h) if treat_internal_as_infinity: tweaked, negated = compute_xonly_pubkey(tweak) diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index dee90f9fd6c..dd49d7992c7 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -36,6 +36,7 @@ from test_framework.messages import ( ser_compact_size, ) from test_framework.script import ( + LEAF_VERSION_TAPSCRIPT, CScript, OP_1, OP_NOP, @@ -445,7 +446,7 @@ def getnewdestination(address_type='bech32m'): scriptpubkey = key_to_p2wpkh_script(pubkey) address = key_to_p2wpkh(pubkey) elif address_type == 'bech32m': - tap = taproot_construct(compute_xonly_pubkey(key.get_bytes())[0]) + tap = taproot_construct(compute_xonly_pubkey(key.get_bytes())[0], LEAF_VERSION_TAPSCRIPT) pubkey = tap.output_pubkey scriptpubkey = tap.scriptPubKey address = output_key_to_p2tr(pubkey) diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index cc9683fcded..a54916917c5 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -20,6 +20,7 @@ from test_framework.script import ( OP_CHECKSIGADD, OP_NUMEQUAL, taproot_construct, + LEAF_VERSION_TAPSCRIPT ) from test_framework.segwit_addr import encode_segwit_address @@ -179,7 +180,7 @@ def multi_a(k, hex_keys, sort=False): def compute_taproot_address(pubkey, scripts): """Compute the address for a taproot output with given inner key and scripts.""" - return output_key_to_p2tr(taproot_construct(pubkey, scripts).output_pubkey) + return output_key_to_p2tr(taproot_construct(pubkey, LEAF_VERSION_TAPSCRIPT, scripts).output_pubkey) def compute_raw_taproot_address(pubkey): return encode_segwit_address("bcrt", 1, pubkey)