From 236f0b176215fc483187660290ab9b57e62aba8e Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 26 Jan 2021 16:16:41 +0100 Subject: [PATCH 1/3] tests: Replace CECKey with new ECKey impl from Bitcoin master Somehow the CECKey impl is really broken if you want to set a raw private key. Core master already has a new impl so I just took that one and replaces the usages. This will probably be equivalent with what would be rebased in at a later stage. --- test/functional/feature_assumevalid.py | 8 +- test/functional/feature_block.py | 12 +- test/functional/feature_blocksign.py | 11 +- test/functional/p2p_segwit.py | 23 +- test/functional/test_framework/key.py | 573 ++++++++++++++++--------- test/functional/test_framework/util.py | 17 + 6 files changed, 406 insertions(+), 238 deletions(-) diff --git a/test/functional/feature_assumevalid.py b/test/functional/feature_assumevalid.py index 57f58ab65f..8a957f185f 100755 --- a/test/functional/feature_assumevalid.py +++ b/test/functional/feature_assumevalid.py @@ -32,7 +32,7 @@ import time from test_framework.blocktools import (create_block, create_coinbase) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlockHeader, COutPoint, @@ -104,9 +104,9 @@ def run_test(self): self.blocks = [] # Get a pubkey for the coinbase TXO - coinbase_key = CECKey() - coinbase_key.set_secretbytes(b"horsebattery") - coinbase_pubkey = coinbase_key.get_pubkey() + coinbase_key = ECKey() + coinbase_key.set(b'\x09' * 32, True) + coinbase_pubkey = coinbase_key.get_pubkey().get_bytes() # Create the first block with a coinbase output to our key height = self.nodes[0].getblockcount() + 1 diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index a80937c86f..36c3254bdd 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -14,7 +14,7 @@ get_legacy_sigopcount_block, MAX_BLOCK_SIGOPS, ) -from test_framework.key import CECKey +from test_framework.key import ECKey from test_framework.messages import ( CBlock, COIN, @@ -85,9 +85,9 @@ def run_test(self): self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} - self.coinbase_key = CECKey() - self.coinbase_key.set_secretbytes(b"horsebattery") - self.coinbase_pubkey = self.coinbase_key.get_pubkey() + self.coinbase_key = ECKey() + self.coinbase_key.set(b'\x09' * 32, True) + self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() self.tip = None self.blocks = {} self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) @@ -535,7 +535,7 @@ def run_test(self): tx.vout.append(CTxOut(b39.vtx[i].vout[0].nValue.getAmount())) # fee # Note: must pass the redeem_script (not p2sh_script) to the signature hash function (sighash, err) = SignatureHash(redeem_script, tx, 1, SIGHASH_ALL) - sig = self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL])) + sig = self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL])) scriptSig = CScript([sig, redeem_script]) tx.vin[1].scriptSig = scriptSig @@ -1321,7 +1321,7 @@ def sign_tx(self, tx, spend_tx): tx.vin[0].scriptSig = CScript() return (sighash, err) = SignatureHash(spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + tx.vin[0].scriptSig = CScript([self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))]) 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_blocksign.py b/test/functional/feature_blocksign.py index 7347195d64..bc17a4c673 100755 --- a/test/functional/feature_blocksign.py +++ b/test/functional/feature_blocksign.py @@ -31,7 +31,7 @@ def make_signblockscript(num_nodes, required_signers, keys): for i in range(num_nodes): k = keys[i] script += "21" - script += codecs.encode(k.get_pubkey(), 'hex_codec').decode("utf-8") + script += codecs.encode(k.get_pubkey().get_bytes(), 'hex_codec').decode("utf-8") script += "{}".format(50 + num_nodes) # num keys script += "ae" # OP_CHECKMULTISIG return script @@ -66,11 +66,10 @@ def init_keys(self, num_keys): self.keys = [] self.wifs = [] for i in range(num_keys): - k = key.CECKey() - pk_bytes = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest() - k.set_secretbytes(pk_bytes) - k.set_compressed(True) - w = wif(pk_bytes) + k = key.ECKey() + sk_bytes = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest() + k.set(sk_bytes, True) + w = wif(sk_bytes) self.keys.append(k) self.wifs.append(w) diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py index eb8f8f7245..b6b95e597f 100755 --- a/test/functional/p2p_segwit.py +++ b/test/functional/p2p_segwit.py @@ -10,7 +10,7 @@ import time from test_framework.blocktools import create_block, create_coinbase, add_witness_commitment, WITNESS_COMMITMENT_HEADER -from test_framework.key import CECKey, CPubKey +from test_framework.key import ECKey from test_framework.messages import ( COIN, BIP125_SEQUENCE_NUMBER, @@ -109,7 +109,7 @@ def get_p2pkh_script(pubkeyhash): def sign_p2pk_witness_input(script, tx_to, in_idx, hashtype, value, key): """Add signature for a P2PK witness program.""" tx_hash = SegwitVersion1SignatureHash(script, tx_to, in_idx, hashtype, CTxOutValue(value)) - signature = key.sign(tx_hash) + chr(hashtype).encode('latin-1') + signature = key.sign_ecdsa(tx_hash) + chr(hashtype).encode('latin-1') tx_to.wit.vtxinwit[in_idx].scriptWitness.stack = [signature, script] tx_to.rehash() @@ -1527,10 +1527,9 @@ def test_uncompressed_pubkey(self): # Segwit transactions using uncompressed pubkeys are not accepted # under default policy, but should still pass consensus. - key = CECKey() - key.set_secretbytes(b"9") - key.set_compressed(False) - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.set(b'\x09' * 32, False) + pubkey = key.get_pubkey().get_bytes() assert_equal(len(pubkey), 65) # This should be an uncompressed pubkey utxo = self.utxo.pop(0) @@ -1562,7 +1561,7 @@ def test_uncompressed_pubkey(self): tx2.vout.append(CTxOut(1000)) # fee script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx2.wit.vtxinwit.append(CTxInWitness()) tx2.wit.vtxinwit[0].scriptWitness.stack = [signature, pubkey] tx2.rehash() @@ -1619,7 +1618,7 @@ def test_uncompressed_pubkey(self): tx5.vout.append(CTxOut(tx4.vout[0].nValue.getAmount() - 1000, CScript([OP_TRUE]))) tx5.vout.append(CTxOut(1000)) # fee (sig_hash, err) = SignatureHash(script_pubkey, tx5, 0, SIGHASH_ALL) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL tx5.vin[0].scriptSig = CScript([signature, pubkey]) tx5.rehash() # Should pass policy and consensus. @@ -1632,9 +1631,9 @@ def test_uncompressed_pubkey(self): @subtest def test_signature_version_1(self): - key = CECKey() - key.set_secretbytes(b"9") - pubkey = CPubKey(key.get_pubkey()) + key = ECKey() + key.set(b'\x09' * 32, True) + pubkey = key.get_pubkey().get_bytes() witness_program = CScript([pubkey, CScriptOp(OP_CHECKSIG)]) witness_hash = sha256(witness_program) @@ -1774,7 +1773,7 @@ def test_signature_version_1(self): script = get_p2pkh_script(pubkeyhash) sig_hash = SegwitVersion1SignatureHash(script, tx2, 0, SIGHASH_ALL, tx.vout[0].nValue) - signature = key.sign(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL + signature = key.sign_ecdsa(sig_hash) + b'\x01' # 0x1 is SIGHASH_ALL # Check that we can't have a scriptSig tx2.vin[0].scriptSig = CScript([signature, pubkey]) diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 1b3e510dc4..fb9628e2ac 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -1,226 +1,379 @@ -# Copyright (c) 2011 Sam Rushing -"""ECC secp256k1 OpenSSL wrapper. - -WARNING: This module does not mlock() secrets; your private keys may end up on -disk in swap! Use with caution! - -This file is modified from python-bitcoinlib. -""" - -import ctypes -import ctypes.util -import hashlib - -ssl = ctypes.cdll.LoadLibrary(ctypes.util.find_library ('ssl') or 'libeay32') - -ssl.BN_new.restype = ctypes.c_void_p -ssl.BN_new.argtypes = [] - -ssl.BN_bin2bn.restype = ctypes.c_void_p -ssl.BN_bin2bn.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p] - -ssl.BN_CTX_free.restype = None -ssl.BN_CTX_free.argtypes = [ctypes.c_void_p] - -ssl.BN_CTX_new.restype = ctypes.c_void_p -ssl.BN_CTX_new.argtypes = [] - -ssl.ECDH_compute_key.restype = ctypes.c_int -ssl.ECDH_compute_key.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] - -ssl.ECDSA_sign.restype = ctypes.c_int -ssl.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -ssl.ECDSA_verify.restype = ctypes.c_int -ssl.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] - -ssl.EC_KEY_free.restype = None -ssl.EC_KEY_free.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int] - -ssl.EC_KEY_get0_group.restype = ctypes.c_void_p -ssl.EC_KEY_get0_group.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_get0_public_key.restype = ctypes.c_void_p -ssl.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p] - -ssl.EC_KEY_set_private_key.restype = ctypes.c_int -ssl.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.EC_KEY_set_conv_form.restype = None -ssl.EC_KEY_set_conv_form.argtypes = [ctypes.c_void_p, ctypes.c_int] - -ssl.EC_KEY_set_public_key.restype = ctypes.c_int -ssl.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.i2o_ECPublicKey.restype = ctypes.c_void_p -ssl.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p] - -ssl.EC_POINT_new.restype = ctypes.c_void_p -ssl.EC_POINT_new.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_free.restype = None -ssl.EC_POINT_free.argtypes = [ctypes.c_void_p] - -ssl.EC_POINT_mul.restype = ctypes.c_int -ssl.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p] - -# this specifies the curve used with ECDSA. -NID_secp256k1 = 714 # from openssl/obj_mac.h - +# Copyright (c) 2019 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 + +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 +anything but tests.""" +import random + +from .util import modinv +from .address import base58_to_byte + +def jacobi_symbol(n, k): + """Compute the Jacobi symbol of n modulo k + + See http://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 = modinv(z1, 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 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.""" + 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, 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 = EllipticCurve(2**256 - 2**32 - 977, 0, 7) +SECP256K1_G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798, 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8, 1) SECP256K1_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 SECP256K1_ORDER_HALF = SECP256K1_ORDER // 2 -# Thx to Sam Devlin for the ctypes magic 64-bit fix. -def _check_result(val, func, args): - if val == 0: - raise ValueError - else: - return ctypes.c_void_p (val) - -ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p -ssl.EC_KEY_new_by_curve_name.errcheck = _check_result - -class CECKey(): - """Wrapper around OpenSSL's EC_KEY""" - - POINT_CONVERSION_COMPRESSED = 2 - POINT_CONVERSION_UNCOMPRESSED = 4 +class ECPubKey(): + """A secp256k1 public key""" def __init__(self): - self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1) - - def __del__(self): - if ssl: - ssl.EC_KEY_free(self.k) - self.k = None - - def set_secretbytes(self, secret): - priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new()) - group = ssl.EC_KEY_get0_group(self.k) - pub_key = ssl.EC_POINT_new(group) - ctx = ssl.BN_CTX_new() - if not ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx): - raise ValueError("Could not derive public key from the supplied secret.") - ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx) - ssl.EC_KEY_set_private_key(self.k, priv_key) - ssl.EC_KEY_set_public_key(self.k, pub_key) - ssl.EC_POINT_free(pub_key) - ssl.BN_CTX_free(ctx) - return self.k - - def set_privkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.d2i_ECPrivateKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def set_pubkey(self, key): - self.mb = ctypes.create_string_buffer(key) - return ssl.o2i_ECPublicKey(ctypes.byref(self.k), ctypes.byref(ctypes.pointer(self.mb)), len(key)) - - def get_privkey(self): - size = ssl.i2d_ECPrivateKey(self.k, 0) - mb_pri = ctypes.create_string_buffer(size) - ssl.i2d_ECPrivateKey(self.k, ctypes.byref(ctypes.pointer(mb_pri))) - return mb_pri.raw - - def get_pubkey(self): - size = ssl.i2o_ECPublicKey(self.k, 0) - mb = ctypes.create_string_buffer(size) - ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb))) - return mb.raw - - def get_raw_ecdh_key(self, other_pubkey): - ecdh_keybuffer = ctypes.create_string_buffer(32) - r = ssl.ECDH_compute_key(ctypes.pointer(ecdh_keybuffer), 32, - ssl.EC_KEY_get0_public_key(other_pubkey.k), - self.k, 0) - if r != 32: - raise Exception('CKey.get_ecdh_key(): ECDH_compute_key() failed') - return ecdh_keybuffer.raw - - def get_ecdh_key(self, other_pubkey, kdf=lambda k: hashlib.sha256(k).digest()): - # FIXME: be warned it's not clear what the kdf should be as a default - r = self.get_raw_ecdh_key(other_pubkey) - return kdf(r) - - def sign(self, hash, low_s = True): - # FIXME: need unit tests for below cases - if not isinstance(hash, bytes): - raise TypeError('Hash must be bytes instance; got %r' % hash.__class__) - if len(hash) != 32: - raise ValueError('Hash must be exactly 32 bytes long') - - sig_size0 = ctypes.c_uint32() - sig_size0.value = ssl.ECDSA_size(self.k) - mb_sig = ctypes.create_string_buffer(sig_size0.value) - result = ssl.ECDSA_sign(0, hash, len(hash), mb_sig, ctypes.byref(sig_size0), self.k) - assert 1 == result - assert mb_sig.raw[0] == 0x30 - assert mb_sig.raw[1] == sig_size0.value - 2 - total_size = mb_sig.raw[1] - assert mb_sig.raw[2] == 2 - r_size = mb_sig.raw[3] - assert mb_sig.raw[4 + r_size] == 2 - s_size = mb_sig.raw[5 + r_size] - s_value = int.from_bytes(mb_sig.raw[6+r_size:6+r_size+s_size], byteorder='big') - if (not low_s) or s_value <= SECP256K1_ORDER_HALF: - return mb_sig.raw[:sig_size0.value] - else: - low_s_value = SECP256K1_ORDER - s_value - low_s_bytes = (low_s_value).to_bytes(33, byteorder='big') - while len(low_s_bytes) > 1 and low_s_bytes[0] == 0 and low_s_bytes[1] < 0x80: - low_s_bytes = low_s_bytes[1:] - new_s_size = len(low_s_bytes) - new_total_size_byte = (total_size + new_s_size - s_size).to_bytes(1,byteorder='big') - new_s_size_byte = (new_s_size).to_bytes(1,byteorder='big') - return b'\x30' + new_total_size_byte + mb_sig.raw[2:5+r_size] + new_s_size_byte + low_s_bytes - - def verify(self, hash, sig): - """Verify a DER signature""" - return ssl.ECDSA_verify(0, hash, len(hash), sig, len(sig), self.k) == 1 - - def set_compressed(self, compressed): - if compressed: - form = self.POINT_CONVERSION_COMPRESSED + """Construct an uninitialized public key""" + self.valid = False + + 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) + # if the oddness of the y co-ord isn't correct, find the other + # valid y + if (p[1] & 1) != (data[0] & 1): + p = SECP256K1.negate(p) + self.p = p + self.valid = True + self.compressed = True + else: + self.valid = False else: - form = self.POINT_CONVERSION_UNCOMPRESSED - ssl.EC_KEY_set_conv_form(self.k, form) - - -class CPubKey(bytes): - """An encapsulated public key + self.valid = False - Attributes: + @property + def is_compressed(self): + return self.compressed - is_valid - Corresponds to CPubKey.IsValid() - is_fullyvalid - Corresponds to CPubKey.IsFullyValid() - is_compressed - Corresponds to CPubKey.IsCompressed() - """ + @property + def is_valid(self): + return self.valid + + def get_bytes(self): + assert(self.valid) + p = SECP256K1.affine(self.p) + if p is None: + return None + if self.compressed: + return bytes([0x02 + (p[1] & 1)]) + p[0].to_bytes(32, 'big') + else: + return bytes([0x04]) + p[0].to_bytes(32, 'big') + p[1].to_bytes(32, 'big') + + # 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) + + # # Extract r and s from the DER formatted signature. Return false for + # # any DER encoding errors. + # if (sig[1] + 2 != len(sig)): + # return False + # if (len(sig) < 4): + # return False + # if (sig[0] != 0x30): + # return False + # if (sig[2] != 0x02): + # return False + # rlen = sig[3] + # if (len(sig) < 6 + rlen): + # return False + # if rlen < 1 or rlen > 33: + # return False + # if sig[4] >= 0x80: + # return False + # if (rlen > 1 and (sig[4] == 0) and not (sig[5] & 0x80)): + # return False + # r = int.from_bytes(sig[4:4+rlen], 'big') + # if (sig[4+rlen] != 0x02): + # return False + # slen = sig[5+rlen] + # if slen < 1 or slen > 33: + # return False + # if (len(sig) != 6 + rlen + slen): + # return False + # if sig[6+rlen] >= 0x80: + # return False + # if (slen > 1 and (sig[6+rlen] == 0) and not (sig[7+rlen] & 0x80)): + # return False + # 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: + # return False + # if low_s and s >= SECP256K1_ORDER_HALF: + # return False + # z = int.from_bytes(msg, 'big') + + # # Run verifier algorithm on r, s + # w = modinv(s, 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] != r: + # return False + # return True + +class ECKey(): + """A secp256k1 private key""" - def __new__(cls, buf, _cec_key=None): - self = super(CPubKey, cls).__new__(cls, buf) - if _cec_key is None: - _cec_key = CECKey() - self._cec_key = _cec_key - self.is_fullyvalid = _cec_key.set_pubkey(self) != 0 - return self + def __init__(self): + self.valid = False + + def set(self, secret, compressed): + """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) + if self.valid: + self.secret = secret + self.compressed = compressed + + def set_wif(self, wif): + (b, v) = base58_to_byte(wif) + if len(b) != 32 and len(b) != 33: + raise ValueError("invalid WIF: unexpected length {}".format(len(b))) + compressed = len(b) == 33 + self.set(b[0:32], compressed) + + def generate(self, compressed=True): + """Generate a random private key (compressed or uncompressed).""" + self.set(random.randrange(1, SECP256K1_ORDER).to_bytes(32, 'big'), compressed) + + def get_bytes(self): + """Retrieve the 32-byte representation of this key.""" + assert(self.valid) + return self.secret.to_bytes(32, 'big') @property def is_valid(self): - return len(self) > 0 + return self.valid @property def is_compressed(self): - return len(self) == 33 - - def verify(self, hash, sig): - return self._cec_key.verify(hash, sig) - - def __str__(self): - return repr(self) - - def __repr__(self): - return '%s(%s)' % (self.__class__.__name__, super(CPubKey, self).__repr__()) + return self.compressed + def get_pubkey(self): + """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.compressed = self.compressed + return ret + + def sign_ecdsa(self, msg, low_s=True): + """Construct a DER-encoded ECDSA signature with this key. + + See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm for the + ECDSA signer algorithm.""" + assert(self.valid) + z = int.from_bytes(msg, 'big') + # Note: no RFC6979, but a simple random nonce (some tests rely on distinct transactions for the same operation) + k = random.randrange(1, SECP256K1_ORDER) + R = SECP256K1.affine(SECP256K1.mul([(SECP256K1_G, k)])) + r = R[0] % SECP256K1_ORDER + s = (modinv(k, SECP256K1_ORDER) * (z + self.secret * r)) % SECP256K1_ORDER + if low_s and s > SECP256K1_ORDER_HALF: + s = SECP256K1_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). + rb = r.to_bytes((r.bit_length() + 8) // 8, 'big') + sb = s.to_bytes((s.bit_length() + 8) // 8, 'big') + return b'\x30' + bytes([4 + len(rb) + len(sb), 2, len(rb)]) + rb + bytes([2, len(sb)]) + sb diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 0e25771e99..49c2dc27d5 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -608,3 +608,20 @@ def find_vout_for_address(node, txid, addr): if any([unblind_addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]): return i raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr)) + +def modinv(a, n): + """Compute the modular inverse of a modulo n using the extended Euclidean + Algorithm. See https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers. + """ + # TODO: Change to pow(a, -1, n) available in Python 3.8 + t1, t2 = 0, 1 + r1, r2 = n, a + while r2 != 0: + q = r1 // r2 + t1, t2 = t2, t1 - q * t2 + r1, r2 = r2, r1 - q * r2 + if r1 > 1: + return None + if t1 < 0: + t1 += n + return t1 From 691040a63df5e36f9f984da481c49cbc511fd956 Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 26 Jan 2021 17:08:03 +0100 Subject: [PATCH 2/3] Add SIGHASH_RANGEPROOF support --- src/bench/verify_script.cpp | 2 +- src/script/generic.hpp | 4 +- src/script/interpreter.cpp | 82 ++++++++++++++++++++++++---- src/script/interpreter.h | 18 ++++-- src/script/sign.cpp | 48 +++++++++------- src/script/sign.h | 4 +- src/test/multisig_tests.cpp | 2 +- src/test/script_tests.cpp | 10 ++-- src/test/sighash_tests.cpp | 7 ++- src/test/txvalidationcache_tests.cpp | 8 +-- src/validation.cpp | 13 ++++- 11 files changed, 144 insertions(+), 54 deletions(-) diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index cf8ef6b0d5..12288ab899 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -77,7 +77,7 @@ static void VerifyScriptBench(benchmark::State& state) txSpend.witness.vtxinwit.resize(1); CScriptWitness& witness = txSpend.witness.vtxinwit[0].scriptWitness; witness.stack.emplace_back(); - key.Sign(SignatureHash(witScriptPubkey, txSpend, 0, SIGHASH_ALL, txCredit.vout[0].nValue, SigVersion::WITNESS_V0), witness.stack.back()); + key.Sign(SignatureHash(witScriptPubkey, txSpend, 0, SIGHASH_ALL, txCredit.vout[0].nValue, SigVersion::WITNESS_V0, 0), witness.stack.back()); witness.stack.back().push_back(static_cast(SIGHASH_ALL)); witness.stack.push_back(ToByteVector(pubkey)); diff --git a/src/script/generic.hpp b/src/script/generic.hpp index ca1317c584..fb5dd308f3 100644 --- a/src/script/generic.hpp +++ b/src/script/generic.hpp @@ -18,7 +18,7 @@ class SimpleSignatureChecker : public BaseSignatureChecker bool sighash_byte; SimpleSignatureChecker(const uint256& hashIn, bool sighash_byte_in) : hash(hashIn), sighash_byte(sighash_byte_in) {}; - bool CheckSig(const std::vector& vchSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const + bool CheckSig(const std::vector& vchSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override { std::vector vchSigCopy(vchSig); CPubKey pubkey(vchPubKey); @@ -47,7 +47,7 @@ class SimpleSignatureCreator : public BaseSignatureCreator public: SimpleSignatureCreator(const uint256& hashIn, bool sighash_byte_in) : checker(hashIn, sighash_byte_in), sighash_byte(sighash_byte_in) {}; const BaseSignatureChecker& Checker() const { return checker; } - bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const + bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override { CKey key; if (!provider.GetKey(keyid, key)) diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 01be38e85a..df34fbb0dd 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -186,11 +186,17 @@ bool static IsLowDERSignature(const valtype &vchSig, ScriptError* serror) { return true; } -bool static IsDefinedHashtypeSignature(const valtype &vchSig) { +bool static IsDefinedHashtypeSignature(const valtype &vchSig, unsigned int flags) { if (vchSig.size() == 0) { return false; } unsigned char nHashType = vchSig[vchSig.size() - 1] & (~(SIGHASH_ANYONECANPAY)); + + // ELEMENTS: Only allow SIGHASH_RANGEPROOF if the flag is set (after dynafed activation). + if ((flags & SCRIPT_SIGHASH_RANGEPROOF) == SCRIPT_SIGHASH_RANGEPROOF) { + nHashType = nHashType & (~(SIGHASH_RANGEPROOF)); + } + if (nHashType < SIGHASH_ALL || nHashType > SIGHASH_SINGLE) return false; @@ -216,7 +222,7 @@ bool CheckSignatureEncoding(const std::vector &vchSig, unsigned i } else if ((flags & SCRIPT_VERIFY_LOW_S) != 0 && !IsLowDERSignature(vchSigCopy, serror)) { // serror is set return false; - } else if ((flags & SCRIPT_VERIFY_STRICTENC) != 0 && !IsDefinedHashtypeSignature(vchSigCopy)) { + } else if ((flags & SCRIPT_VERIFY_STRICTENC) != 0 && !IsDefinedHashtypeSignature(vchSigCopy, flags)) { return set_error(serror, SCRIPT_ERR_SIG_HASHTYPE); } return true; @@ -1213,7 +1219,8 @@ bool EvalScript(std::vector >& stack, const CScript& //serror is set return false; } - bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion); + + bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion, flags); if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size()) return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL); @@ -1291,7 +1298,7 @@ bool EvalScript(std::vector >& stack, const CScript& } // Check signature - bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion); + bool fOk = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion, flags); if (fOk) { isig++; @@ -1466,13 +1473,15 @@ class CTransactionSignatureSerializer const CScript& scriptCode; //!< output script being consumed const unsigned int nIn; //!< input index of txTo being signed const bool fAnyoneCanPay; //!< whether the hashtype has the SIGHASH_ANYONECANPAY flag set + const bool fRangeproof; //!< whether the hashtype has the SIGHASH_RANGEPROOF flag set const bool fHashSingle; //!< whether the hashtype is SIGHASH_SINGLE const bool fHashNone; //!< whether the hashtype is SIGHASH_NONE public: - CTransactionSignatureSerializer(const T& txToIn, const CScript& scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : + CTransactionSignatureSerializer(const T& txToIn, const CScript& scriptCodeIn, unsigned int nInIn, int nHashTypeIn, unsigned int flags) : txTo(txToIn), scriptCode(scriptCodeIn), nIn(nInIn), fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)), + fRangeproof(!!(flags & SCRIPT_SIGHASH_RANGEPROOF) && !!(nHashTypeIn & SIGHASH_RANGEPROOF)), fHashSingle((nHashTypeIn & 0x1f) == SIGHASH_SINGLE), fHashNone((nHashTypeIn & 0x1f) == SIGHASH_NONE) {} @@ -1529,11 +1538,23 @@ class CTransactionSignatureSerializer /** Serialize an output of txTo */ template void SerializeOutput(S &s, unsigned int nOutput) const { - if (fHashSingle && nOutput != nIn) + if (fHashSingle && nOutput != nIn) { // Do not lock-in the txout payee at other indices as txin ::Serialize(s, CTxOut()); - else + } else { ::Serialize(s, txTo.vout[nOutput]); + + // Serialize rangeproof + if (fRangeproof) { + if (nOutput < txTo.witness.vtxoutwit.size()) { + ::Serialize(s, txTo.witness.vtxoutwit[nOutput].vchRangeproof); + ::Serialize(s, txTo.witness.vtxoutwit[nOutput].vchSurjectionproof); + } else { + ::Serialize(s, (unsigned char) 0); + ::Serialize(s, (unsigned char) 0); + } + } + } } /** Serialize txTo */ @@ -1599,6 +1620,21 @@ uint256 GetOutputsHash(const T& txTo) return ss.GetHash(); } +template +uint256 GetRangeproofsHash(const T& txTo) { + CHashWriter ss(SER_GETHASH, 0); + for (size_t i = 0; i < txTo.vout.size(); i++) { + if (i < txTo.witness.vtxoutwit.size()) { + ss << txTo.witness.vtxoutwit[i].vchRangeproof; + ss << txTo.witness.vtxoutwit[i].vchSurjectionproof; + } else { + ss << (unsigned char) 0; + ss << (unsigned char) 0; + } + } + return ss.GetHash(); +} + } // namespace template @@ -1610,6 +1646,7 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) hashSequence = GetSequenceHash(txTo); hashIssuance = GetIssuanceHash(txTo); hashOutputs = GetOutputsHash(txTo); + hashRangeproofs = GetRangeproofsHash(txTo); ready = true; } } @@ -1619,7 +1656,7 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); template -uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache) +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache) { assert(nIn < txTo.vin.size()); @@ -1628,7 +1665,9 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn uint256 hashSequence; uint256 hashIssuance; uint256 hashOutputs; + uint256 hashRangeproofs; const bool cacheready = cache && cache->ready; + bool fRangeproof = !!(flags & SCRIPT_SIGHASH_RANGEPROOF) && !!(nHashType & SIGHASH_RANGEPROOF); if (!(nHashType & SIGHASH_ANYONECANPAY)) { hashPrevouts = cacheready ? cache->hashPrevouts : GetPrevoutHash(txTo); @@ -1644,10 +1683,26 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) { hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo); + + if (fRangeproof) { + hashRangeproofs = cacheready ? cache->hashRangeproofs : GetRangeproofsHash(txTo); + } } else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) { CHashWriter ss(SER_GETHASH, 0); ss << txTo.vout[nIn]; hashOutputs = ss.GetHash(); + + if (fRangeproof) { + CHashWriter ss(SER_GETHASH, 0); + if (nIn < txTo.witness.vtxoutwit.size()) { + ss << txTo.witness.vtxoutwit[nIn].vchRangeproof; + ss << txTo.witness.vtxoutwit[nIn].vchSurjectionproof; + } else { + ss << (unsigned char) 0; + ss << (unsigned char) 0; + } + hashRangeproofs = ss.GetHash(); + } } CHashWriter ss(SER_GETHASH, 0); @@ -1676,6 +1731,11 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn } // Outputs (none/one/all, depending on flags) ss << hashOutputs; + if (fRangeproof) { + // This addition must be conditional because it was added after + // the segwit sighash was specified. + ss << hashRangeproofs; + } // Locktime ss << txTo.nLockTime; // Sighash type @@ -1695,7 +1755,7 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn } // Wrapper to serialize only the necessary parts of the transaction being signed - CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType); + CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType, flags); // Serialize and hash CHashWriter ss(SER_GETHASH, 0); @@ -1710,7 +1770,7 @@ bool GenericTransactionSignatureChecker::VerifySignature(const std::vector -bool GenericTransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +bool GenericTransactionSignatureChecker::CheckSig(const std::vector& vchSigIn, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const { CPubKey pubkey(vchPubKey); if (!pubkey.IsValid()) @@ -1723,7 +1783,7 @@ bool GenericTransactionSignatureChecker::CheckSig(const std::vectortxdata); + uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, flags, this->txdata); if (!VerifySignature(vchSig, pubkey, sighash)) return false; diff --git a/src/script/interpreter.h b/src/script/interpreter.h index 8801bd52af..a7a800e6a4 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -25,6 +25,10 @@ enum SIGHASH_NONE = 2, SIGHASH_SINGLE = 3, SIGHASH_ANYONECANPAY = 0x80, + + // ELEMENTS: + // A flag that means the rangeproofs should be included in the sighash. + SIGHASH_RANGEPROOF = 0x40, }; /** Script verification flags. @@ -116,16 +120,22 @@ enum // SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16), + // ELEMENTS: + // Signature checking assumes no sighash byte after the DER signature // SCRIPT_NO_SIGHASH_BYTE = (1U << 17), + + // Support/allow SIGHASH_RANGEPROOF. + // + SCRIPT_SIGHASH_RANGEPROOF = (1U << 18), }; bool CheckSignatureEncoding(const std::vector &vchSig, unsigned int flags, ScriptError* serror); struct PrecomputedTransactionData { - uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance; + uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance, hashRangeproofs; bool ready = false; template @@ -143,12 +153,12 @@ static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32; static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20; template -uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr); +uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, unsigned int flags, const PrecomputedTransactionData* cache = nullptr); class BaseSignatureChecker { public: - virtual bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const + virtual bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const { return false; } @@ -181,7 +191,7 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker public: GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {} GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; }; diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 6d7af801bc..2b9af7febc 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -17,7 +17,7 @@ typedef std::vector valtype; MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {} -bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const +bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const { CKey key; if (!provider.GetKey(address, key)) @@ -27,7 +27,7 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed()) return false; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion); + uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, flags); if (!key.Sign(hash, vchSig)) return false; vchSig.push_back((unsigned char)nHashType); @@ -68,7 +68,7 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd return provider.GetPubKey(address, pubkey); } -static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector& sig_out, const CPubKey& pubkey, const CScript& scriptcode, SigVersion sigversion) +static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector& sig_out, const CPubKey& pubkey, const CScript& scriptcode, SigVersion sigversion, unsigned int flags) { CKeyID keyid = pubkey.GetID(); const auto it = sigdata.signatures.find(keyid); @@ -80,7 +80,7 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat if (provider.GetKeyOrigin(keyid, info)) { sigdata.misc_pubkeys.emplace(keyid, std::make_pair(pubkey, std::move(info))); } - if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion)) { + if (creator.CreateSig(provider, sig_out, keyid, scriptcode, sigversion, flags)) { auto i = sigdata.signatures.emplace(keyid, SigPair(pubkey, sig_out)); assert(i.second); return true; @@ -97,8 +97,9 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat * Returns false if scriptPubKey could not be completely satisfied. */ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, - std::vector& ret, txnouttype& whichTypeRet, SigVersion sigversion, SignatureData& sigdata) -{ + std::vector& ret, txnouttype& whichTypeRet, SigVersion sigversion, + SignatureData& sigdata, unsigned int flags +) { CScript scriptRet; uint160 h160; ret.clear(); @@ -114,7 +115,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator case TX_WITNESS_UNKNOWN: return false; case TX_PUBKEY: - if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false; + if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion, flags)) return false; ret.push_back(std::move(sig)); return true; case TX_PUBKEYHASH: { @@ -125,7 +126,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator sigdata.missing_pubkeys.push_back(keyID); return false; } - if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) return false; + if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion, flags)) return false; ret.push_back(std::move(sig)); ret.push_back(ToByteVector(pubkey)); return true; @@ -145,7 +146,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator ret.push_back(valtype()); // workaround CHECKMULTISIG bug for (size_t i = 1; i < vSolutions.size() - 1; ++i) { CPubKey pubkey = CPubKey(vSolutions[i]); - if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion)) { + if (ret.size() < required + 1 && CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, sigversion, flags)) { ret.push_back(std::move(sig)); } } @@ -196,9 +197,14 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato { if (sigdata.complete) return true; + // We will already activate SIGHASH_RANGEPROOF for signing. This means that + // users using the flag before it activates will produce invalid signatures. + unsigned int signFlags = SCRIPT_SIGHASH_RANGEPROOF; + unsigned int verifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS | SCRIPT_SIGHASH_RANGEPROOF | additional_flags; + std::vector result; txnouttype whichType; - bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); + bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata, signFlags); bool P2SH = false; CScript subscript; sigdata.scriptWitness.stack.clear(); @@ -210,7 +216,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato // and then the serialized subscript: subscript = CScript(result[0].begin(), result[0].end()); sigdata.redeem_script = subscript; - solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE, sigdata) && whichType != TX_SCRIPTHASH; + solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE, sigdata, signFlags) && whichType != TX_SCRIPTHASH; P2SH = true; } @@ -219,7 +225,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato CScript witnessscript; witnessscript << OP_DUP << OP_HASH160 << ToByteVector(result[0]) << OP_EQUALVERIFY << OP_CHECKSIG; txnouttype subType; - solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata); + solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata, signFlags); sigdata.scriptWitness.stack = result; sigdata.witness = true; result.clear(); @@ -229,7 +235,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato CScript witnessscript(result[0].begin(), result[0].end()); sigdata.witness_script = witnessscript; txnouttype subType; - solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH; + solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata, signFlags) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH; result.push_back(std::vector(witnessscript.begin(), witnessscript.end())); sigdata.scriptWitness.stack = result; sigdata.witness = true; @@ -244,7 +250,7 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptSig = PushAll(result); // Test solution - sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS|additional_flags, creator.Checker()); + sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, verifyFlags, creator.Checker()); return sigdata.complete; } @@ -256,12 +262,12 @@ class SignatureExtractorChecker final : public BaseSignatureChecker public: SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override; }; -bool SignatureExtractorChecker::CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const +bool SignatureExtractorChecker::CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const { - if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion)) { + if (checker.CheckSig(scriptSig, vchPubKey, scriptCode, sigversion, flags)) { CPubKey pubkey(vchPubKey); sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig)); return true; @@ -330,6 +336,8 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI stack.witness.clear(); sigversion = SigVersion::WITNESS_V0; } + // We enable SIGHASH_RANGEPROOF for signing. + unsigned int flags = SCRIPT_SIGHASH_RANGEPROOF; if (script_type == TX_MULTISIG && !stack.script.empty()) { // Build a map of pubkey -> signature by matching sigs to pubkeys: assert(solutions.size() > 1); @@ -339,7 +347,7 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI for (unsigned int i = last_success_key; i < num_pubkeys; ++i) { const valtype& pubkey = solutions[i+1]; // We either have a signature for this pubkey, or we have found a signature and it is valid - if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion)) { + if (data.signatures.count(CPubKey(pubkey).GetID()) || extractor_checker.CheckSig(sig, pubkey, next_script, sigversion, flags)) { last_success_key = i + 1; break; } @@ -404,7 +412,7 @@ class DummySignatureChecker final : public BaseSignatureChecker { public: DummySignatureChecker() {} - bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } + bool CheckSig(const std::vector& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override { return true; } }; const DummySignatureChecker DUMMY_CHECKER; @@ -415,7 +423,7 @@ class DummySignatureCreator final : public BaseSignatureCreator { public: DummySignatureCreator(char r_len, char s_len) : m_r_len(r_len), m_s_len(s_len) {} const BaseSignatureChecker& Checker() const override { return DUMMY_CHECKER; } - bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override + bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override { // Create a dummy signature that is a valid DER-encoding vchSig.assign(m_r_len + m_s_len + 7, '\000'); diff --git a/src/script/sign.h b/src/script/sign.h index a97e7ec5df..45b44121da 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -95,7 +95,7 @@ class BaseSignatureCreator { virtual const BaseSignatureChecker& Checker() const =0; /** Create a singular (non-script) signature. */ - virtual bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0; + virtual bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const =0; }; /** A signature creator for transactions. */ @@ -109,7 +109,7 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { public: MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override { return checker; } - bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; + bool CreateSig(const SigningProvider& provider, std::vector& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion, unsigned int flags) const override; }; /** A signature creator that just produces 71-byte empty signatures. */ diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 8afe4b8a59..f19ff2d4d3 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -21,7 +21,7 @@ BOOST_FIXTURE_TEST_SUITE(multisig_tests, BasicTestingSetup) static CScript sign_multisig(const CScript& scriptPubKey, const std::vector& keys, const CTransaction& transaction, int whichIn) { - uint256 hash = SignatureHash(scriptPubKey, transaction, whichIn, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(scriptPubKey, transaction, whichIn, SIGHASH_ALL, 0, SigVersion::BASE, 0); CScript result; result << OP_0; // CHECKMULTISIG bug workaround diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index 8186248b93..967c113ea0 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -371,7 +371,7 @@ class TestBuilder TestBuilder& PushSig(const CKey& key, int nHashType = SIGHASH_ALL, unsigned int lenR = 32, unsigned int lenS = 32, SigVersion sigversion = SigVersion::BASE, CAmount amount = 0) { - uint256 hash = SignatureHash(script, spendTx, 0, nHashType, amount, sigversion); + uint256 hash = SignatureHash(script, spendTx, 0, nHashType, amount, sigversion, 0); std::vector vchSig, r, s; uint32_t iter = 0; do { @@ -1063,7 +1063,7 @@ BOOST_AUTO_TEST_CASE(script_cltv_truncated) static CScript sign_multisig(const CScript& scriptPubKey, const std::vector& keys, const CTransaction& transaction) { - uint256 hash = SignatureHash(scriptPubKey, transaction, 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(scriptPubKey, transaction, 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); CScript result; // @@ -1267,15 +1267,15 @@ BOOST_AUTO_TEST_CASE(script_combineSigs) // A couple of partially-signed versions: std::vector sig1; - uint256 hash1 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash1 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); BOOST_CHECK(keys[0].Sign(hash1, sig1)); sig1.push_back(SIGHASH_ALL); std::vector sig2; - uint256 hash2 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_NONE, 0, SigVersion::BASE); + uint256 hash2 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_NONE, 0, SigVersion::BASE, 0); BOOST_CHECK(keys[1].Sign(hash2, sig2)); sig2.push_back(SIGHASH_NONE); std::vector sig3; - uint256 hash3 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_SINGLE, 0, SigVersion::BASE); + uint256 hash3 = SignatureHash(scriptPubKey, txTo, 0, SIGHASH_SINGLE, 0, SigVersion::BASE, 0); BOOST_CHECK(keys[2].Sign(hash3, sig3)); sig3.push_back(SIGHASH_SINGLE); diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 04d5462acb..978b6eb173 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -129,7 +129,8 @@ BOOST_AUTO_TEST_CASE(sighash_test) int nRandomTests = 50000; #endif for (int i=0; i vchSig; - uint256 hash = SignatureHash(scriptPubKey, spends[i], 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(scriptPubKey, spends[i], 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); BOOST_CHECK(coinbaseKey.Sign(hash, vchSig)); vchSig.push_back((unsigned char)SIGHASH_ALL); spends[i].vin[0].scriptSig << vchSig; @@ -183,7 +183,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Sign, with a non-DER signature { std::vector vchSig; - uint256 hash = SignatureHash(p2pk_scriptPubKey, spend_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(p2pk_scriptPubKey, spend_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); BOOST_CHECK(coinbaseKey.Sign(hash, vchSig)); vchSig.push_back((unsigned char) 0); // padding byte makes this non-DER vchSig.push_back((unsigned char)SIGHASH_ALL); @@ -256,7 +256,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Sign std::vector vchSig; - uint256 hash = SignatureHash(spend_tx.vout[2].scriptPubKey, invalid_with_cltv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(spend_tx.vout[2].scriptPubKey, invalid_with_cltv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); BOOST_CHECK(coinbaseKey.Sign(hash, vchSig)); vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_cltv_tx.vin[0].scriptSig = CScript() << vchSig << 101; @@ -284,7 +284,7 @@ BOOST_FIXTURE_TEST_CASE(checkinputs_test, TestChain100Setup) // Sign std::vector vchSig; - uint256 hash = SignatureHash(spend_tx.vout[3].scriptPubKey, invalid_with_csv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + uint256 hash = SignatureHash(spend_tx.vout[3].scriptPubKey, invalid_with_csv_tx, 0, SIGHASH_ALL, 0, SigVersion::BASE, 0); BOOST_CHECK(coinbaseKey.Sign(hash, vchSig)); vchSig.push_back((unsigned char)SIGHASH_ALL); invalid_with_csv_tx.vin[0].scriptSig = CScript() << vchSig << 101; diff --git a/src/validation.cpp b/src/validation.cpp index 01671f9a1d..e3ee030a97 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -969,7 +969,14 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool } } - constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; + unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS; + + // Temporarily add additional script flags based on the activation of + // Dynamic Federations. This can be included in the + // STANDARD_LOCKTIME_VERIFY_FLAGS in a release post-activation. + if (IsDynaFedEnabled(chainActive.Tip(), chainparams.GetConsensus())) { + scriptVerifyFlags |= SCRIPT_SIGHASH_RANGEPROOF; + } // Check against previous transactions // This is done last to help prevent CPU exhaustion denial-of-service attacks. @@ -1902,6 +1909,10 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens flags |= SCRIPT_VERIFY_NULLDUMMY; } + if (IsDynaFedEnabled(pindex->pprev, consensusparams)) { + flags |= SCRIPT_SIGHASH_RANGEPROOF; + } + return flags; } From b888f42270bb0ef94a3b324e52591da06305a95d Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Tue, 26 Jan 2021 17:08:51 +0100 Subject: [PATCH 3/3] tests: Add test feature_sighash_rangeproof.py --- test/functional/feature_sighash_rangeproof.py | 334 ++++++++++++++++++ test/functional/feature_txwitness.py | 5 +- test/functional/test_framework/address.py | 33 +- test/functional/test_framework/messages.py | 12 +- test/functional/test_framework/script.py | 31 +- test/functional/test_runner.py | 1 + 6 files changed, 406 insertions(+), 10 deletions(-) create mode 100755 test/functional/feature_sighash_rangeproof.py diff --git a/test/functional/feature_sighash_rangeproof.py b/test/functional/feature_sighash_rangeproof.py new file mode 100755 index 0000000000..2ca7aa2ae3 --- /dev/null +++ b/test/functional/feature_sighash_rangeproof.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +# Copyright (c) 2019 The Elements Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +""" +Test the post-dynafed elements-only SIGHASH_RANGEPROOF sighash flag. +""" + +import struct +from test_framework.test_framework import BitcoinTestFramework +from test_framework.script import ( + hash160, + SignatureHash, + SegwitVersion1SignatureHash, + SIGHASH_ALL, + SIGHASH_SINGLE, + SIGHASH_NONE, + SIGHASH_ANYONECANPAY, + SIGHASH_RANGEPROOF, + CScript, + CScriptOp, + FindAndDelete, + OP_CODESEPARATOR, + OP_CHECKSIG, + OP_DUP, + OP_EQUALVERIFY, + OP_HASH160, +) +from test_framework.key import ECKey + +from test_framework.messages import ( + CBlock, + CTransaction, + CTxOut, + FromHex, + WitToHex, + hash256, uint256_from_str, ser_uint256, ser_string, ser_vector +) + +from test_framework import util +from test_framework.util import ( + assert_equal, + hex_str_to_bytes, + assert_raises_rpc_error, +) + +from test_framework.blocktools import add_witness_commitment + +def get_p2pkh_script(pubkeyhash): + """Get the script associated with a P2PKH.""" + return CScript([CScriptOp(OP_DUP), CScriptOp(OP_HASH160), pubkeyhash, CScriptOp(OP_EQUALVERIFY), CScriptOp(OP_CHECKSIG)]) + +def SignatureHash_legacy(script, txTo, inIdx, hashtype): + """ + This method is identical to the regular `SignatureHash` method, + but without support for SIGHASH_RANGEPROOF. + So basically it's the old version of the method from before the + new sighash flag was added. + """ + HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + + if inIdx >= len(txTo.vin): + return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin))) + txtmp = CTransaction(txTo) + + for txin in txtmp.vin: + txin.scriptSig = b'' + txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR])) + + if (hashtype & 0x1f) == SIGHASH_NONE: + txtmp.vout = [] + + for i in range(len(txtmp.vin)): + if i != inIdx: + txtmp.vin[i].nSequence = 0 + + elif (hashtype & 0x1f) == SIGHASH_SINGLE: + outIdx = inIdx + if outIdx >= len(txtmp.vout): + return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout))) + + tmp = txtmp.vout[outIdx] + txtmp.vout = [] + for i in range(outIdx): + txtmp.vout.append(CTxOut(-1)) + txtmp.vout.append(tmp) + + for i in range(len(txtmp.vin)): + if i != inIdx: + txtmp.vin[i].nSequence = 0 + + if hashtype & SIGHASH_ANYONECANPAY: + tmp = txtmp.vin[inIdx] + txtmp.vin = [] + txtmp.vin.append(tmp) + + # sighash serialization is different from non-witness serialization + # do manual sighash serialization: + s = b"" + s += struct.pack(" 0 + self.nodes[0].sendtoaddress(addr, 1.0) + self.nodes[0].generate(1) + self.sync_all() + utxo = self.nodes[1].listunspent(1, 1, [addr])[0] + utxo_tx = FromHex(CTransaction(), self.nodes[1].getrawtransaction(utxo["txid"])) + utxo_spk = CScript(hex_str_to_bytes(utxo["scriptPubKey"])) + utxo_value = utxo_tx.vout[utxo["vout"]].nValue + + assert len(utxo["amountblinder"]) > 0 + sink_addr = self.nodes[2].getnewaddress() + unsigned_hex = self.nodes[1].createrawtransaction( + [{"txid": utxo["txid"], "vout": utxo["vout"]}], + {sink_addr: 0.9, "fee": 0.1} + ) + blinded_hex = self.nodes[1].blindrawtransaction(unsigned_hex) + blinded_tx = FromHex(CTransaction(), blinded_hex) + signed_hex = self.nodes[1].signrawtransactionwithwallet(blinded_hex)["hex"] + signed_tx = FromHex(CTransaction(), signed_hex) + + # Make sure that the tx the node produced is always valid. + test_accept = self.nodes[0].testmempoolaccept([signed_hex])[0] + assert test_accept["allowed"], "not accepted: {}".format(test_accept["reject-reason"]) + + # Prepare the keypair we need to re-sign the tx. + wif = self.nodes[1].dumpprivkey(addr) + privkey = ECKey() + privkey.set_wif(wif) + pubkey = privkey.get_pubkey() + + # Now we need to replace the signature with an equivalent one with the new sighash set. + hashtype = SIGHASH_ALL | SIGHASH_RANGEPROOF + if address_type == "legacy": + if sighash_rangeproof_aware: + (sighash, _) = SignatureHash(utxo_spk, blinded_tx, 0, hashtype) + else: + (sighash, _) = SignatureHash_legacy(utxo_spk, blinded_tx, 0, hashtype) + signature = privkey.sign_ecdsa(sighash) + chr(hashtype).encode('latin-1') + assert len(signature) <= 0xfc + assert len(pubkey.get_bytes()) <= 0xfc + signed_tx.vin[0].scriptSig = CScript( + struct.pack(" 0 + block.vtx.append(tx) + block.hashMerkleRoot = block.calc_merkle_root() + add_witness_commitment(block) + block.solve() + block_hex = WitToHex(block) + + # First test the testproposed block RPC. + if assert_valid: + self.nodes[0].testproposedblock(block_hex) + else: + assert_raises_rpc_error(-25, "block-validation-failed", self.nodes[0].testproposedblock, block_hex) + + # Then try submit the block and check if it was accepted or not. + pre = self.nodes[0].getblockcount() + self.nodes[0].submitblock(block_hex) + post = self.nodes[0].getblockcount() + + if assert_valid: + # assert block was accepted + assert pre < post + else: + # assert block was not accepted + assert pre == post + + def run_test(self): + util.node_fastmerkle = self.nodes[0] + ADDRESS_TYPES = ["legacy", "bech32", "p2sh-segwit"] + + # Different test scenarios. + # - before activation, using the flag is non-standard + # - before activation, using the flag but a non-flag-aware signature is legal + # - after activation, using the flag but a non-flag-aware signature is illegal + # - after activation, using the flag is standard (and thus also legal) + + # Mine come coins for node 0. + self.nodes[0].generate(200) + self.sync_all() + + # Ensure that if we use the SIGHASH_RANGEPROOF flag before it's activated, + # - the tx is not accepted in the mempool and + # - the tx is accepted if manually mined in a block + for address_type in ADDRESS_TYPES: + self.log.info("Pre-activation for {} address".format(address_type)) + tx = self.prepare_tx_signed_with_sighash(address_type, False) + self.assert_tx_standard(tx, False) + self.assert_tx_valid(tx, True) + + # Activate dynafed (nb of blocks taken from dynafed activation test) + self.nodes[0].generate(1006 + 1 + 144 + 144) + assert_equal(self.nodes[0].getblockchaininfo()["bip9_softforks"]["dynafed"]["status"], "active") + self.sync_all() + + # Test that the use of SIGHASH_RANGEPROOF is legal and standard + # after activation. + for address_type in ADDRESS_TYPES: + self.log.info("Post-activation for {} address".format(address_type)) + tx = self.prepare_tx_signed_with_sighash(address_type, True) + self.assert_tx_standard(tx, True) + self.assert_tx_valid(tx, True) + + # Ensure that if we then use the old sighash algorith that doesn't hash + # the rangeproofs, the signature is no longer valid. + for address_type in ADDRESS_TYPES: + self.log.info("Post-activation invalid sighash for {} address".format(address_type)) + tx = self.prepare_tx_signed_with_sighash(address_type, False) + self.assert_tx_standard(tx, False) + self.assert_tx_valid(tx, False) + +if __name__ == '__main__': + SighashRangeproofTest().main() + diff --git a/test/functional/feature_txwitness.py b/test/functional/feature_txwitness.py index 450bdd6850..63825707b3 100755 --- a/test/functional/feature_txwitness.py +++ b/test/functional/feature_txwitness.py @@ -14,7 +14,7 @@ """ -from test_framework.messages import CTransaction, CBlock, ser_uint256, FromHex, uint256_from_str, CTxOut, ToHex, CTxIn, COutPoint, OUTPOINT_ISSUANCE_FLAG, ser_string +from test_framework.messages import CTransaction, CBlock, ser_uint256, FromHex, uint256_from_str, CTxOut, ToHex, WitToHex, CTxIn, COutPoint, OUTPOINT_ISSUANCE_FLAG, ser_string from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, bytes_to_hex_str, hex_str_to_bytes, assert_raises_rpc_error, assert_greater_than from test_framework import util @@ -126,9 +126,6 @@ def test_transaction_serialization(self): def test_coinbase_witness(self): - def WitToHex(obj): - return bytes_to_hex_str(obj.serialize(with_witness=True)) - block = self.nodes[0].getnewblockhex() block_struct = FromHex(CBlock(), block) diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 0ea67e99ae..98c55f6ad7 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -5,7 +5,7 @@ """Encode and decode BASE58, P2PKH and P2SH addresses.""" from .script import hash256, hash160, sha256, CScript, OP_0 -from .util import bytes_to_hex_str, hex_str_to_bytes +from .util import bytes_to_hex_str, hex_str_to_bytes, assert_equal from . import segwit_addr @@ -29,7 +29,36 @@ def byte_to_base58(b, version): str = str[2:] return result -# TODO: def base58_decode + +def base58_to_byte(s): + """Converts a base58-encoded string to its data and version. + + Throws if the base58 checksum is invalid.""" + if not s: + return b'' + n = 0 + for c in s: + n *= 58 + assert c in chars + digit = chars.index(c) + n += digit + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = n.to_bytes((n.bit_length() + 7) // 8, 'big') + pad = 0 + for c in s: + if c == chars[0]: + pad += 1 + else: + break + res = b'\x00' * pad + res + + # Assert if the checksum is invalid + assert_equal(hash256(res[:-4])[:4], res[-4:]) + + return res[1:-4], int(res[0]) + def keyhash_to_p2pkh(hash, main = False): assert (len(hash) == 20) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 1de209a299..68438c7f90 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -183,6 +183,11 @@ def FromHex(obj, hex_string): def ToHex(obj): return bytes_to_hex_str(obj.serialize()) +# Convert a binary-serializable object to hex (eg for submission via RPC) +# This variant also serializes the witness. +def WitToHex(obj): + return bytes_to_hex_str(obj.serialize(with_witness=True)) + # Objects that map to bitcoind objects, which can be serialized/deserialized @@ -754,8 +759,11 @@ def serialize_with_witness(self): r += self.wit.serialize() return r - def serialize(self): - return self.serialize_with_witness() + def serialize(self, with_witness=True): + if with_witness: + return self.serialize_with_witness() + else: + return self.serialize_without_witness() def rehash(self): self.sha256 = None diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 3e8ad1af52..4ce67719de 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -7,7 +7,7 @@ This file is modified from python-bitcoinlib. """ -from .messages import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string, ser_vector +from .messages import CTransaction, CTxOut, sha256, hash256, uint256_from_str, ser_uint256, ser_string, ser_vector, ser_compact_size from binascii import hexlify import hashlib @@ -594,6 +594,8 @@ def GetSigOpCount(self, fAccurate): SIGHASH_NONE = 2 SIGHASH_SINGLE = 3 SIGHASH_ANYONECANPAY = 0x80 +# ELEMENTS: +SIGHASH_RANGEPROOF = 0x40 def FindAndDelete(script, sig): """Consensus critical, see FindAndDelete() in Satoshi codebase""" @@ -661,7 +663,15 @@ def SignatureHash(script, txTo, inIdx, hashtype): s = b"" s += struct.pack(" inIdx: + wit = txTo.wit.vtxoutwit[inIdx] + serialize_rangeproofs = ser_string(wit.vchRangeproof) + ser_string(wit.vchSurjectionproof) + hashRangeproofs = uint256_from_str(hash256(serialize_rangeproofs)) + ss = bytes() ss += struct.pack("