def test_invalid_schnorr(): sighash = bytes.fromhex("00" * 32) sig = ssa.serialize(*ssa._sign(sighash, 1)) pubkey = bytes_from_point(mult(1)) pubkey_hash = hash256(pubkey) script = Script([ [0x00, 0x00, pubkey], [0x01, 0x00, sig], [0x02, 0x00, pubkey_hash], # push pubkey_hash [0x03, 0x02, b"\x00"], # hash of pub key from unlocking script [0xFF, 0x01, b"\x03\x02"], # check equality [0xFF, 0x04, b"\xff"], # exit if not equal [0xFF, 0x03, b"\x00\x01"], # schnorr verify [0xFF, 0x04, b"\xff"], ] # exit if not equal]) # push signature ) assert script.execute(memory={0x100: sighash})
def test_flow_14(): """ This MUST NOT fail """ blockchain = Blockchain() coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_header = BlockHeader("00" * 32, generate_merkle_root([coinbase_0]), 0) origin = Block(origin_header, [coinbase_0]) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(5 * 10**9, Script()), TxOut(5 * 10**9, Script())], ) block_1_header = BlockHeader(origin.header.pow, generate_merkle_root([coinbase_1]), 0) block_1 = Block(block_1_header, [coinbase_1]) coinbase_2 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) block_2_header = BlockHeader(block_1.header.pow, generate_merkle_root([coinbase_2]), 0) block_2 = Block(block_2_header, [coinbase_2]) # we try adding the origin again, no problem, it is skipped assert blockchain.add_blocks([origin, block_1, block_2]) # invalid coinbase coinbase_3 = Tx( [ TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa")), TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb")), ], [TxOut(10**10, Script())], ) block_3_header = BlockHeader(block_1.header.pow, generate_merkle_root([coinbase_3]), 0) block_3 = Block(block_3_header, [coinbase_3]) assert not blockchain.add_blocks([origin, block_1, block_2, block_3]) reset_blockchain()
def lock_p2pk(pubkey): return Script( [ [0x02, OP_PUSHDATA, bytes.fromhex(pubkey)], # push pubkey [0xFF, OP_EQUAL, b"\x02\x00"], # check equality [0xFF, OP_VERIFY, b"\xff"], # exit if not equal [0xFF, OP_SHNORR_CHECKSIG, b"\x00\x01"], # schnorr verify [0xFF, OP_VERIFY, b"\xff"], # exit if not equal ] )
def lock_p2pkh(pubkey_hash): return Script( [ [0x02, OP_PUSHDATA, bytes.fromhex(pubkey_hash)], # push pubkey_hash [0x03, OP_HASH256, b"\x00"], # hash of pub key from unlocking script [0xFF, OP_EQUAL, b"\x03\x02"], # check equality [0xFF, OP_VERIFY, b"\xff"], # exit if not equal [0xFF, OP_SHNORR_CHECKSIG, b"\x00\x01"], # schnorr verify [0xFF, OP_VERIFY, b"\xff"], # exit if not equal ] )
def test_flow_4(): """ This MUST fail The second block transaction tries to spend more than its input """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(10**10, Script())], ) # overspends tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**10 + 1, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) with pytest.raises(Exception): blockchain._add_block(block_1) reset_blockchain()
def test_flow_6(): """ This MUST fail The block 1 coinbase collects more fees than it should """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(2 * 10**10 - 10**5 + 1, Script())], ) tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**5, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) with pytest.raises(Exception): blockchain._add_block(block_1) reset_blockchain()
def test_flow_9(): """ This MUST fail The transaction tries to spend an unkown utxo """ coinbase_0 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(10**10, Script())], ) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) # tries to spend the coinbase output at index 1, which doesn't exist tx = Tx([TxIn(OutPoint(coinbase_0.txid, 1), Script())], [TxOut(10**5, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) with pytest.raises(Exception): blockchain._add_block(block_1) reset_blockchain()
def test_flow_10(): """ This MUST fail Test of block reverse """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() rev_block_0 = blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(2 * 10**10 - 10**5, Script())], ) tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**5, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) blockchain._add_block(block_1) with pytest.raises(Exception): blockchain._reverse_block(rev_block_0) reset_blockchain()
def test_flow_3(): """ This MUST NOT fail We add two blocks to the blockchain, the second one with a transaction which spends the coinbase 0 output """ coinbase_0 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(10**10, Script())], ) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**5, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) blockchain._add_block(block_1) reset_blockchain()
def test_flow_2(): """ This MUST fail We add a block without a coinbase transaction """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**10, Script())]) block_1_header = BlockHeader(origin.header.pow, generate_merkle_root([tx]), 0) block_1 = Block(block_1_header, [tx]) with pytest.raises(Exception): # missing coinbase blockchain._add_block(block_1) reset_blockchain()
def test_validation(): tx_in = TxIn(OutPoint("ff" * 32, 0), Script()) assert tx_in.is_valid() tx_out = TxOut(10, Script()) assert tx_out.is_valid() tx_in_valid = TxIn(OutPoint("ff" * 32, 0), Script.from_hex("fffd" + "ff" * (256**2 - 3))) assert tx_in_valid.is_valid() tx_out_valid = TxOut(10, Script.from_hex("fffd" + "ff" * (256**2 - 3))) assert tx_out_valid.is_valid() tx_in_invalid = TxIn(OutPoint("ff" * 32, 0), Script.from_hex("fffe" + "ff" * (256**2 - 2))) assert not tx_in_invalid.is_valid() tx_out_invalid = TxOut(10, Script.from_hex("fffe" + "ff" * (256**2 - 2))) assert not tx_out_invalid.is_valid() tx = Tx([tx_in_invalid], [tx_out_valid]) assert not tx.is_valid() tx = Tx([tx_in_valid], [tx_out_invalid]) assert not tx.is_valid() tx_in = TxIn(OutPoint("ff" * 32, 0), Script()) tx_out = TxOut(10, Script()) tx = Tx([tx_in], [tx_out]) assert tx.is_valid() tx_in_2 = TxIn(OutPoint("ff" * 32, 0), Script()) tx = Tx([tx_in, tx_in_2], [tx_out]) assert not tx.is_valid()
def test_flow_16(): """ This MUST NOT fail """ blockchain = Blockchain() coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) coinbase_0.outputs[0].locking_script = lock_p2pkh( pubkey_hash_from_prvkey(1)) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) assert blockchain.add_blocks([origin]) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(5 * 10**9, Script()), TxOut(5 * 10**9, Script())], ) tx = Tx( [TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**10 - 100, Script()), TxOut(50, Script())], ) tx.inputs[0].unlocking_script = unlock_p2pkh(sighash_all(tx), 1) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) assert blockchain.add_blocks([block_1]) reset_blockchain()
class TxOut: value: int = 0 locking_script: Script = Script() def serialize(self): out = self.value.to_bytes(8, "big") script_bytes = self.locking_script.serialize() out += len(script_bytes).to_bytes(2, "big") + script_bytes return out @classmethod def deserialize(cls, data): out = int.from_bytes(data[:8], "big") script_len = int.from_bytes(data[8:10], "big") locking_script = Script.deserialize(data[10:10 + script_len]) return TxOut(out, locking_script) def is_valid(self): if not self.locking_script.is_valid(): return False if self.value < 0 or self.value >= 256**8: return False return True
def test_double_spend(): coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(10**10, Script())], ) tx_1 = Tx( [TxIn(OutPoint("aa" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) tx_2 = Tx( [TxIn(OutPoint("aa" * 32, 0), Script.from_hex("00030000cc"))], [TxOut(10**10, Script())], ) header = BlockHeader() block = Block(header, [coinbase_1, tx_1, tx_2]) header.merkle_root = generate_merkle_root(block.transactions) header.previous_pow = "00" * 32 assert not block.is_valid()
def test_flow_8(): """ This MUST NOT fail Test of block reverse """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() rev_origin = blockchain._add_block(origin) old_utxo_list = blockchain.main_utxo_set.get_utxo_list() coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(2 * 10**10 - 10**5, Script())], ) tx = Tx([TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**5, Script())]) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) rev_block = blockchain._add_block(block_1) assert not blockchain.main_utxo_set.get_utxo_list() == old_utxo_list blockchain._reverse_block(rev_block) assert blockchain.main_utxo_set.get_utxo_list() == old_utxo_list with pytest.raises(Exception): blockchain._reverse_block(rev_block) blockchain._reverse_block(rev_origin) assert blockchain.main_utxo_set.get_utxo_list() == [] reset_blockchain()
def deserialize(cls, data): prevout = OutPoint.deserialize(data) script_len = int.from_bytes(data[34:36], "big") unlocking_script = Script.deserialize(data[36:36 + script_len]) return TxIn(prevout, unlocking_script)
def test_coinbase(): tx_in = TxIn(OutPoint("00" * 32, 0), Script()) assert tx_in.is_coinbase()
def test_flow_12(): """ This MUST NOT fail """ blockchain = Blockchain() coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(5 * 10**9, Script()), TxOut(5 * 10**9, Script())], ) tx = Tx( [TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**10 - 100, Script()), TxOut(50, Script())], ) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) coinbase_2 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) tx_2 = Tx([TxIn(OutPoint(tx.txid, 0), Script())], [TxOut(10**10 - 200, Script())]) tx_3 = Tx( [ TxIn(OutPoint(coinbase_1.txid, 0), Script()), TxIn(OutPoint(coinbase_1.txid, 1), Script()), TxIn(OutPoint(tx.txid, 1), Script()), ], [TxOut(10**10 + 50, Script())], ) block_2_transactions = [coinbase_2, tx_2, tx_3] block_2_header = BlockHeader(block_1.header.pow, generate_merkle_root(block_2_transactions), 0) block_2 = Block(block_2_header, block_2_transactions) # we try adding the origin again, no problem, it is skipped assert blockchain.add_blocks([origin, block_1, block_2]) coinbase_3 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000cc"))], [TxOut(10**10, Script())], ) # tx_4 speds the same inputs as tx_2, but it is in a different chain so it is OK tx_4 = Tx( [TxIn(OutPoint(tx.txid, 0), Script.from_hex("00030000dd"))], [TxOut(10**10 - 200, Script())], ) block_3_transactions = [coinbase_3, tx_4] block_3_header = BlockHeader(block_1.header.pow, generate_merkle_root(block_3_transactions), 0) block_3 = Block(block_3_header, block_3_transactions) block_3.header.nonce = calculate_nonce(block_3_header, 100) coinbase_4 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000ee"))], [TxOut(10**10, Script())], ) block_4_transactions = [coinbase_4] # invalid reference, must be block_3 header, not block_2 block_4_header = BlockHeader(block_2.header.pow, generate_merkle_root(block_4_transactions), 0) block_4 = Block(block_4_header, block_4_transactions) assert blockchain.add_blocks([block_3, block_4]) assert blockchain.get_last_blocks()[0][0] == block_3.header.pow block_4.header = BlockHeader(block_3.header.pow, generate_merkle_root(block_4_transactions), 0) assert blockchain.add_blocks([block_3, block_4]) assert blockchain.get_last_blocks()[0][0] == block_4.header.pow reset_blockchain()
def test_flow_11(): """ This MUST NOT fail """ coinbase_0 = Tx([TxIn(OutPoint("00" * 32, 0), Script())], [TxOut(10**10, Script())]) origin_transactions = [coinbase_0] origin_header = BlockHeader("00" * 32, generate_merkle_root(origin_transactions), 0) origin = Block(origin_header, origin_transactions) blockchain = Blockchain() blockchain._add_block(origin) coinbase_1 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000aa"))], [TxOut(5 * 10**9, Script()), TxOut(5 * 10**9, Script())], ) tx = Tx( [TxIn(OutPoint(coinbase_0.txid, 0), Script())], [TxOut(10**10 - 100, Script()), TxOut(50, Script())], ) block_1_transactions = [coinbase_1, tx] block_1_header = BlockHeader(origin.header.pow, generate_merkle_root(block_1_transactions), 0) block_1 = Block(block_1_header, block_1_transactions) blockchain._add_block(block_1) coinbase_2 = Tx( [TxIn(OutPoint("00" * 32, 0), Script.from_hex("00030000bb"))], [TxOut(10**10, Script())], ) tx_2 = Tx([TxIn(OutPoint(tx.txid, 0), Script())], [TxOut(10**10 - 200, Script())]) tx_3 = Tx( [ TxIn(OutPoint(coinbase_1.txid, 0), Script()), TxIn(OutPoint(coinbase_1.txid, 1), Script()), TxIn(OutPoint(tx.txid, 1), Script()), ], [TxOut(10**10 + 50, Script())], ) block_2_transactions = [coinbase_2, tx_2, tx_3] block_2_header = BlockHeader(block_1.header.pow, generate_merkle_root(block_2_transactions), 0) block_2 = Block(block_2_header, block_2_transactions) blockchain._add_block(block_2) reset_blockchain()
def test_invalid_outpoint_index(): tx_in = TxIn(OutPoint("00" * 32, -1), Script()) assert not tx_in.is_valid() tx_in = TxIn(OutPoint("00" * 32, 0xFFFF + 1), Script()) assert not tx_in.is_valid()
def test_invalid_output_value(): tx_out = TxOut(-1, Script()) assert not tx_out.is_valid() tx_out = TxOut(0xFFFFFFFFFFFFFFFF + 1, Script()) assert not tx_out.is_valid()
def unlock_p2pkh(sighash, prvkey): sig = ssa.serialize(*ssa._sign(sighash, prvkey)) pubkey = bytes_from_point(mult(prvkey)) return Script( [[0x00, OP_PUSHDATA, pubkey], [0x01, OP_PUSHDATA, sig]] ) # push signature
def deserialize(cls, data): out = int.from_bytes(data[:8], "big") script_len = int.from_bytes(data[8:10], "big") locking_script = Script.deserialize(data[10:10 + script_len]) return TxOut(out, locking_script)
def get_utxo(self, id): self.cursor.execute("SELECT * FROM utxo WHERE id = ?", (id, )) utxo = self.cursor.fetchall() return TxOut(utxo[0][1], Script.deserialize( utxo[0][2])) if utxo else None
def test_serialization(): assert Script() == Script.from_hex("") assert Script.from_hex(Script().hex) == Script()