From f06e6d03452cf5e0b1a0863afb08c9e6d3ef452e Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Sun, 13 Sep 2020 22:20:17 -0700 Subject: tests: functional tests for Schnorr/Taproot/Tapscript A large functional test is added that automatically generates random transactions which exercise various aspects of the new rules, and verifies they are accepted into the mempool (when appropriate), and correctly accepted/rejected in (Python-constructed) blocks. Includes sighashing code and many tests by Johnson Lau. Includes a test by Matthew Zipkin. Includes several tests and improvements by Greg Sanders. --- test/functional/test_framework/blocktools.py | 19 +++- test/functional/test_framework/key.py | 6 +- test/functional/test_framework/script.py | 133 +++++++++++++++++++++++++-- 3 files changed, 141 insertions(+), 17 deletions(-) (limited to 'test/functional/test_framework') diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index afc1995009..4be8b7d80b 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -43,7 +43,9 @@ from .script import ( from .util import assert_equal from io import BytesIO +WITNESS_SCALE_FACTOR = 4 MAX_BLOCK_SIGOPS = 20000 +MAX_BLOCK_SIGOPS_WEIGHT = MAX_BLOCK_SIGOPS * WITNESS_SCALE_FACTOR # Genesis block time (regtest) TIME_GENESIS_BLOCK = 1296688602 @@ -101,22 +103,31 @@ def script_BIP34_coinbase_height(height): return CScript([CScriptNum(height)]) -def create_coinbase(height, pubkey=None): - """Create a coinbase transaction, assuming no miner fees. +def create_coinbase(height, pubkey=None, extra_output_script=None, fees=0): + """Create a coinbase transaction. If pubkey is passed in, the coinbase output will be a P2PK output; - otherwise an anyone-can-spend output.""" + otherwise an anyone-can-spend output. + + If extra_output_script is given, make a 0-value output to that + script. This is useful to pad block weight/sigops as needed. """ coinbase = CTransaction() coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 50 * COIN halvings = int(height / 150) # regtest coinbaseoutput.nValue >>= halvings - if (pubkey is not None): + coinbaseoutput.nValue += fees + if pubkey is not None: coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG]) else: coinbaseoutput.scriptPubKey = CScript([OP_TRUE]) coinbase.vout = [coinbaseoutput] + if extra_output_script is not None: + coinbaseoutput2 = CTxOut() + coinbaseoutput2.nValue = 0 + coinbaseoutput2.scriptPubKey = extra_output_script + coinbase.vout.append(coinbaseoutput2) coinbase.calc_sha256() return coinbase diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index c9f09a31aa..17b869e542 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -476,7 +476,7 @@ def verify_schnorr(key, sig, msg): return False return True -def sign_schnorr(key, msg, aux=None): +def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): """Create a Schnorr signature (see BIP 340).""" if aux is None: @@ -490,13 +490,13 @@ def sign_schnorr(key, msg, aux=None): if sec == 0 or sec >= SECP256K1_ORDER: return None P = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, sec)])) - if not SECP256K1.has_even_y(P): + if SECP256K1.has_even_y(P) == flip_p: sec = SECP256K1_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 assert kp != 0 R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, kp)])) - k = kp if SECP256K1.has_even_y(R) else SECP256K1_ORDER - 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') diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 5e35ba0fce..8e5848d493 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -6,11 +6,15 @@ This file is modified from python-bitcoinlib. """ + +from collections import namedtuple import hashlib import struct import unittest from typing import List, Dict +from .key import TaggedHash, tweak_add_pubkey + from .messages import ( CTransaction, CTxOut, @@ -22,8 +26,13 @@ from .messages import ( ) MAX_SCRIPT_ELEMENT_SIZE = 520 +LOCKTIME_THRESHOLD = 500000000 +ANNEX_TAG = 0x50 + OPCODE_NAMES = {} # type: Dict[CScriptOp, str] +LEAF_VERSION_TAPSCRIPT = 0xc0 + def hash160(s): return hashlib.new('ripemd160', sha256(s)).digest() @@ -239,11 +248,8 @@ OP_NOP8 = CScriptOp(0xb7) OP_NOP9 = CScriptOp(0xb8) OP_NOP10 = CScriptOp(0xb9) -# template matching params -OP_SMALLINTEGER = CScriptOp(0xfa) -OP_PUBKEYS = CScriptOp(0xfb) -OP_PUBKEYHASH = CScriptOp(0xfd) -OP_PUBKEY = CScriptOp(0xfe) +# BIP 342 opcodes (Tapscript) +OP_CHECKSIGADD = CScriptOp(0xba) OP_INVALIDOPCODE = CScriptOp(0xff) @@ -359,10 +365,7 @@ OPCODE_NAMES.update({ OP_NOP8: 'OP_NOP8', OP_NOP9: 'OP_NOP9', OP_NOP10: 'OP_NOP10', - OP_SMALLINTEGER: 'OP_SMALLINTEGER', - OP_PUBKEYS: 'OP_PUBKEYS', - OP_PUBKEYHASH: 'OP_PUBKEYHASH', - OP_PUBKEY: 'OP_PUBKEY', + OP_CHECKSIGADD: 'OP_CHECKSIGADD', OP_INVALIDOPCODE: 'OP_INVALIDOPCODE', }) @@ -593,6 +596,7 @@ class CScript(bytes): return n +SIGHASH_DEFAULT = 0 # Taproot-only default, semantics same as SIGHASH_ALL SIGHASH_ALL = 1 SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 @@ -615,7 +619,6 @@ def FindAndDelete(script, sig): r += script[last_sop_idx:] return CScript(r) - def LegacySignatureHash(script, txTo, inIdx, hashtype): """Consensus-correct SignatureHash @@ -738,3 +741,113 @@ class TestFrameworkScript(unittest.TestCase): values = [0, 1, -1, -2, 127, 128, -255, 256, (1 << 15) - 1, -(1 << 16), (1 << 24) - 1, (1 << 31), 1 - (1 << 32), 1 << 40, 1500, -1500] for value in values: self.assertEqual(CScriptNum.decode(CScriptNum.encode(CScriptNum(value))), value) + +def TaprootSignatureHash(txTo, spent_utxos, hash_type, input_index = 0, scriptpath = False, script = CScript(), codeseparator_pos = -1, annex = None, leaf_ver = LEAF_VERSION_TAPSCRIPT): + assert (len(txTo.vin) == len(spent_utxos)) + assert (input_index < len(txTo.vin)) + out_type = SIGHASH_ALL if hash_type == 0 else hash_type & 3 + in_type = hash_type & SIGHASH_ANYONECANPAY + spk = spent_utxos[input_index].scriptPubKey + ss = bytes([0, hash_type]) # epoch, hash_type + ss += struct.pack("= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe) -- cgit v1.2.3