diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py index efe8ae4..48f54c1 100644 --- a/pybtc/blockchain.py +++ b/pybtc/blockchain.py @@ -260,7 +260,6 @@ class Transaction(): self.flag = flag self.valid = True self.lock = False - self.orphaned = False self.in_sum = None self.tx_fee = None self.version = version @@ -272,7 +271,9 @@ class Transaction(): if self.tx_in: self.coinbase = self.tx_in[0].coinbase else: - self.coinbase = False + self.coinbase = None + if self.coinbase: + self.whash = b"\x00" * 32 self.double_spend = 0 self.data = None self.ip = None @@ -294,8 +295,17 @@ class Transaction(): self.recalculate_txid() def recalculate_txid(self): - self.hash = double_sha256(self.serialize(segwit=False)) - self.whash = double_sha256(self.serialize(segwit=True)) + self.tx_in_count = len(self.tx_in) + self.tx_out_count = len(self.tx_out) + t = self.serialize(segwit=False) + t2 = self.serialize(segwit=True) + self.hash = double_sha256(t) + if self.coinbase: + self.whash = b"\x00" * 32 + else: + self.whash = double_sha256(t2) + self.size = len(t) + self.vsize = math.ceil((self.size * 3 + self.size) / 4) def add_input(self, tx_hash, output_number, sequence = 0xffffffff, @@ -541,7 +551,7 @@ class Transaction(): break else: wtx_id = tx_id - vsize = math.ceil((len(raw_tx) * 3 + size) / 4) + vsize = math.ceil((size * 3 + size) / 4) else: stream.seek(start) marker = b"\x00" @@ -555,7 +565,7 @@ class Transaction(): stream.seek(start) data = stream.read(size) tx_id = double_sha256(data) - wtx_id = None + wtx_id = tx_id vsize = size return cls(version, tx_in, tx_out, lock_time, @@ -566,31 +576,75 @@ class Transaction(): class Block(): def __init__(self, version, prev_block, merkle_root, - timestamp, bits, nonce, txs, block_size,hash=None): + timestamp, bits, nonce, txs, block_size, hash = None, header = None): self.hash = hash + self.header = header self.version = version self.prev_block = prev_block self.merkle_root = merkle_root self.timestamp = timestamp self.bits = bits self.nonce = nonce - self.txs = txs - self.block_size = block_size - self.height = None - self.id = None - self.chain = None - self.amount = 0 - self.mountpoint = None - self.side_branch_set = None + self.transactions = txs self.tx_hash_list = list() - self.op_sig_count = 0 + self.size = block_size + self.weight = block_size + self.height = None + self.amount = 0 + self.fee = 0 + self.sigop = 0 for t in txs: - if t.hash in txs: + if t.hash in self.tx_hash_list: raise Exception("CVE-2012-2459") # merkle tree malleability - self.op_sig_count += t.op_sig_count self.tx_hash_list.append(t.hash) self.target = None self.fee = 0 + self.witness_root_hash = None + if txs[0].coinbase: + if version > 1: + self.height = int.from_bytes(txs[0].tx_in[0].sig_script.raw[1:5], "little") + self.coinbase = txs[0].tx_in[0].sig_script.raw[5:] + else: + self.coinbase = txs[0].tx_in[0].sig_script.raw + try: + for out in txs[0].tx_out: + if out.pk_script.ntype == 3: + if b'\xaa!\xa9\xed' == out.pk_script.data[:4]: + self.witness_root_hash = out.pk_script.data[4:36] + except: + pass + + def calculate_commitment(self): + wtxid_list = [b"\x00" * 32,] + for tx in self.transactions[0 if not self.transactions[0].coinbase else 1:]: + wtxid_list.append(tx.whash) + return double_sha256(merkleroot(wtxid_list[::-1]) + b"\x00" * 32) + + def create_coinbase_transaction(self, block_height, outputs, coinbase_message = b"", insert = True): + tx = Transaction(version = 1,tx_in = [], tx_out = [], witness= [] ) + coinbase = b'\x03' + block_height.to_bytes(4,'little') + coinbase_message + if len(coinbase) > 100: + raise Exception("coinbase is to long") + coinbase_input = Input((b'\x00'*32 ,0xffffffff), coinbase, 0xffffffff) + tx.tx_in = [coinbase_input] + commitment = self.calculate_commitment() + for o in outputs: + if type(o[1]) == str: + tx.tx_out.append(Output(o[0], address2script(o[1]))) + else: + tx.tx_out.append(Output(o[0], o[1])) + tx.tx_out.append(Output(0, b'j$\xaa!\xa9\xed' + commitment)) + tx.witness = [Witness([b'\x00'*32])] + tx.recalculate_txid() + if insert: + if self.transactions[0].coinbase: + self.transactions[0] = tx + else: + self.transactions.insert(0,tx) + return tx + + + @classmethod def deserialize(cls, stream): @@ -598,7 +652,7 @@ class Block(): header = stream.read(80) stream.seek(-80, 1) kwargs = { - 'hash': hashlib.sha256(hashlib.sha256(header).digest()).digest(), + 'hash': double_sha256(header), 'version': int.from_bytes(stream.read(4), 'little'), 'prev_block': stream.read(32), 'merkle_root': stream.read(32), @@ -606,7 +660,8 @@ class Block(): 'bits': int.from_bytes(stream.read(4), 'little'), 'nonce': int.from_bytes(stream.read(4), 'little'), 'txs': read_var_list(stream, Transaction), - 'block_size': stream.tell() + 'block_size': stream.tell(), + 'header': header } return cls(**kwargs) diff --git a/pybtc/tools.py b/pybtc/tools.py index 56b6c93..06927bc 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -6,7 +6,7 @@ import struct import hmac from secp256k1 import lib as secp256k1 from secp256k1 import ffi - +from .opcodes import * SIGHASH_ALL = 0x00000001 SIGHASH_NONE = 0x00000002 @@ -219,6 +219,15 @@ def address_type(address): return 'P2PKH' return 'UNKNOWN' +def address2script(address): + if address[0] in ('2', '3'): + return OPCODE["OP_HASH160"] + b'\x14' + address2hash160(address) + OPCODE["OP_EQUAL"] + if address[0] in ('1', 'm', 'n'): + return OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + b'\x14' + \ + address2hash160(address) + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] + raise Exception("Unknown address") + + def pub2address(pubkey, testnet = False, p2sh = False): h = hash160(pubkey) return hash1602address(h, testnet = testnet, p2sh = p2sh) diff --git a/test/__init__.py b/test/__init__.py index 63ac9cc..f34f15c 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,6 +1,7 @@ -from .script_deserialize import * -from .hash_functions import * -from .address_functions import * -from .transaction_deserialize import * -from .sighash import * -from .ecdsa import * \ No newline at end of file +# from .script_deserialize import * +# from .hash_functions import * +# from .address_functions import * +# from .transaction_deserialize import * +# from .sighash import * +# from .ecdsa import * +from .block import * \ No newline at end of file diff --git a/test/block.py b/test/block.py new file mode 100644 index 0000000..69c8593 --- /dev/null +++ b/test/block.py @@ -0,0 +1,28 @@ +import unittest +from pybtc import blockchain +from binascii import unhexlify +from pybtc import rh2s + +block_a = """00000020358d4156e298db6a1158e7796d511e7c3076ab64fd10bed03c1600000000000063b17c526b14f986fc73334d0879fb052daffdd33c1e280baa52d13b67aa98ab9c6d7d5a402b371a9465559c05010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3103cf891300049c6d7d5a04545d002b0caff5545a289c0100000000000a636b706f6f6c0d2f6d696e656420627920636b2fffffffff02d82cab04000000001976a9147c16d7bcff670c37bab219e9407d75ceafaa34d488ac0000000000000000266a24aa21a9edf97be29b2cda15085a4bac02d8f9bcc6273744287c41e353ba394a6394f68799012000000000000000000000000000000000000000000000000000000000000000000000000001000000000101989da20569b2aca132eb0f8e0090c370d41edf59cf6e1520a8aabb2d1371378d0100000017160014a9389bcb70e54d474465d7ce90bcd8793cf3a9e2ffffffff0280778e060000000017a914a640d6d68fa719d2755bda2552c3e6958fc78bc087fdf684f61500000017a914ce175699405d5329596d3885e6aff61dc22b657d87024830450221009656689a849fe0cbc588442d56418270d21537e6c64d80aad394b57a182c246002200c94e5b11331709dd6b5a13b6643baee1e65537cf601ae5ccc70309185c5f423012102108261b4b61e9cb9bc54049a230e1509513d5a51881db0763cc9ffc50ca50b97000000000100000000010192ef9685c684dc644de8d4763ebf7072c656a1b6595fef9070a959ce42db970e0100000017160014a5b77a54a03531774805d7e97691b0828b5bf7e3ffffffff0280a4bf07000000001976a914efa24b963bc2f47ee35e1d43842668cc12eb3b0288ac42e8adf11500000017a9143c29761703c645d0e2c68dc22a5f56b3c60db37a870247304402205c4952fabdfd3ee859e7fc486762689456580f0ba158179ebfbba630acfa1b0e02205929178f15509b400fcd426d3e17c3e75ee72fd19bf80447ce04cc9297fbe0fb0121032455df65ea00ef726cc39a21f022ab459bb459ba19be604073c33fed2b16836b0000000001000000018cceaf4a6cc6745bc7387e12a69194c3f0f84ca677ea84fd90a27994a0023e5200000000fd660100473044022010a7b12a6c46b59ab25aa65750c51a14a997c83141f9ae5d7e788d473eeb6d3b02204b9087fcdda3317c35a53868db9bf3773e35c0a1b1a3e4c195b18455d915b7e70147304402202d8a8becc14d18e00305d8af81207fd15b53b2d5b0a26ed8e9fbf4dceaee78220220255302d8b6ed246e698887fb1b0550d6e8127ee1d1d5014eea5c779c2b84dd9401473044022032a19463e29fbbf72c198031c4a89eb48ca1556a1fe75c1e96b5a3ed4109023c02202f8db619c12256bbd5122c486a1843f60adba1a400ad7e5e082306e068659024014c8b5321039f37268e558566e5a816bdd32e2fdb46ed0106ead3bbeedd2e56d4ef24803dc02102718a52e82f51fcf0cda12ee34699bee4575430d6c7bb7503a7f243f0d0c3a85521035f62cccfb1108526ce32db4ffecb1d1d1be118899529b6a0697b81b2109994b8210206e93b92d95a053ad1c1bd43a52368c7edeb446caad63506621618fddd75a0a454aeffffffff02e0d40e000000000017a914a1726229f16d5ddf5bb6712b68003d7fed79f0be870000000000000000326a3045584f4e554d0100580f0200000000003080cc90dc9e59cd41664653b6df7419dc50dcbede2ec85c948890fc310a858e000000000100000001f62bf88577e7596b32246788b31119632da7db7df143ac0dd7716f66e1f7b5f300000000fd67010047304402201b0df8489f2ff8258d6d7f81e6a4bae875206d3d9634abf4bdcb26d93b4919af02206b8a05945d029afa84eb13b09667596657edacdc0994aac7d4579c932517661e0147304402206b18469f261a2b9ba044527a99ce5284575031af2b272f68ac284b5e1fc61e7302200269c2240c264a466c2dac545aee38b4b58fd9556a61d2b742dee032670d42d301483045022100e6a9ab3f91e6391ce20194084911e28ac1004f7970d7fc3b2d2b05325a048fb20220433616d1053d61780812cf7638ce66d4fbff85aab2d2761cea583b81bd8ee004014c8b532102949dcb6f05bc5b1fb8855aa0cbbd883edc70b1785a492bf1fa0c0287a8befc3321023193f287b227fb572f63d11aaa03a05fafc47f29caa5f8bb350359bc24191cee2103cd4aa05c4952acf1c29185df960e68787ac59aa30d6e748ff833522e4d11c8e3210221b476c94496bef7c4ace3af3e9c7bc805587f67870c425cafc6dc4a7bafe74754aeffffffff02f8530e000000000017a914b68c3488d295902dd465b486cc51be2d29af2408870000000000000000326a3045584f4e554d0100e0930400000000004892a52323d0775a566e44467b4f7aa1b0723cc98ec9e9e3f3ba266d658c8cb000000000""" +block_b = "00000020770aa9d59da8a8d897ed2aa023755d774c859bd7740e3fa6ef11000000000000a1bdc212eaae007129353da83ba81c3361681fcd526a9543f6b0210b4e595d1a5d8a7d5a402b371ae4be5a2202010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff2b03df891300045d8a7d5a0456360e330c64887d5a73887d5a410f00000a636b706f6f6c07746573746e6574ffffffff02699ea904000000001976a91422ba040f820174c8ad8d3b568c2801390a81fa8c88ac0000000000000000266a24aa21a9ed15e05d39fb8de40813e91f4029eb0c0bdfd27df73bca3253b4c738c1cd8b374e012000000000000000000000000000000000000000000000000000000000000000000000000001000000000101875e650b1096e216784b141f98318ec9c6f4093cade620ffad8c753304b6028101000000171600142392b05ad19a4706f1a5c3feb1f37690b964cab5ffffffff0263190000000000001976a914eb2d08a5e7839661eaf02a39289f991ad8b44d2688ac3d9683d71500000017a914a10bf0bf639376e87a6b94559a67a328e88484f28702483045022100bd9b6f580242576d069b6e0cda91260166b969fbe8748a7238364324c669031a022051e602d7b0de61442d064baaef85a355667ffcb89b45aa7fa0f3eacf68a1aa8401210351286a6b3f8f603f35e8bdcda042973fcfe31385ef436e04c2c733fe8dead8d700000000" +class BlockDeserializeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting Block class deserialization:\n") + def test_block_deserialize(self): + block = blockchain.Block.deserialize(block_a) + print(rh2s(block.transactions[0].hash)) + cbm = block.coinbase + cb = block.transactions.pop(0) + outs = [] + for out in cb.tx_out: + if out.pk_script.ntype != 3: + outs.append((out.value, out.pk_script.raw)) + cb2 = block.create_coinbase_transaction(block.height, outs, cbm ) + print(cb.serialize(hex=True)) + print(cb2.serialize(hex=True)) + print(cb.serialize(segwit = 0,hex=True) == cb2.serialize(segwit = 0 ,hex=True)) + print(cb.serialize(segwit = 0,hex=True) == cb2.serialize(segwit = 0 ,hex=True)) + print(rh2s(cb.hash)) + print(rh2s(cb2.hash)) +