diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 6ebc84cee..ac2deaaa7 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -1,156 +1,159 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Utilities for manipulating blocks and transactions.""" from .mininode import * -from .script import CScript, OP_TRUE, OP_CHECKSIG, OP_RETURN, OP_PUSHDATA2 +from .script import CScript, OP_TRUE, OP_CHECKSIG, OP_RETURN, OP_PUSHDATA2, OP_DUP, OP_HASH160, OP_EQUALVERIFY from .mininode import CTransaction, CTxOut, CTxIn from .util import satoshi_round # Create a block (with regtest difficulty) def create_block(hashprev, coinbase, nTime=None): block = CBlock() if nTime is None: import time block.nTime = int(time.time() + 600) else: block.nTime = nTime block.hashPrevBlock = hashprev block.nBits = 0x207fffff # Will break after a difficulty adjustment... block.vtx.append(coinbase) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() return block def serialize_script_num(value): r = bytearray(0) if value == 0: return r neg = value < 0 absvalue = -value if neg else value while (absvalue): r.append(int(absvalue & 0xff)) absvalue >>= 8 if r[-1] & 0x80: r.append(0x80 if neg else 0) elif neg: r[-1] |= 0x80 return r # Create a coinbase transaction, assuming no miner fees. # If pubkey is passed in, the coinbase output will be a P2PK output; # otherwise an anyone-can-spend output. def create_coinbase(height, pubkey=None): coinbase = CTransaction() coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), ser_string(serialize_script_num(height)), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 50 * COIN halvings = int(height / 150) # regtest coinbaseoutput.nValue >>= halvings if (pubkey != None): coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG]) else: coinbaseoutput.scriptPubKey = CScript([OP_TRUE]) coinbase.vout = [coinbaseoutput] # Make sure the coinbase is at least 100 bytes coinbase_size = len(coinbase.serialize()) if coinbase_size < 100: coinbase.vin[0].scriptSig += b'x' * (100 - coinbase_size) coinbase.calc_sha256() return coinbase # Create a transaction. # If the scriptPubKey is not specified, make it anyone-can-spend. def create_transaction(prevtx, n, sig, value, scriptPubKey=CScript()): tx = CTransaction() assert(n < len(prevtx.vout)) tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), sig, 0xffffffff)) tx.vout.append(CTxOut(value, scriptPubKey)) tx.calc_sha256() return tx def get_legacy_sigopcount_block(block, fAccurate=True): count = 0 for tx in block.vtx: count += get_legacy_sigopcount_tx(tx, fAccurate) return count def get_legacy_sigopcount_tx(tx, fAccurate=True): count = 0 for i in tx.vout: count += i.scriptPubKey.GetSigOpCount(fAccurate) for j in tx.vin: # scriptSig might be of type bytes, so convert to CScript for the moment count += CScript(j.scriptSig).GetSigOpCount(fAccurate) return count # Helper to create at least "count" utxos # Pass in a fee that is sufficient for relay and mining new transactions. def create_confirmed_utxos(fee, node, count, age=101): to_generate = int(0.5 * count) + age while to_generate > 0: node.generate(min(25, to_generate)) to_generate -= 25 utxos = node.listunspent() iterations = count - len(utxos) addr1 = node.getnewaddress() addr2 = node.getnewaddress() if iterations <= 0: return utxos for i in range(iterations): t = utxos.pop() inputs = [] inputs.append({"txid": t["txid"], "vout": t["vout"]}) outputs = {} send_value = t['amount'] - fee outputs[addr1] = satoshi_round(send_value / 2) outputs[addr2] = satoshi_round(send_value / 2) raw_tx = node.createrawtransaction(inputs, outputs) signed_tx = node.signrawtransaction(raw_tx)["hex"] node.sendrawtransaction(signed_tx) while (node.getmempoolinfo()['size'] > 0): node.generate(1) utxos = node.listunspent() assert(len(utxos) >= count) return utxos def send_big_transactions(node, utxos, num, fee_multiplier): + from .cashaddr import decode txids = [] padding = "1"*(512*127) + addrHash = decode(node.getnewaddress())[2] for _ in range(num): ctx = CTransaction() utxo = utxos.pop() txid = int(utxo['txid'], 16) ctx.vin.append(CTxIn(COutPoint(txid, int(utxo["vout"])), b"")) ctx.vout.append(CTxOut(0, CScript( [OP_RETURN, OP_PUSHDATA2, len(padding), bytes(padding, 'utf-8')]))) ctx.vout.append( - CTxOut(int(satoshi_round(utxo['amount']*COIN)), CScript([OP_TRUE]))) + CTxOut(int(satoshi_round(utxo['amount']*COIN)), + CScript([OP_DUP, OP_HASH160, addrHash, OP_EQUALVERIFY, OP_CHECKSIG]))) # Create a proper fee for the transaction to be mined ctx.vout[1].nValue -= int(fee_multiplier * node.calculate_fee(ctx)) signresult = node.signrawtransaction( ToHex(ctx), None, None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True) txids.append(txid) return txids diff --git a/test/functional/test_framework/cashaddr.py b/test/functional/test_framework/cashaddr.py new file mode 100644 index 000000000..324057d0c --- /dev/null +++ b/test/functional/test_framework/cashaddr.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 Pieter Wuille, Shammah Chancellor, Neil Booth +# +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Originally taken from Electron-Cash: +# https://raw.githubusercontent.com/fyookball/electrum/master/lib/cashaddr.py +# +_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" + + +def _polymod(values): + """Internal function that computes the cashaddr checksum.""" + c = 1 + for d in values: + c0 = c >> 35 + c = ((c & 0x07ffffffff) << 5) ^ d + if (c0 & 0x01): + c ^= 0x98f2bc8e61 + if (c0 & 0x02): + c ^= 0x79b76d99e2 + if (c0 & 0x04): + c ^= 0xf33e5fb3c4 + if (c0 & 0x08): + c ^= 0xae2eabe2a8 + if (c0 & 0x10): + c ^= 0x1e4f43e470 + retval = c ^ 1 + return retval + + +def _prefix_expand(prefix): + """Expand the prefix into values for checksum computation.""" + retval = bytearray(ord(x) & 0x1f for x in prefix) + # Append null separator + retval.append(0) + return retval + + +def _create_checksum(prefix, data): + """Compute the checksum values given prefix and data.""" + values = _prefix_expand(prefix) + data + bytes(8) + polymod = _polymod(values) + # Return the polymod expanded into eight 5-bit elements + return bytes((polymod >> 5 * (7 - i)) & 31 for i in range(8)) + + +def _convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = bytearray() + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + + if pad and bits: + ret.append((acc << (tobits - bits)) & maxv) + + return ret + + +def _pack_addr_data(kind, addr_hash): + """Pack addr data with version byte""" + version_byte = kind << 3 + + offset = 1 + encoded_size = 0 + if len(addr_hash) >= 40: + offset = 2 + encoded_size |= 0x04 + encoded_size |= (len(addr_hash) - 20 * offset) // (4 * offset) + + # invalid size? + if ((len(addr_hash) - 20 * offset) % (4 * offset) != 0 + or not 0 <= encoded_size <= 7): + raise ValueError('invalid address hash size {}'.format(addr_hash)) + + version_byte |= encoded_size + + data = bytes([version_byte]) + addr_hash + return _convertbits(data, 8, 5, True) + + +def _decode_payload(addr): + """Validate a cashaddr string. + + Throws CashAddr.Error if it is invalid, otherwise returns the + tuple + + (prefix, payload) + + without the checksum. + """ + lower = addr.lower() + if lower != addr and addr.upper() != addr: + raise ValueError('mixed case in address: {}'.format(addr)) + + parts = lower.split(':', 1) + if len(parts) != 2: + raise ValueError("address missing ':' separator: {}".format(addr)) + + prefix, payload = parts + if not prefix: + raise ValueError('address prefix is missing: {}'.format(addr)) + if not all(33 <= ord(x) <= 126 for x in prefix): + raise ValueError('invalid address prefix: {}'.format(prefix)) + if not (8 <= len(payload) <= 124): + raise ValueError('address payload has invalid length: {}' + .format(len(addr))) + try: + data = bytes(_CHARSET.find(x) for x in payload) + except ValueError: + raise ValueError('invalid characters in address: {}' + .format(payload)) + + if _polymod(_prefix_expand(prefix) + data): + raise ValueError('invalid checksum in address: {}'.format(addr)) + + if lower != addr: + prefix = prefix.upper() + + # Drop the 40 bit checksum + return prefix, data[:-8] + +# +# External Interface +# + + +PUBKEY_TYPE = 0 +SCRIPT_TYPE = 1 + + +def decode(address): + '''Given a cashaddr address, return a tuple + + (prefix, kind, hash) + ''' + if not isinstance(address, str): + raise TypeError('address must be a string') + + prefix, payload = _decode_payload(address) + + # Ensure there isn't extra padding + extrabits = len(payload) * 5 % 8 + if extrabits >= 5: + raise ValueError('excess padding in address {}'.format(address)) + + # Ensure extrabits are zeros + if payload[-1] & ((1 << extrabits) - 1): + raise ValueError('non-zero padding in address {}'.format(address)) + + decoded = _convertbits(payload, 5, 8, False) + version = decoded[0] + addr_hash = bytes(decoded[1:]) + size = (version & 0x03) * 4 + 20 + # Double the size, if the 3rd bit is on. + if version & 0x04: + size <<= 1 + if size != len(addr_hash): + raise ValueError('address hash has length {} but expected {}' + .format(len(addr_hash), size)) + + kind = version >> 3 + if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): + raise ValueError('unrecognised address type {}'.format(kind)) + + return prefix, kind, addr_hash + + +def encode(prefix, kind, addr_hash): + """Encode a cashaddr address without prefix and separator.""" + if not isinstance(prefix, str): + raise TypeError('prefix must be a string') + + if not isinstance(addr_hash, (bytes, bytearray)): + raise TypeError('addr_hash must be binary bytes') + + if kind not in (SCRIPT_TYPE, PUBKEY_TYPE): + raise ValueError('unrecognised address type {}'.format(kind)) + + payload = _pack_addr_data(kind, addr_hash) + checksum = _create_checksum(prefix, payload) + return ''.join([_CHARSET[d] for d in (payload + checksum)]) + + +def encode_full(prefix, kind, addr_hash): + """Encode a full cashaddr address, with prefix and separator.""" + return ':'.join([prefix, encode(prefix, kind, addr_hash)])