From a4050459d94cb2e7962cd440cc6ae4df5383cd03 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Mon, 28 May 2018 13:59:35 +0400 Subject: [PATCH 01/13] version 2.0 draft --- pybtc/address.py | 106 ++++++ pybtc/block.py | 0 pybtc/blockchain.py | 325 ++--------------- pybtc/constants.py | 1 + pybtc/encode.py | 2 - pybtc/hash.py | 1 - pybtc/opcodes.py | 297 +++++++-------- pybtc/tools.py | 401 ++++++++++++++------ pybtc/transaction.py | 225 ++++++++++++ tests/test/__init__.py | 14 +- tests/test/address_class.py | 59 +++ tests/test/address_functions.py | 505 +++++++++++++++++++++++--- tests/test/block.py | 5 + tests/test/create_transaction.py | 6 +- tests/test/ecdsa.py | 8 +- tests/test/hash_functions.py | 4 + tests/test/integer.py | 115 ++++++ tests/test/raw_block.txt | 1 + tests/test/script_deserialize.py | 10 +- tests/test/sighash.py | 6 +- tests/test/transaction_deserialize.py | 62 +++- 21 files changed, 1526 insertions(+), 627 deletions(-) create mode 100644 pybtc/address.py create mode 100644 pybtc/block.py create mode 100644 pybtc/transaction.py create mode 100644 tests/test/address_class.py create mode 100644 tests/test/integer.py create mode 100644 tests/test/raw_block.txt diff --git a/pybtc/address.py b/pybtc/address.py new file mode 100644 index 0000000..0828877 --- /dev/null +++ b/pybtc/address.py @@ -0,0 +1,106 @@ +from .tools import * + +class PrivateKey(): + def __init__(self, key=None, compressed=True, testnet=False): + if key is None: + self.compressed = compressed + self.testnet = testnet + self.raw_key = create_private_key() + else: + if type(key) == str: + try: + key = unhexlify(key) + except: + pass + if type(key) == bytes: + assert len(key) == 32 + self.raw_key = key + self.compressed = compressed + self.testnet = testnet + return + assert type(key) == str + self.raw_key = WIF_to_private_key(key) + if key[0] in (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, + TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX): + self.compressed = False + else: + self.compressed = True + if key[0] in (TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, + TESTNET_PRIVATE_KEY_COMPRESSED_PREFIX): + self.testnet = True + else: + self.testnet = False + + def hex(self): + return hexlify(self.raw_key).decode() + + def WIF(self, compressed=None, testnet=None): + if compressed is None: + compressed = self.compressed + if testnet is None: + testnet = self.testnet + return private_key_to_WIF(self.raw_key, compressed, testnet) + + +class PublicKey(): + def __init__(self, key=None): + if type(key) == str: + try: + key = unhexlify(key) + except: + pass + if type(key) == PrivateKey: + key = private_to_public_key(key.raw_key, + compressed=key.compressed) + assert type(key) == bytes + assert len(key) == 33 or len(key) == 65 + if len(key) == 33: + self.compressed = True + else: + self.compressed = False + self.raw_key = key + + + def hex(self): + return hexlify(self.raw_key).decode() + + + +class Address(): + def __init__(self, key = None, + address_type="P2WPKH", testnet=False, compressed = True): + if key is None: + self.private_key = PrivateKey(testnet = testnet, + compressed = compressed) + self.public_key = PublicKey(self.private_key) + elif type(key) == PrivateKey: + self.private_key = key + testnet = key.testnet + compressed = key.compressed + self.public_key = PublicKey(self.private_key) + elif type(key) == PublicKey: + self.public_key = key + testnet = testnet + compressed = key.compressed + assert address_type in ("P2PKH", "PUBKEY", "P2WPKH", "P2SH_P2WPKH") + if not compressed: + assert address_type in ("P2PKH", "PUBKEY") + self.type = address_type + self.testnet = testnet + if address_type in ("P2WPKH"): + self.witness_version = 0 + else: + self.witness_version = None + self.compressed = compressed + + if address_type == "P2SH_P2WPKH": + self.script_hash = True + self.redeem_script = public_key_to_P2SH_P2WPKH_script(self.public_key.raw_key) + self.redeem_script_hex = hexlify(self.redeem_script).decode() + self.hash = hash160(self.redeem_script) + else: + self.script_hash = False + self.hash = hash160(self.public_key.raw_key) + self.address = hash_to_address(self.hash, + script_hash = self.script_hash, + witness_version = self.witness_version) diff --git a/pybtc/block.py b/pybtc/block.py new file mode 100644 index 0000000..e69de29 diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py index 3d59474..7a49756 100644 --- a/pybtc/blockchain.py +++ b/pybtc/blockchain.py @@ -6,259 +6,10 @@ from .tools import * from .consensus import * from binascii import hexlify, unhexlify -def get_stream(stream): - if type(stream) != io.BytesIO: - if type(stream) == str: - stream = unhexlify(stream) - if type(stream) == bytes: - stream = io.BytesIO(stream) - else: - raise TypeError - return stream - -class Opcode(): - """ Class opcode """ - def __init__(self, raw_opcode, data, data_length = b""): - self.raw = raw_opcode - if self.raw in RAW_OPCODE: - if self.raw in (OPCODE["OP_PUSHDATA1"], OPCODE["OP_PUSHDATA2"], OPCODE["OP_PUSHDATA4"]): - self.str = '<%s>' % len(data) - else: - self.str = RAW_OPCODE[self.raw] - elif self.raw < b'L': - self.str = '<%s>' % len(data) - else: - self.str = '[?]' - self.data = data - self.data_length = data_length - - def __str__(self): - return self.str - - @classmethod - def to_raw(cls, name): - if name in OPCODE: - return OPCODE[name] - else: - return b'' - - @classmethod - def pop_from_stream (cls, stream): - b = stream.read(1) - if not b: return None - data = b'' - data_length = b'' - if b <= OPCODE["OP_PUSHDATA4"]: - if b < OPCODE["OP_PUSHDATA1"]: s = int.from_bytes(b,'little') - elif b == OPCODE["OP_PUSHDATA1"]: - data_length = stream.read(1) - s = int.from_bytes( data_length ,'little') - elif b == OPCODE["OP_PUSHDATA2"]: - data_length = stream.read(2) - s = int.from_bytes( data_length ,'little') - elif b == OPCODE["OP_PUSHDATA4"]: - data_length = stream.read(4) - s = int.from_bytes( data_length ,'little') - data = stream.read(s) - if len(data)!=s: - return None - raise Exception('opcode read error') - return cls(b,data,data_length) +k = 0 -class Script(): - """ - Bitcoin script class - """ - def __init__(self, raw_script, coinbase = False, segwit = True): - if type(raw_script) == str: - raw_script = unhexlify(raw_script) - self.raw = raw_script - stream = io.BytesIO(raw_script) - self.script = [] - self.address = list() - self.pattern = bytearray() - self.asm = bytearray() - self.data = b'' - self.type = "NON_STANDARD" - self.ntype = 7 - self.witness_version = None - self.op_sig_count = 0 - if coinbase: - self.pattern = b"" - self.asm = hexlify(raw_script).decode() - return - t = time.time() - while True: - o = Opcode.pop_from_stream(stream) - if o is None: - break - if o.raw == OPCODE["OP_CHECKSIG"] or o.raw == OPCODE["OP_CHECKSIGVERIFY"]: - self.op_sig_count += 1 - if o.raw ==OPCODE["OP_CHECKMULTISIG"]: - self.op_sig_count += 20 - self.script.append(o) - self.pattern += o.str.encode() + b' ' - if o.data: - self.asm += hexlify(o.data) + b' ' - else: - self.asm += o.str.encode() + b' ' - self.asm = self.asm.decode().rstrip() - self.pattern= self.pattern.decode().rstrip() - # check script type - if self.pattern == "OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG": - self.type = "P2PKH" - self.ntype = 0 - self.address.append(self.script[2].data) - elif self.pattern == "OP_HASH160 <20> OP_EQUAL": - self.type = "P2SH" - self.ntype = 1 - self.address.append(self.script[1].data) - elif self.pattern == "<65> OP_CHECKSIG" or self.pattern == "<33> OP_CHECKSIG" : - self.type = "PUBKEY" - self.ntype = 2 - self.address.append(hash160(self.script[0].data)) - elif len(self.script) == 2 and self.script[0].raw == OPCODE["OP_RETURN"]: - # OP_RETURN - if len(self.script[1].data) < NULL_DATA_LIMIT: # <0 to 80 bytes of data> - self.data = self.script[1].data - self.type = "NULL_DATA" - self.ntype = 3 - elif len(self.script)>= 4: - if self.script[-1].raw == OPCODE["OP_CHECKMULTISIG"] \ - and self.script[-2].raw <= OPCODE["OP_15"] \ - and self.script[-2].raw >= OPCODE["OP_1"] : # OP_CHECKMULTISIG "OP_1" "OP_16" - if self.script[0].raw <= OPCODE["OP_15"] \ - and self.script[0].raw >= OPCODE["OP_1"]: - self.op_sig_count = 0 - for o in self.script[1:-2]: - if not o.data: - self.op_sig_count = 20 - break - self.op_sig_count += 1 - self.address.append(hash160(o.data)) - else: - self.bare_multisig_accepted = ord(self.script[0].raw) - 80 - self.bare_multisig_from = ord(self.script[-2].raw) - 80 - self.type = "MULTISIG" - self.ntype = 4 - - elif segwit: - if self.pattern == "OP_0 <20>": - self.type = "P2WPKH" - self.op_sig_count = 1 - self.ntype = 5 - self.witness_version = 0 - self.address.append(self.script[1].data) - elif self.pattern == "OP_0 <32>": - self.type = "P2WSH" - self.ntype = 6 - self.witness_version = 0 - self.address.append(self.script[1].data) - - - -class Input: - """ Transaction Input class """ - # outpoint = (b'00f0f09...',n') - # script = raw bytes - # sequense = int - def __init__(self, outpoint, script, sequence, amount = None, private_key = None): - if type(outpoint[0]) == str: - outpoint = (unhexlify(outpoint[0])[::-1], outpoint[1]) - if type(outpoint[0]) == str: - private_key = WIF2priv(private_key) - self.outpoint = outpoint - self.sequence = sequence - self.pk_script = None - self.amount = amount - self.private_key = private_key - self.p2sh_type = None - self.coinbase = False - if outpoint == (b'\x00'*32 ,0xffffffff): self.coinbase = True - self.sig_script = Script(script, self.coinbase) - self.double_spend = None - self.lock = False - self.addresses = [] - self.redeem_script = None - if len(self.sig_script.script) > 0: - try: - if len(self.sig_script.script[-1].data) <= 520: - self.redeem_script = Script(self.sig_script.script[-1].data) - else: - pass - except Exception as err: - pass - - @classmethod - def deserialize(cls, stream): - stream = get_stream(stream) - outpoint = stream.read(32), int.from_bytes(stream.read(4), 'little') - script_len = from_var_int(read_var_int(stream)) - script = stream.read(script_len) - sequence = int.from_bytes(stream.read(4), 'little') - return cls(outpoint, script, sequence) - - def serialize(self): - return self.outpoint[0] + self.outpoint[1].to_bytes(4, 'little') \ - + to_var_int(len(self.sig_script.raw)) + self.sig_script.raw \ - + self.sequence.to_bytes(4, 'little') - - - -class Output: - """ Transactin output class """ - def __init__(self, value, script): - self.value = value - self.pk_script = Script(script) - - def serialize(self): - return self.value.to_bytes(8,'little') \ - + to_var_int(len(self.pk_script.raw)) + self.pk_script.raw - - - @classmethod - def deserialize(cls, stream): - stream = get_stream(stream) - value = int.from_bytes(stream.read(8), 'little') - script_len = from_var_int(read_var_int(stream)) - pk_script = stream.read(script_len) - return cls(value, pk_script) - -class Witness: - def __init__(self, data, empty = False): - self.empty = empty - self.witness = [b"\x00"] if empty else data - - def __str__(self): - return json.dumps([hexlify(w).decode() for w in self.witness]) - - def hex(self): - return [hexlify(w).decode() for w in self.witness] - - @classmethod - def deserialize(cls, stream): - stream = get_stream(stream) - empty = True - witness_len = from_var_int(read_var_int(stream)) - witness = [] - if witness_len: - for i in range(witness_len): - l = from_var_int(read_var_int(stream)) - w = stream.read(l) - witness.append(w) - empty = False - return cls(witness, empty) - - def serialize(self): - if self.empty: - return b'\x00' - - n = to_var_int(len(self.witness)) - for w in self.witness: - n += to_var_int(len(w)) + w - return n class Transaction(): @@ -306,7 +57,7 @@ class Transaction(): if not self.tx_in: self.witness = list() if witness is None: - self.witness = [Witness.deserialize(b"\x00") for i in range(len(tx_in))] + self.witness = (Witness.deserialize(b"\x00") for i in range(len(tx_in))) if hash is None : self.recalculate_txid() @@ -376,7 +127,7 @@ class Transaction(): if type(p2wpkh_address)==str: assert address_type(p2wpkh_address) == 'P2WPKH' witness_version = get_witness_version(p2wpkh_address) - p2wpkh_address = address2hash(p2wpkh_address) + p2wpkh_address = address_to_hash(p2wpkh_address) assert len(p2wpkh_address) == 20 self.tx_out.append(Output(amount, bytes([witness_version]) + b'\x14' + p2wpkh_address)) @@ -387,7 +138,7 @@ class Transaction(): if type(p2wsh_address)==str: assert address_type(p2wsh_address) == 'P2WSH' witness_version = get_witness_version(p2wsh_address) - p2wsh_address = address2hash(p2wsh_address) + p2wsh_address = address_to_hash(p2wsh_address) assert len(p2wsh_address) == 32 self.tx_out.append(Output(amount, bytes([witness_version]) + b'\x20' + p2wsh_address)) @@ -427,9 +178,9 @@ class Transaction(): def serialize(self, segwit = True, hex = False): version = self.version.to_bytes(4,'little') - ninputs = to_var_int(self.tx_in_count) + ninputs = int_to_var_int(self.tx_in_count) inputs = [i.serialize() for i in self.tx_in] - nouts = to_var_int(len(self.tx_out)) + nouts = int_to_var_int(len(self.tx_out)) outputs = [o.serialize() for o in self.tx_out] marke_flag = b"\x00\x01" if segwit else b"" witness = b"" @@ -445,7 +196,7 @@ class Transaction(): def sign_P2SHP2WPKH_input(self, sighash_type, input_index, private_key = None, amount = None): if type(private_key) == str: - private_key = WIF2priv(private_key) + private_key = WIF_to_private_key(private_key) if amount is not None: self.tx_in[input_index].amount = amount else: @@ -454,7 +205,7 @@ class Transaction(): self.tx_in[input_index].private_key = private_key else: private_key = self.tx_in[input_index].private_key - pubkey = priv2pub(private_key, True) + pubkey = private_to_public_key(private_key, True) pubkey_hash160 = hash160(pubkey) scriptCode = b"\x19" + OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] scriptCode += b'\x14' + pubkey_hash160 + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] @@ -469,7 +220,7 @@ class Transaction(): self.tx_in[input_index].private_key = private_key else: private_key = self.tx_in[input_index].private_key - pubkey = priv2pub(private_key, compressed) + pubkey = private_to_public_key(private_key, compressed) pubkey_hash160 = hash160(pubkey) scriptCode = OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + b'\x14' + \ pubkey_hash160 + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] @@ -489,24 +240,24 @@ class Transaction(): if ((sighash_type&31) == SIGHASH_SINGLE) and (input_index>(len(self.tx_out)-1)): return double_sha256(b'\x01'+b'\x00'*31 + sighash_type.to_bytes(4, 'little')) preimage += self.version.to_bytes(4,'little') - preimage += b'\x01' if sighash_type & SIGHASH_ANYONECANPAY else to_var_int(self.tx_in_count) + preimage += b'\x01' if sighash_type & SIGHASH_ANYONECANPAY else int_to_var_int(self.tx_in_count) for number, i in enumerate(self.tx_in): if (sighash_type & SIGHASH_ANYONECANPAY) and (input_index != number): continue input = i.outpoint[0]+i.outpoint[1].to_bytes(4,'little') if sighash_type == 0 or input_index == number: - input += ((to_var_int(len(scriptCode)) + scriptCode) if sighash_type else \ - (to_var_int(len(i.sig_script.raw)) + i.sig_script.raw)) + i.sequence.to_bytes(4,'little') + input += ((int_to_var_int(len(scriptCode)) + scriptCode) if sighash_type else \ + (int_to_var_int(len(i.sig_script.raw)) + i.sig_script.raw)) + i.sequence.to_bytes(4, 'little') else: input += b'\x00' + (i.sequence.to_bytes(4,'little') if \ ((sighash_type&31) == SIGHASH_ALL) else b'\x00\x00\x00\x00') preimage += input - preimage += b'\x00' if (sighash_type&31) == SIGHASH_NONE else ( to_var_int(input_index + 1) if \ - (sighash_type&31) == SIGHASH_SINGLE else to_var_int(self.tx_out_count)) + preimage += b'\x00' if (sighash_type&31) == SIGHASH_NONE else (int_to_var_int(input_index + 1) if \ + (sighash_type&31) == SIGHASH_SINGLE else int_to_var_int(self.tx_out_count)) if (sighash_type&31) != SIGHASH_NONE: for number, i in enumerate(self.tx_out): if number > input_index and (sighash_type&31) == SIGHASH_SINGLE: continue preimage +=(b'\xff'*8+b'\x00' if (sighash_type&31) == SIGHASH_SINGLE and (input_index != number)\ - else i.value.to_bytes(8,'little')+to_var_int(len(i.pk_script.raw))+i.pk_script.raw) + else i.value.to_bytes(8,'little') + int_to_var_int(len(i.pk_script.raw)) + i.pk_script.raw) preimage += self.lock_time.to_bytes(4,'little') preimage += sighash_type.to_bytes(4, 'little') return double_sha256(preimage) if not hex else hexlify(double_sha256(preimage)).decode() @@ -543,10 +294,10 @@ class Transaction(): ho = bytearray() for n, o in enumerate(self.tx_out): if (sighash_type&31) != SIGHASH_SINGLE and (sighash_type&31) != SIGHASH_NONE: - ho += o.value.to_bytes(8,'little')+to_var_int(len(o.pk_script.raw))+o.pk_script.raw + ho += o.value.to_bytes(8,'little') + int_to_var_int(len(o.pk_script.raw)) + o.pk_script.raw elif (sighash_type&31) == SIGHASH_SINGLE and input_index < len(self.tx_out): if input_index == n: - ho += o.value.to_bytes(8, 'little') + to_var_int(len(o.pk_script.raw)) + o.pk_script.raw + ho += o.value.to_bytes(8, 'little') + int_to_var_int(len(o.pk_script.raw)) + o.pk_script.raw hashOutputs = double_sha256(ho) if ho else b'\x00'*32 preimage += hashPrevouts + hashSequence + outpoint + scriptCode + value + nSequence + hashOutputs preimage += self.lock_time.to_bytes(4, 'little') @@ -589,10 +340,10 @@ class Transaction(): if o.pk_script.ntype in (1,6): sh =True for a in o.pk_script.address: - out["address"].append(hash2address(a, - testnet=testnet, - script_hash= sh, - witness_version=o.pk_script.witness_version)) + out["address"].append(hash_to_address(a, + testnet=testnet, + script_hash= sh, + witness_version=o.pk_script.witness_version)) r["vout"].append(out) @@ -662,6 +413,7 @@ class Transaction(): class Block(): def __init__(self, version, prev_block, merkle_root, timestamp, bits, nonce, txs, block_size, hash = None, header = None): + qt = time.time() self.hash = hash self.header = header self.version = version @@ -671,6 +423,7 @@ class Block(): self.timestamp = timestamp self.bits = bits self.nonce = nonce + self.transactions = txs self.tx_hash_list = list() self.size = block_size @@ -679,13 +432,12 @@ class Block(): self.amount = 0 self.fee = 0 self.sigop = 0 - for t in txs: - if t.hash in self.tx_hash_list: - raise Exception("CVE-2012-2459") # merkle tree malleability - self.tx_hash_list.append(t.hash) + + self.target = None self.fee = 0 self.witness_root_hash = None + if txs: if txs[0].coinbase: if self.nversion > 1: @@ -700,6 +452,7 @@ class Block(): self.witness_root_hash = out.pk_script.data[4:36] except: pass + print("t ", time.time() - qt) def calculate_commitment(self, witness = None): wtxid_list = [b"\x00" * 32,] @@ -723,7 +476,7 @@ class Block(): commitment = self.calculate_commitment(tx.witness[0].witness[0]) for o in outputs: if type(o[1]) == str: - tx.tx_out.append(Output(o[0], address2script(o[1]))) + tx.tx_out.append(Output(o[0], address_to_script(o[1]))) else: tx.tx_out.append(Output(o[0], o[1])) tx.tx_out.append(Output(0, b'j$\xaa!\xa9\xed' + commitment)) @@ -762,11 +515,14 @@ class Block(): 'merkle_root': stream.read(32), 'timestamp': int.from_bytes(stream.read(4), 'little'), 'bits': stream.read(4), - 'nonce': stream.read(4), - 'txs': read_var_list(stream, Transaction), - 'block_size': stream.tell(), - 'header': header - } + 'nonce': stream.read(4)} + t = time.time() + kwargs['txs'] = read_var_list(stream, Transaction) + print("tx ",time.time() - t) + kwargs['block_size'] = stream.tell() + kwargs['header'] = header + global k + print(">.>.>.",k) return cls(**kwargs) def serialize(self, hex = False): @@ -776,13 +532,12 @@ class Block(): self.timestamp.to_bytes(4,'little') + \ self.bits + \ self.nonce + \ - to_var_int(len (self.transactions)) + int_to_var_int(len (self.transactions)) for t in self.transactions: if t.hash == t.whash: block += t.serialize(segwit = 0) else: block += t.serialize(segwit = 1) - if hex: return hexlify(block).decode() else: @@ -820,8 +575,8 @@ class BlockTemplate(): self.scan_tx_list() self.coinbase_tx = self.create_coinbase_transaction() self.coinb1, self.coinb2 = self.split_coinbase() - self.target = bits2target(self.bits) - self.difficulty = target2difficulty(self.target) + self.target = bits_to_target(self.bits) + self.difficulty = target_to_difficulty(self.target) self.merkle_branches = [hexlify(i).decode() for i in merkle_branches([self.coinbase_tx.hash,] + self.txid_list)] @@ -906,7 +661,7 @@ class BlockTemplate(): print("branches ", self.merkle_branches) header = version + prev_hash + merkle_root + time + bits + nonce block = hexlify(header).decode() - block += hexlify(to_var_int(len (self.transactions)+1)).decode() + block += hexlify(int_to_var_int(len (self.transactions) + 1)).decode() block += cb for t in self.transactions: block += t["data"] diff --git a/pybtc/constants.py b/pybtc/constants.py index 4eb603a..cdc508f 100644 --- a/pybtc/constants.py +++ b/pybtc/constants.py @@ -74,3 +74,4 @@ SCRIPT_TYPES = { "P2PKH": 0, "NON_STANDART": 7 } + diff --git a/pybtc/encode.py b/pybtc/encode.py index ccbb546..9f7a155 100644 --- a/pybtc/encode.py +++ b/pybtc/encode.py @@ -14,8 +14,6 @@ for n, i in enumerate(base32charset_upcase): int_base32_map[i] = n - - def rebasebits(data, frombits, tobits, pad=True): """General power-of-2 base conversion.""" acc = 0 diff --git a/pybtc/hash.py b/pybtc/hash.py index 51c779e..6f45fc9 100644 --- a/pybtc/hash.py +++ b/pybtc/hash.py @@ -16,7 +16,6 @@ def double_sha256(h, hex = False): return sha256(sha256(h), 1) return sha256(sha256(h)) - def hmac_sha512(key, data, hex = False): if hex: return hmac.new(key, data, hashlib.sha512).hexdigest() diff --git a/pybtc/opcodes.py b/pybtc/opcodes.py index 95d8df6..ad416ee 100644 --- a/pybtc/opcodes.py +++ b/pybtc/opcodes.py @@ -1,148 +1,155 @@ +from binascii import hexlify + +OPCODE = dict() + +# push opcodes + +OPCODE["OP_FALSE"] = 0x00 +OPCODE["OP_0"] = 0x00 +OPCODE["OP_PUSHDATA1"] = 0x4c +OPCODE["OP_PUSHDATA2"] = 0x4d +OPCODE["OP_PUSHDATA4"] = 0x4e +OPCODE["OP_1NEGATE"] = 0x4f +OPCODE["OP_RESERVED"] = 0x50 +OPCODE["OP_1"] = 0x51 +OPCODE["OP_TRUE"] = 0x51 +OPCODE["OP_2"] = 0x52 +OPCODE["OP_3"] = 0x53 +OPCODE["OP_4"] = 0x54 +OPCODE["OP_5"] = 0x55 +OPCODE["OP_6"] = 0x56 +OPCODE["OP_7"] = 0x57 +OPCODE["OP_8"] = 0x58 +OPCODE["OP_9"] = 0x59 +OPCODE["OP_10"] = 0x5a +OPCODE["OP_11"] = 0x5b +OPCODE["OP_12"] = 0x5c +OPCODE["OP_13"] = 0x5d +OPCODE["OP_14"] = 0x5e +OPCODE["OP_15"] = 0x5f +OPCODE["OP_16"] = 0x60 + +# control + +OPCODE["OP_NOP"] = 0x61 +OPCODE["OP_VER"] = 0x62 +OPCODE["OP_IF"] = 0x63 +OPCODE["OP_NOTIF"] = 0x64 +OPCODE["OP_VERIF"] = 0x65 +OPCODE["OP_ELSE"] = 0x67 +OPCODE["OP_ENDIF"] = 0x68 +OPCODE["OP_VERIFY"] = 0x69 +OPCODE["OP_RETURN"] = 0x6a + +# stack + +OPCODE["OP_TOALTSTACK"] = 0x6b +OPCODE["OP_FROMALTSTACK"] = 0x6c +OPCODE["OP_2DROP"] = 0x6d +OPCODE["OP_2DUP"] = 0x6e +OPCODE["OP_3DUP"] = 0x6f +OPCODE["OP_2OVER"] = 0x70 +OPCODE["OP_2ROT"] = 0x71 +OPCODE["OP_2SWAP"] = 0x72 +OPCODE["OP_IFDUP"] = 0x73 +OPCODE["OP_DEPTH"] = 0x74 +OPCODE["OP_DROP"] = 0x75 +OPCODE["OP_DUP"] = 0x76 +OPCODE["OP_NIP"] = 0x77 +OPCODE["OP_OVER"] = 0x78 +OPCODE["OP_PICK"] = 0x79 +OPCODE["OP_ROLL"] = 0x7a +OPCODE["OP_ROT"] = 0x7b +OPCODE["OP_SWAP"] = 0x7c +OPCODE["OP_TUCK"] = 0x7d + +# splice + +OPCODE["OP_CAT"] = 0x7e +OPCODE["OP_SUBSTR"] = 0x7f +OPCODE["OP_LEFT"] = 0x80 +OPCODE["OP_RIGHT"] = 0x81 +OPCODE["OP_SIZE"] = 0x82 + +# bit operations + +OPCODE["OP_INVERT"] = 0x83 +OPCODE["OP_AND"] = 0x84 +OPCODE["OP_OR"] = 0x85 +OPCODE["OP_XOR"] = 0x86 +OPCODE["OP_EQUAL"] = 0x87 +OPCODE["OP_EQUALVERIFY"] = 0x88 +OPCODE["OP_RESERVED1"] = 0x89 +OPCODE["OP_RESERVED2"] = 0x8a + +# math + +OPCODE["OP_1ADD"] = 0x8b +OPCODE["OP_1SUB"] = 0x8c +OPCODE["OP_2MUL"] = 0x8d +OPCODE["OP_2DIV"] = 0x8e +OPCODE["OP_NEGATE"] = 0x8f +OPCODE["OP_ABS"] = 0x90 +OPCODE["OP_NOT"] = 0x91 +OPCODE["OP_0NOTEQUAL"] = 0x92 + +OPCODE["OP_ADD"] = 0x93 +OPCODE["OP_SUB"] = 0x94 +OPCODE["OP_MUL"] = 0x95 +OPCODE["OP_DIV"] = 0x96 +OPCODE["OP_MOD"] = 0x97 +OPCODE["OP_LSHIFT"] = 0x98 +OPCODE["OP_RSHIFT"] = 0x99 + +OPCODE["OP_BOOLAND"] = 0x9a +OPCODE["OP_BOOLOR"] = 0x9b +OPCODE["OP_NUMEQUAL"] = 0x9c +OPCODE["OP_NUMEQUALVERIFY"] = 0x9d +OPCODE["OP_NUMNOTEQUAL"] = 0x9e +OPCODE["OP_LESSTHAN"] = 0x9f +OPCODE["OP_GREATERTHAN"] = 0xa0 +OPCODE["OP_LESSTHANOREQUAL"] = 0xa1 +OPCODE["OP_GREATERTHANOREQUAL"] = 0xa2 +OPCODE["OP_MIN"] = 0xa3 +OPCODE["OP_MAX"] = 0xa4 + +OPCODE["OP_WITHIN"] = 0xa5 + +# crypto + +OPCODE["OP_RIPEMD160"] = 0xa6 +OPCODE["OP_SHA1"] = 0xa7 +OPCODE["OP_SHA256"] = 0xa8 +OPCODE["OP_HASH160"] = 0xa9 +OPCODE["OP_HASH256"] = 0xaa +OPCODE["OP_CODESEPARATOR"] = 0xab +OPCODE["OP_CHECKSIG"] = 0xac +OPCODE["OP_CHECKSIGVERIFY"] = 0xad +OPCODE["OP_CHECKMULTISIG"] = 0xae +OPCODE["OP_CHECKMULTISIGVERIFY"] = 0xaf + +# expansion + +OPCODE["OP_NOP1"] = 0xb0 +OPCODE["OP_CHECKLOCKTIMEVERIFY"] = 0xb1 +OPCODE["OP_CHECKSEQUENCEVERIFY"] = 0xb2 +OPCODE["OP_NOP4"] = 0xb3 +OPCODE["OP_NOP5"] = 0xb4 +OPCODE["OP_NOP6"] = 0xb5 +OPCODE["OP_NOP7"] = 0xb6 +OPCODE["OP_NOP8"] = 0xb7 +OPCODE["OP_NOP9"] = 0xb8 +OPCODE["OP_NOP10"] = 0xb9 + +# template matching params + +OPCODE["OP_SMALLINTEGER"] = 0xfa +OPCODE["OP_PUBKEYS"] = 0xfb +OPCODE["OP_PUBKEYHASH"] = 0xfd +OPCODE["OP_PUBKEY"] = 0xfe +OPCODE["OP_INVALIDOPCODE"] = 0xff -OPCODE = {"OP_0": b'\x00', - "OP_PUSHDATA1": b'L', - "OP_PUSHDATA2": b'M', - "OP_PUSHDATA4": b'N', - "OP_1NEGATE": b'O', - "OP_RESERVED": b'P', - "OP_1": b'Q', - "OP_2": b'R', - "OP_3": b'S', - "OP_4": b'T', - "OP_5": b'U', - "OP_6": b'V', - "OP_7": b'W', - "OP_8": b'X', - "OP_9": b'Y', - "OP_10": b'Z', - "OP_11": b'[', - "OP_12": b'\\', - "OP_13": b']', - "OP_14": b'^', - "OP_15": b'_', - "OP_16": b'`', - "OP_NOP": b'a', - "OP_VER": b'b', - "OP_IF": b'c', - "OP_NOTIF": b'd', - "OP_VERIF": b'e', - "OP_VERNOTIF": b'f', - "OP_ELSE": b'g', - "OP_ENDIF": b'h', - "OP_VERIFY": b'i', - "OP_RETURN": b'j', - "OP_TOALTSTACK": b'k', - "OP_FROMALTSTACK": b'l', - "OP_2DROP": b'm', - "OP_2DUP": b'n', - "OP_3DUP": b'o', - "OP_2OVER": b'p', - "OP_2ROT": b'q', - "OP_2SWAP": b'r', - "OP_IFDUP": b's', - "OP_DEPTH": b't', - "OP_DROP": b'u', - "OP_DUP": b'v', - "OP_NIP": b'w', - "OP_OVER": b'x', - "OP_PICK": b'y', - "OP_ROLL": b'z', - "OP_ROT": b'{', - "OP_SWAP": b'|', - "OP_TUCK": b'}', - "OP_CAT": b'~', - "OP_SUBSTR": b'\x7f', - "OP_LEFT": b'\x80', - "OP_RIGHT": b'\x81', - "OP_SIZE": b'\x82', - "OP_INVERT": b'\x83', - "OP_AND": b'\x84', - "OP_OR": b'\x85', - "OP_XOR": b'\x86', - "OP_EQUAL": b'\x87', - "OP_EQUALVERIFY": b'\x88', - "OP_RESERVED1": b'\x89', - "OP_RESERVED2": b'\x8a', - "OP_1ADD": b'\x8b', - "OP_1SUB": b'\x8c', - "OP_2MUL": b'\x8d', - "OP_2DIV": b'\x8e', - "OP_NEGATE": b'\x8f', - "OP_ABS": b'\x90', - "OP_NOT": b'\x91', - "OP_0NOTEQUAL": b'\x92', - "OP_ADD": b'\x93', - "OP_SUB": b'\x94', - "OP_MUL": b'\x95', - "OP_DIV": b'\x96', - "OP_MOD": b'\x97', - "OP_LSHIFT": b'\x98', - "OP_RSHIFT": b'\x99', - "OP_BOOLAND": b'\x9a', - "OP_BOOLOR": b'\x9b', - "OP_NUMEQUAL": b'\x9c', - "OP_NUMEQUALVERIFY": b'\x9d', - "OP_NUMNOTEQUAL": b'\x9e', - "OP_LESSTHAN": b'\x9f', - "OP_GREATERTHAN": b'\xa0', - "OP_LESSTHANOREQUAL": b'\xa1', - "OP_GREATERTHANOREQUAL": b'\xa2', - "OP_MIN": b'\xa3', - "OP_MAX": b'\xa4', - "OP_WITHIN": b'\xa5', - "OP_RIPEMD160": b'\xa6', - "OP_SHA1": b'\xa7', - "OP_SHA256": b'\xa8', - "OP_HASH160": b'\xa9', - "OP_HASH256": b'\xaa', - "OP_CODESEPARATOR": b'\xab', - "OP_CHECKSIG": b'\xac', - "OP_CHECKSIGVERIFY": b'\xad', - "OP_CHECKMULTISIG": b'\xae', - "OP_CHECKMULTISIGVERIFY": b'\xaf', - "OP_NOP1": b'\xb0', - "OP_NOP2": b'\xb1', - "OP_NOP3": b'\xb2', - "OP_NOP4": b'\xb3', - "OP_NOP5": b'\xb4', - "OP_NOP6": b'\xb5', - "OP_NOP7": b'\xb6', - "OP_NOP8": b'\xb7', - "OP_NOP9": b'\xb8', - "OP_NOP10": b'\xb9', - "OP_NULLDATA": b'\xfc', - "OP_PUBKEYHASH": b'\xfd', - "OP_PUBKEY": b'\xfe', - "OP_INVALIDOPCODE": b'\xff'} RAW_OPCODE = dict ( (OPCODE[i], i) for i in OPCODE ) - -DISABLED_OPCODE = set (( - # OPCODE["OP_RETURN"], - OPCODE["OP_CAT"], - OPCODE["OP_SUBSTR"], - OPCODE["OP_LEFT"], - OPCODE["OP_RIGHT"], - OPCODE["OP_LEFT"], - OPCODE["OP_LEFT"], - OPCODE["OP_AND"], - OPCODE["OP_OR"], - OPCODE["OP_XOR"], - OPCODE["OP_2MUL"], - OPCODE["OP_2DIV"], - OPCODE["OP_MUL"], - OPCODE["OP_DIV"], - OPCODE["OP_MOD"], - OPCODE["OP_LSHIFT"], - OPCODE["OP_RSHIFT"], - OPCODE["OP_RESERVED"], - # OPCODE["OP_VER"], - OPCODE["OP_VERIF"], - OPCODE["OP_VERNOTIF"], - OPCODE["OP_RESERVED1"], - OPCODE["OP_RESERVED2"], - OPCODE["OP_PUBKEYHASH"], - OPCODE["OP_PUBKEY"], - OPCODE["OP_INVALIDOPCODE"] - )) - +BYTE_OPCODE = dict ((i,bytes([OPCODE[i]])) for i in OPCODE ) +HEX_OPCODE = dict ((i,hexlify(bytes([OPCODE[i]])).decode()) for i in OPCODE ) diff --git a/pybtc/tools.py b/pybtc/tools.py index 74213f1..dbed6bd 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -1,23 +1,20 @@ -import hashlib -from binascii import hexlify, unhexlify import time import struct -import hmac from .constants import * from .opcodes import * from .hash import * from .encode import * +import math +import io -# Bitcoin keys/ addresses +# Bitcoin keys # -def create_priv(hex=False): +def create_private_key(hex=False): """ :return: 32 bytes private key """ - q = time.time() - rnd = random.SystemRandom() - a = rnd.randint(0,MAX_INT_PRIVATE_KEY) + a = random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY) i = int((time.time()%0.01)*100000) h = a.to_bytes(32,byteorder="big") while True: @@ -30,15 +27,12 @@ def create_priv(hex=False): return hexlify(h).decode() return h -def priv_from_int(k): - return int.to_bytes(k,byteorder="big",length=32) - - -def priv2WIF(h, compressed = True, testnet = False): +def private_key_to_WIF(h, compressed = True, testnet = False): #uncompressed: 0x80 + [32-byte secret] + [4 bytes of Hash() of previous 33 bytes], base58 encoded #compressed: 0x80 + [32-byte secret] + 0x01 + [4 bytes of Hash() previous 34 bytes], base58 encoded if type(h) == str: h = unhexlify(h) + assert len(h) == 32 if testnet: h = TESTNET_PRIVATE_KEY_BYTE_PREFIX + h else: @@ -47,15 +41,15 @@ def priv2WIF(h, compressed = True, testnet = False): h += double_sha256(h)[:4] return encode_base58(h) -def WIF2priv(h, hex = False, verify = 1): - if verify: - assert is_WIF_valid(h) +def WIF_to_private_key(h, hex = False): + assert is_WIF_valid(h) h = decode_base58(h) if hex: return hexlify(h[1:33]).decode() return h[1:33] def is_WIF_valid(wif): + assert type(wif) == str if wif[0] not in PRIVATE_KEY_PREFIX_LIST: return False try: @@ -73,7 +67,7 @@ def is_WIF_valid(wif): return False return True -def priv2pub(private_key, compressed = True, hex = False): +def private_to_public_key(private_key, compressed = True, hex = False): if type(private_key)!= bytes: if type(private_key) == bytearray: private_key = bytes(private_key) @@ -84,7 +78,7 @@ def priv2pub(private_key, compressed = True, hex = False): if private_key[0] in (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX): compressed = False - private_key = WIF2priv(private_key, verify=0) + private_key = WIF_to_private_key(private_key) else: raise TypeError("private key must be a bytes or WIF or hex encoded string") pubkey_ptr = ffi.new('secp256k1_pubkey *') @@ -99,7 +93,7 @@ def priv2pub(private_key, compressed = True, hex = False): pub = bytes(ffi.buffer(pubkey, len_key)) return hexlify(pub).decode() if hex else pub -def is_valid_pub(key): +def is_valid_public_key(key): if len(key) < 33: return False if key[0] == 0x04 and len(key) != 65: @@ -115,8 +109,8 @@ def is_valid_pub(key): # -def hash2address(address_hash, testnet = False, - script_hash = False, witness_version = 0): +def hash_to_address(address_hash, testnet = False, + script_hash = False, witness_version = 0): if type(address_hash) == str: address_hash = unhexlify(address_hash) if not script_hash: @@ -151,7 +145,7 @@ def hash2address(address_hash, testnet = False, return "%s1%s" % (hrp, rebase_5_to_32(address_hash + checksum).decode()) -def address2hash(address, hex = False): +def address_to_hash(address, hex = False): if address[0] in ADDRESS_PREFIX_LIST: h = decode_base58(address)[1:-4] elif address[:2] in (MAINNET_SEGWIT_ADDRESS_PREFIX, @@ -190,7 +184,7 @@ def address_type(address, num = False): return SCRIPT_TYPES['NON_STANDARD'] if num else 'UNKNOWN' return SCRIPT_TYPES[t] if num else t -def script2hash(s, witness = False, hex = False): +def script_to_hash(s, witness = False, hex = False): if type(s) == str: s = unhexlify(s) if witness: @@ -198,31 +192,41 @@ def script2hash(s, witness = False, hex = False): else: return hash160(s, hex) -def address2script(address): +def address_to_script(address, hex = False): if address[0] in (TESTNET_SCRIPT_ADDRESS_PREFIX, MAINNET_SCRIPT_ADDRESS_PREFIX): - return OPCODE["OP_HASH160"] + b'\x14' + address2hash(address) + OPCODE["OP_EQUAL"] - if address[0] in (MAINNET_ADDRESS_PREFIX, + s = [BYTE_OPCODE["OP_HASH160"], + b'\x14', + address_to_hash(address), + BYTE_OPCODE["OP_EQUAL"]] + elif address[0] in (MAINNET_ADDRESS_PREFIX, TESTNET_ADDRESS_PREFIX, TESTNET_ADDRESS_PREFIX_2): - return OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + b'\x14' + \ - address2hash(address) + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] - if address[0] in (TESTNET_SEGWIT_ADDRESS_PREFIX, + s = [BYTE_OPCODE["OP_DUP"], + BYTE_OPCODE["OP_HASH160"], + b'\x14', + address_to_hash(address), + BYTE_OPCODE["OP_EQUALVERIFY"], + BYTE_OPCODE["OP_CHECKSIG"]] + elif address[:2] in (TESTNET_SEGWIT_ADDRESS_PREFIX, MAINNET_SEGWIT_ADDRESS_PREFIX): - h = address2hash(address) - return OPCODE["OP_0"] + bytes([len(h)]) + h - raise Exception("Unknown address") + h = address_to_hash(address) + s = [BYTE_OPCODE["OP_0"], + bytes([len(h)]), + h] + else: + assert False + s = b''.join(s) + return hexlify(s).decode() if hex else s -def script_P2SH_P2WPKH(pubkey, hash = False): +def public_key_to_P2SH_P2WPKH_script(pubkey): assert len(pubkey) == 33 - if hash: - return hash160(b'\x00\x14' + hash160(pubkey)) return b'\x00\x14' + hash160(pubkey) -def pub2address(pubkey, testnet = False, - p2sh_p2wpkh = False, - witness_version = 0): +def public_key_to_address(pubkey, testnet = False, + p2sh_p2wpkh = False, + witness_version = 0): if type(pubkey) == str: pubkey = unhexlify(pubkey) if p2sh_p2wpkh: @@ -233,18 +237,122 @@ def pub2address(pubkey, testnet = False, if witness_version is not None: assert len(pubkey) == 33 h = hash160(pubkey) - return hash2address(h, testnet = testnet, + return hash_to_address(h, testnet = testnet, script_hash = p2sh_p2wpkh, witness_version = witness_version) -# def pub2P2SH_P2WPKH_hash(pubkey): -# return hash160(b'\x00\x14' + hash160(pubkey)) -# -# def pub2P2SH_P2WPKH_address(pubkey, testnet = False): -# return hash2address(pub2P2SH_P2WPKH_hash(pubkey), -# testnet=testnet, -# script_hash=True, -# witness_version=None) +def parse_script(script, segwit=True): + if type(script) == str: + try: + script = unhexlify(script) + except: + pass + assert type(script) == bytes + l = len(script) + if segwit: + if l == 22 and script[0] == 0: + return {"nType": 5, "type": "P2WPKH", "reqSigs": 1, "addressHash": script[2:]} + if l == 34 and script[0] == 0: + return {"nType": 6, "type": "P2WSH", "reqSigs": None, "addressHash": script[2:]} + if l == 25 and \ + script[:2] == b"\x76\xa9" and \ + script[-2:] == b"\x88\xac": + return {"nType": 0, "type": "P2PKH", "reqSigs": 1, "addressHash": script[3:-2]} + if l == 23 and \ + script[0] == 169 and \ + script[-1] == 135: + return {"nType": 1, "type": "P2SH", "reqSigs": None, "addressHash": script[2:-1]} + if l == 67 and script[-1] == 172: + return {"nType": 2, "type": "PUBKEY", "reqSigs": 1, "addressHash": hash160(script[1:-1])} + if l == 35 and script[-1] == 172: + return {"nType": 2, "type": "PUBKEY", "reqSigs": 1, "addressHash": hash160(script[1:-1])} + if script[0] == 106 and l > 1 and l <= 82: + if script[1] == l - 2: + return {"nType": 3, "type": "NULL_DATA", "reqSigs": 0, "data": script[2:]} + if script[0] >= 81 and script[0] <= 96: + if script[-1] == 174: + if script[-2] >= 81 and script[-2] <= 96: + if script[-2] >= script[0]: + c, s = 0, 1 + while l - 2 - s > 0: + if script[s] < 0x4c: + s += script[s] + c += 1 + else: + c = 0 + break + s += 1 + if c == script[-2] - 80: + return {"nType": 4, "type": "MULTISIG", "reqSigs": script[0] - 80, "script": script} + + s, m, n, last, req_sigs = 0, 0, 0, 0, 0 + while l - s > 0: + if script[s] >= 81 and script[s] <= 96: + if not n: + n = script[s] - 80 + else: + if m == 0: + n, m = script[s] - 80, 0 + elif n > m: + n, m = script[s] - 80, 0 + elif m == script[s] - 80: + last = 0 if last else 2 + elif script[s] < 0x4c: + s += script[s] + m += 1 + if m > 16: + n, m = 0, 0 + elif script[s] == OPCODE["OP_PUSHDATA1"]: + s += 1 + script[s + 1] + elif script[s] == OPCODE["OP_PUSHDATA2"]: + s += 2 + struct.unpack(' 0: + if script[s] < 0x4c and script[s]: + if asm: + result.append(hexlify(script[s+1:s+1 +script[s]]).decode()) + else: + result.append('[%s]' % script[s]) + s += script[s] + 1 + continue + elif script[s] == OPCODE["OP_PUSHDATA1"]: + s += 1 + script[s + 1] + elif script[s] == OPCODE["OP_PUSHDATA2"]: + s += 2 + struct.unpack('> 24 - target = (bits & 0xffffff) * (1 << (8 * (shift - 3))) - return target - -def target2difficulty(target): - return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / target - -def bits2difficulty(bits): - return target2difficulty(bits2target(bits)) - -def difficulty2target(difficulty): - return int(0x00000000FFFF0000000000000000000000000000000000000000000000000000 / difficulty) - def rh2s(tthash): return hexlify(tthash[::-1]).decode() @@ -479,7 +568,9 @@ def s2rh_step4(hash_string): def reverse_hash(h): return struct.pack('>IIIIIIII', *struct.unpack('>IIIIIIII', h)[::-1])[::-1] - +# +# +# def merkleroot(tx_hash_list): tx_hash_list = list(tx_hash_list) @@ -529,73 +620,143 @@ def merkleroot_from_branches(merkle_branches, coinbase_hash_bin): h = unhexlify(h) merkle_root = double_sha256(merkle_root + h) return merkle_root + +def bits_to_target(bits): + if type(bits) == str: + bits = unhexlify(bits) + if type(bits) == bytes: + return int.from_bytes(bits[1:], 'big') * (2 ** (8 * (bits[0] - 3))) + else: + shift = bits >> 24 + target = (bits & 0xffffff) * (1 << (8 * (shift - 3))) + return target + +def target_to_difficulty(target): + return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / target + +def bits_to_difficulty(bits): + return target_to_difficulty(bits_to_target(bits)) + +def difficulty_to_target(difficulty): + return int(0x00000000FFFF0000000000000000000000000000000000000000000000000000 / difficulty) + + # # # +def bytes_needed(n): + if n == 0: + return 1 + return math.ceil(n.bit_length()/8) -def var_int(data): - e, s = 1, 0 - if data[:1] == b'\xfd': - s, e = 1, 3 - elif data[:1] == b'\xfe': - s = 1 - e = 5 - elif data[:1] == b'\xff': - s = 1 - e = 9 - i = int.from_bytes(data[s:e], byteorder='little', signed=False) - return (i, e) +def int_to_bytes(i, byteorder='big'): + return i.to_bytes(bytes_needed(i), byteorder=byteorder, signed=False) + +def bytes_to_int(i, byteorder='big'): + return int.from_bytes(i, byteorder=byteorder, signed=False) -def from_var_int(data): - # retrun - e = 1 - s = 0 - if data[:1] == b'\xfd': - s = 1 - e = 3 - elif data[:1] == b'\xfe': - s = 1 - e = 5 - elif data[:1] == b'\xff': - s = 1 - e = 9 - i = int.from_bytes(data[s:e], byteorder='little', signed=False) - return i +# variable integer - -def var_int_len(byte): - e = 1 - if byte == 253: - e = 3 - elif byte == 254: - e = 5 - elif byte == 255: - e = 9 - return e - - -def to_var_int(i): - if i < 253: - return i.to_bytes(1, byteorder='little') +def int_to_var_int(i): + if i < 0xfd: + return struct.pack(' OP_EQUALVERIFY OP_CHECKSIG") self.assertEqual(s.op_sig_count, 1) @@ -24,7 +28,7 @@ class ScriptDeserializeTests(unittest.TestCase): self.assertEqual(s.type, "P2SH") self.assertEqual(s.ntype, 1) self.assertEqual(s.asm, "OP_HASH160 69f37572ab1b69f304f987b119e2450e0b71bf5c OP_EQUAL") - self.assertEqual(s.address[0], address2hash("3BMEXVsYyfKB5h3m53XRSFHkqi1zPwsvcK")) + self.assertEqual(s.address[0], address_to_hash("3BMEXVsYyfKB5h3m53XRSFHkqi1zPwsvcK")) self.assertEqual(s.pattern, "OP_HASH160 <20> OP_EQUAL") self.assertEqual(s.op_sig_count, 0) diff --git a/tests/test/sighash.py b/tests/test/sighash.py index 947cfd4..5e5ac86 100644 --- a/tests/test/sighash.py +++ b/tests/test/sighash.py @@ -1,7 +1,11 @@ import unittest +import os, sys +parentPath = os.path.abspath("..") +if parentPath not in sys.path: + sys.path.insert(0, parentPath) from pybtc import * from binascii import unhexlify -from pybtc import address2hash as address2hash160 +from pybtc import address_to_hash as address2hash160 class SighashTests(unittest.TestCase): @classmethod diff --git a/tests/test/transaction_deserialize.py b/tests/test/transaction_deserialize.py index 572a5b4..971fed1 100644 --- a/tests/test/transaction_deserialize.py +++ b/tests/test/transaction_deserialize.py @@ -1,31 +1,65 @@ import unittest -from pybtc import blockchain +import os, sys +import time +parentPath = os.path.abspath("..") +if parentPath not in sys.path: + sys.path.insert(0, parentPath) +from pybtc.tools import * +from pybtc.hash import * +from pybtc.transaction import * from binascii import unhexlify -from pybtc import address2hash as address2hash160 +from pybtc import address_to_hash as address2hash160 + +def decode_block_tx(block): + stream = get_stream(block) + tx = dict() + stream.seek(80) + count = var_int_to_int(read_var_int(stream)) + for i in range(count): + tx[i] = Transaction(stream) + return tx class TransactionDeserializeTests(unittest.TestCase): @classmethod def setUpClass(cls): print("\nTesting Transaction class deserialization:\n") + + def test_serialaize_and_perfomance(self): + f = open('./test/raw_block.txt') + fc = f.readline() + qt = time.time() + bt = decode_block_tx(fc[:-1]) + self.assertEqual(time.time() - qt < 1, 1) + print("decode block tx count: %s time: %s" % (len(bt), time.time() - qt)) + for t in bt: + raw_tx_legacy = bt[t].serialize(segwit=False) + raw_tx_segwit = bt[t].serialize() + bt[t] = bt[t].decode() + # print(bt[t]["txId"], bt[t]["hash"], "segwit:", + # True if "segwit" in bt[t] else False, end = " ") + self.assertEqual(bt[t].serialize(segwit=False), raw_tx_legacy) + self.assertEqual(bt[t].serialize(), raw_tx_segwit) + self.assertEqual(rh2s(double_sha256(bt[t].serialize())), bt[t]["hash"]) + self.assertEqual(rh2s(double_sha256(bt[t].serialize(segwit=False))), bt[t]["txId"]) + # print("OK") + def test_segwit_deserialize(self): non_segwit_view = "020000000140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000017a9144a1154d50b03292b3024370901711946cb7cccc38700000000" segwit_view = "0200000000010140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000017a9144a1154d50b03292b3024370901711946cb7cccc387024830450221008604ef8f6d8afa892dee0f31259b6ce02dd70c545cfcfed8148179971876c54a022076d771d6e91bed212783c9b06e0de600fab2d518fad6f15a2b191d7fbd262a3e0121039d25ab79f41f75ceaf882411fd41fa670a4c672c23ffaf0e361a969cde0692e800000000" print("Deserialize Segwit transaction") - ns = blockchain.Transaction.deserialize(non_segwit_view) - s = blockchain.Transaction.deserialize(segwit_view) - s.serialize(True,True) - self.assertEqual(s.serialize(False,True), non_segwit_view) - self.assertEqual(s.serialize(True,True), segwit_view) - self.assertEqual(ns.serialize(False,True), non_segwit_view) + ns = Transaction(non_segwit_view) + s = Transaction(segwit_view) + self.assertEqual(s.serialize(segwit=False, hex = True), non_segwit_view) + self.assertEqual(s.serialize(segwit=True, hex = True), segwit_view) + self.assertEqual(ns.serialize(segwit=False, hex = True), non_segwit_view) non_segwit_view = "01000000060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93f554e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6eda0147304402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680ddf022012c52160ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee60147522103ee30ea502d692ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52db885d04dc43ca8562471759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab020ee0a80a818e4d20a52aa7ba367a0a2d430d22c26ccb4572527e259e14a01000000d900473044022064745ac8cae859bb19305a550981b8d39b69efec55b9a423dca645cd3b5c845502205cf375839d7f056235feb550a8138a03e75fa140503a2ce61fe239a3bfe42145014730440220728b0393d5427d8abb56a608c6b1a0b14c5455f3abeb56ce8a5c7f70035af71d022052a99e4e389790b364f6180caf1523d6da2b3daabe85952705023be2b5409b360147522103e296937dbdafdae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a251f74bd7e2103ead7ad0c398f928cbe851a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae0000000067a6c2e2f14fc412b3f5627fafac2fe43009bc403ec680839579444df2ce042b00000000da00483045022100d3bdc055fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e233102200e7ebb43fd39fb98c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c0147304402202f4338d2710edb1960dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216feb31051f7798297317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25e6ce5fdb716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a2bf9fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c419b2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781def27f25e91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f59627287a6c32180ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeadea55d17160bc6d11d47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb54ecd82d86efe83c0a1d35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c65bb26ec7581ad8652ae0000000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a5b4ac02ab682fdb301000000232200206ea344e9a4a8f8a8983479af2ae3ed29fab153955af14457780a304a6832b9c50000000016dcc4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e580d21578e35002502000000002322002049ea1f7c280b32fee0dce2e1801df2218df59d64614c4fe76c043ee2c80116700000000002005ed0b20000000017a91495c5c19257aa52bd4b702ba1a5e29b8d72a75a3a876d6b4e010000000017a91487b6255a5df746188f0bd22ed0194a40ec98f2de87be810700" segwit_view = "010000000001060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93f554e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6eda0147304402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680ddf022012c52160ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee60147522103ee30ea502d692ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52db885d04dc43ca8562471759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab020ee0a80a818e4d20a52aa7ba367a0a2d430d22c26ccb4572527e259e14a01000000d900473044022064745ac8cae859bb19305a550981b8d39b69efec55b9a423dca645cd3b5c845502205cf375839d7f056235feb550a8138a03e75fa140503a2ce61fe239a3bfe42145014730440220728b0393d5427d8abb56a608c6b1a0b14c5455f3abeb56ce8a5c7f70035af71d022052a99e4e389790b364f6180caf1523d6da2b3daabe85952705023be2b5409b360147522103e296937dbdafdae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a251f74bd7e2103ead7ad0c398f928cbe851a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae0000000067a6c2e2f14fc412b3f5627fafac2fe43009bc403ec680839579444df2ce042b00000000da00483045022100d3bdc055fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e233102200e7ebb43fd39fb98c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c0147304402202f4338d2710edb1960dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216feb31051f7798297317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25e6ce5fdb716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a2bf9fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c419b2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781def27f25e91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f59627287a6c32180ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeadea55d17160bc6d11d47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb54ecd82d86efe83c0a1d35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c65bb26ec7581ad8652ae0000000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a5b4ac02ab682fdb301000000232200206ea344e9a4a8f8a8983479af2ae3ed29fab153955af14457780a304a6832b9c50000000016dcc4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e580d21578e35002502000000002322002049ea1f7c280b32fee0dce2e1801df2218df59d64614c4fe76c043ee2c80116700000000002005ed0b20000000017a91495c5c19257aa52bd4b702ba1a5e29b8d72a75a3a876d6b4e010000000017a91487b6255a5df746188f0bd22ed0194a40ec98f2de87000000000400473044022100d0d2ded141c9369bcc99de23d3d41d1d99d6cff47126df1b0c4d4797f947eacf021f790f1c112b3425ebc3251d719aae6ef0f9830b688585275591a5353f1f973801483045022100bf06c762e6ab64258d2f2777a66fe32ddd8f36e232b80bf5afa6ff9b9aa73ee0022049caf991fce808e60a9b17499f5e0dc11f6163e3ef7bca8109b72b5695d674210147522103edd556806048b319d71f43466c4415001bb32d8afe3aac06532d3ac210fd0e86210215e16727cf1389b4ee377487385f3ec595841a6bb747eb9c3a5cd559e9b1c8dc52ae040047304402204e9cc87526e148d236d692fa70104d26b8df632f30f4e3be38a2e99cec76d0f80220354ae575c3537c0ad2399a6037a9164b0cb147b12f262efc906649ca7950e2eb0147304402203553bcd1565804ec71c997c87006bd91c639b74a004b19a239c7f551aab5635a0220753f74e065c0b7cdf67d16b00f6a20dda7159a49f20aa493a7889b2851b2fea30147522103b09ac1fa65a55fa4feadea57c4cf417d7490065d8b844ada60c242a441e0e3a42103c0625169b46dbbde3492db7c62f1be8f582131467620cef17335306bad7ef88a52aebe810700" print("Deserialize Segwit transaction") - ns = blockchain.Transaction.deserialize(non_segwit_view) - s = blockchain.Transaction.deserialize(segwit_view) - s.serialize(True, True) - self.assertEqual(s.serialize(False, True), non_segwit_view) - self.assertEqual(s.serialize(True, True), segwit_view) - self.assertEqual(ns.serialize(False, True), non_segwit_view) + ns = Transaction(non_segwit_view) + s = Transaction(segwit_view) + self.assertEqual(s.serialize(segwit=False,hex = True), non_segwit_view) + self.assertEqual(s.serialize(segwit=True, hex = True), segwit_view) + self.assertEqual(ns.serialize(segwit=False, hex = True), non_segwit_view) From fa00b7ff21e7ce7a0e27d0fb62cb6a643046ae0f Mon Sep 17 00:00:00 2001 From: 4tochka Date: Mon, 28 May 2018 16:03:43 +0400 Subject: [PATCH 02/13] version 2.0 draft --- pybtc/__init__.py | 6 +++--- pybtc/blockchain.py | 4 ++-- pybtc/transaction.py | 7 +++---- tests/test/transaction_deserialize.py | 7 ++----- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pybtc/__init__.py b/pybtc/__init__.py index 4987c0f..4c5a1c4 100644 --- a/pybtc/__init__.py +++ b/pybtc/__init__.py @@ -1,6 +1,6 @@ -# from .tools import * -# from .opcodes import * +from .tools import * +from .opcodes import * from .consensus import * -from .blockchain import * +from .transaction import * version = "2.0.1" diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py index 7a49756..c52f211 100644 --- a/pybtc/blockchain.py +++ b/pybtc/blockchain.py @@ -12,7 +12,7 @@ k = 0 -class Transaction(): +class OLDTransaction(): def __init__(self, version = 1, tx_in = [], tx_out = [] , lock_time = 0, hash=None, size = 0, timestamp = None, marker = None, flag = None, witness = [], @@ -410,7 +410,7 @@ class Transaction(): witness = witness, whash = wtx_id, vsize = vsize) -class Block(): +class OLDBlock(): def __init__(self, version, prev_block, merkle_root, timestamp, bits, nonce, txs, block_size, hash = None, header = None): qt = time.time() diff --git a/pybtc/transaction.py b/pybtc/transaction.py index 238d4f6..ba6e3d8 100644 --- a/pybtc/transaction.py +++ b/pybtc/transaction.py @@ -42,13 +42,13 @@ class Transaction(dict): for k in range(ic): self["vIn"][k] = dict() self["vIn"][k]["txId"] = stream.read(32) - (self["vIn"][k]["vOut"],) = unpack(' Date: Tue, 29 May 2018 11:33:47 +0400 Subject: [PATCH 03/13] PEP8 formating --- pybtc/address.py | 6 +- pybtc/blockchain.py | 2 +- pybtc/constants.py | 1 - pybtc/tools.py | 228 ++++++++++++++------------ tests/test/address_functions.py | 34 ++-- tests/test/transaction_deserialize.py | 6 +- 6 files changed, 152 insertions(+), 125 deletions(-) diff --git a/pybtc/address.py b/pybtc/address.py index 0828877..2f46a43 100644 --- a/pybtc/address.py +++ b/pybtc/address.py @@ -19,7 +19,7 @@ class PrivateKey(): self.testnet = testnet return assert type(key) == str - self.raw_key = WIF_to_private_key(key) + self.raw_key = wif_to_private_key(key) if key[0] in (MAINNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX, TESTNET_PRIVATE_KEY_UNCOMPRESSED_PREFIX): self.compressed = False @@ -39,7 +39,7 @@ class PrivateKey(): compressed = self.compressed if testnet is None: testnet = self.testnet - return private_key_to_WIF(self.raw_key, compressed, testnet) + return private_key_to_wif(self.raw_key, compressed, testnet) class PublicKey(): @@ -95,7 +95,7 @@ class Address(): if address_type == "P2SH_P2WPKH": self.script_hash = True - self.redeem_script = public_key_to_P2SH_P2WPKH_script(self.public_key.raw_key) + self.redeem_script = public_key_to_p2sh_p2wpkh_script(self.public_key.raw_key) self.redeem_script_hex = hexlify(self.redeem_script).decode() self.hash = hash160(self.redeem_script) else: diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py index c52f211..8f6fbd4 100644 --- a/pybtc/blockchain.py +++ b/pybtc/blockchain.py @@ -196,7 +196,7 @@ class OLDTransaction(): def sign_P2SHP2WPKH_input(self, sighash_type, input_index, private_key = None, amount = None): if type(private_key) == str: - private_key = WIF_to_private_key(private_key) + private_key = wif_to_private_key(private_key) if amount is not None: self.tx_in[input_index].amount = amount else: diff --git a/pybtc/constants.py b/pybtc/constants.py index cdc508f..352906c 100644 --- a/pybtc/constants.py +++ b/pybtc/constants.py @@ -1,5 +1,4 @@ from secp256k1 import lib as secp256k1 -from secp256k1 import ffi import random SIGHASH_ALL = 0x00000001 diff --git a/pybtc/tools.py b/pybtc/tools.py index dbed6bd..7993637 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -1,5 +1,6 @@ import time import struct +from secp256k1 import ffi from .constants import * from .opcodes import * from .hash import * @@ -14,22 +15,24 @@ def create_private_key(hex=False): """ :return: 32 bytes private key """ - a = random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY) - i = int((time.time()%0.01)*100000) - h = a.to_bytes(32,byteorder="big") + a = random.SystemRandom().randint(0, MAX_INT_PRIVATE_KEY) + i = int((time.time() % 0.01)*100000) + h = a.to_bytes(32, byteorder="big") while True: h = hashlib.sha256(h).digest() - if i>1: i -= 1 + if i > 1: + i -= 1 else: - if int.from_bytes(h,byteorder="big") 73): @@ -512,41 +523,41 @@ def is_valid_signature_encoding(sig): if sig[1] != (length - 3): return False # Extract the length of the R element. - lenR = sig[3] + len_r = sig[3] # Make sure the length of the S element is still inside the signature. - if (5 + lenR) >= length: + if (5 + len_r) >= length: return False # Extract the length of the S element. - lenS = sig[5 + lenR] + len_s = sig[5 + len_r] # Verify that the length of the signature matches the sum of the length # of the elements. - if (lenR + lenS + 7) != length: + if (len_r + len_s + 7) != length: return False # Check whether the R element is an integer. if sig[2] != 0x02: return False # Zero-length integers are not allowed for R. - if lenR == 0: + if len_r == 0: return False # Negative numbers are not allowed for R. if sig[4] & 0x80: return False # Null bytes at the start of R are not allowed, unless R would # otherwise be interpreted as a negative number. - if (lenR > 1) and (sig[4] == 0x00) and (not sig[5] & 0x80): + if (len_r > 1) and (sig[4] == 0x00) and (not sig[5] & 0x80): return False # Check whether the S element is an integer. - if sig[lenR + 4] != 0x02: + if sig[len_r + 4] != 0x02: return False # Zero-length integers are not allowed for S. - if lenS == 0: + if len_s == 0: return False # Negative numbers are not allowed for S. - if sig[lenR + 6] & 0x80: + if sig[len_r + 6] & 0x80: return False # Null bytes at the start of S are not allowed, unless S would otherwise be # interpreted as a negative number. - if (lenS > 1) and (sig[lenR + 6] == 0x00) and (not sig[lenR + 7] & 0x80): + if (len_s > 1) and (sig[len_r + 6] == 0x00) and (not sig[len_r + 7] & 0x80): return False return True @@ -556,15 +567,20 @@ def is_valid_signature_encoding(sig): # def rh2s(tthash): + # raw hash to string return hexlify(tthash[::-1]).decode() + def s2rh(hash_string): + # string to raw hash return unhexlify(hash_string)[::-1] + def s2rh_step4(hash_string): h = unhexlify(hash_string) return reverse_hash(h) + def reverse_hash(h): return struct.pack('>IIIIIIII', *struct.unpack('>IIIIIIII', h)[::-1])[::-1] @@ -572,6 +588,7 @@ def reverse_hash(h): # # + def merkleroot(tx_hash_list): tx_hash_list = list(tx_hash_list) if len(tx_hash_list) == 1: @@ -590,6 +607,7 @@ def merkleroot(tx_hash_list): else: return new_hash_list[0] + def merkle_branches(tx_hash_list): tx_hash_list = list(tx_hash_list) branches = [] @@ -613,6 +631,7 @@ def merkle_branches(tx_hash_list): branches.append(new_hash_list.pop(0)) return branches + def merkleroot_from_branches(merkle_branches, coinbase_hash_bin): merkle_root = coinbase_hash_bin for h in merkle_branches: @@ -621,6 +640,7 @@ def merkleroot_from_branches(merkle_branches, coinbase_hash_bin): merkle_root = double_sha256(merkle_root + h) return merkle_root + def bits_to_target(bits): if type(bits) == str: bits = unhexlify(bits) @@ -631,12 +651,15 @@ def bits_to_target(bits): target = (bits & 0xffffff) * (1 << (8 * (shift - 3))) return target + def target_to_difficulty(target): return 0x00000000FFFF0000000000000000000000000000000000000000000000000000 / target + def bits_to_difficulty(bits): return target_to_difficulty(bits_to_target(bits)) + def difficulty_to_target(difficulty): return int(0x00000000FFFF0000000000000000000000000000000000000000000000000000 / difficulty) @@ -647,12 +670,14 @@ def difficulty_to_target(difficulty): def bytes_needed(n): if n == 0: - return 1 + return 1 return math.ceil(n.bit_length()/8) + def int_to_bytes(i, byteorder='big'): return i.to_bytes(bytes_needed(i), byteorder=byteorder, signed=False) + def bytes_to_int(i, byteorder='big'): return int.from_bytes(i, byteorder=byteorder, signed=False) @@ -668,6 +693,7 @@ def int_to_var_int(i): return b'\xfe' + struct.pack(' 0: have_ext = (v.bit_length() & 0x07) == 0 - neg = False if v < 0: neg = True v = -v - s = struct.pack(b">I", bn_bytes(v, have_ext)) ext = bytearray() if have_ext: @@ -816,7 +843,6 @@ def mpi2bn(s): return None if v_len == 0: return 0 - v_str = bytearray(s[4:]) neg = False i = v_str[0] @@ -824,7 +850,6 @@ def mpi2bn(s): neg = True i &= ~0x80 v_str[0] = i - v = bin2bn(v_str) if neg: @@ -861,6 +886,7 @@ def i2b(i): return bn2vch(i) def b2i(b): return vch2bn(b) + def get_stream(stream): if type(stream) != io.BytesIO: if type(stream) == str: diff --git a/tests/test/address_functions.py b/tests/test/address_functions.py index f7730d5..8b4ab53 100644 --- a/tests/test/address_functions.py +++ b/tests/test/address_functions.py @@ -20,36 +20,36 @@ class AddressFunctionsTests(unittest.TestCase): pum = "5KPPLXhtga99qqMceRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf" put = "93A1vGXSGoDHotruGmgyRgtV8hgfFjgtmtuc4epcag886W9d44L" pct = "cUWo47XLYiyFByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6" - self.assertEqual(tools.private_key_to_WIF(p, compressed=1, testnet=0), pcm) - self.assertEqual(tools.private_key_to_WIF(p, compressed=0, testnet=0), pum) - self.assertEqual(tools.private_key_to_WIF(p, compressed=1, testnet=1), pct) - self.assertEqual(tools.private_key_to_WIF(p, compressed=0, testnet=1), put) + self.assertEqual(tools.private_key_to_wif(p, compressed=1, testnet=0), pcm) + self.assertEqual(tools.private_key_to_wif(p, compressed=0, testnet=0), pum) + self.assertEqual(tools.private_key_to_wif(p, compressed=1, testnet=1), pct) + self.assertEqual(tools.private_key_to_wif(p, compressed=0, testnet=1), put) def test_is_WIF_valid(self): - self.assertEqual(tools.is_WIF_valid("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX"),1) - self.assertEqual(tools.is_WIF_valid("5KPPLXhtga99qqMceRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf"),1) - self.assertEqual(tools.is_WIF_valid("5KPPLXhtga99qqMcWRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf"),0) - self.assertEqual(tools.is_WIF_valid("93A1vGXSGoDHotruGmgyRgtV8hgfFjgtmtuc4epcag886W9d44L"),1) - self.assertEqual(tools.is_WIF_valid("cUWo47XLYiyFByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6"),1) - self.assertEqual(tools.is_WIF_valid("cUWo47XLYiyByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6"),0) + self.assertEqual(tools.is_wif_valid("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX"), 1) + self.assertEqual(tools.is_wif_valid("5KPPLXhtga99qqMceRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf"), 1) + self.assertEqual(tools.is_wif_valid("5KPPLXhtga99qqMcWRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf"), 0) + self.assertEqual(tools.is_wif_valid("93A1vGXSGoDHotruGmgyRgtV8hgfFjgtmtuc4epcag886W9d44L"), 1) + self.assertEqual(tools.is_wif_valid("cUWo47XLYiyFByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6"), 1) + self.assertEqual(tools.is_wif_valid("cUWo47XLYiyByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6"), 0) def test_WIF_to_private_key(self): p = "ceda1ae4286015d45ec5147fe3f63e9377ccd6d4e98bcf0847df9937da1944a4" - self.assertEqual(tools.WIF_to_private_key("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX", + self.assertEqual(tools.wif_to_private_key("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX", hex=1),p) - self.assertEqual(tools.WIF_to_private_key("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX", + self.assertEqual(tools.wif_to_private_key("L49obCXV7fGz2YRzLCSJgeZBYmGeBbKPT7xiehUeYX2S4URkPFZX", hex=0),unhexlify(p)) - self.assertEqual(tools.WIF_to_private_key("5KPPLXhtga99qqMceRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf", + self.assertEqual(tools.wif_to_private_key("5KPPLXhtga99qqMceRo4Z6LXV3Kx6a9hRx3ez2U7EwP5KZfy2Wf", hex=1),p) - self.assertEqual(tools.WIF_to_private_key("93A1vGXSGoDHotruGmgyRgtV8hgfFjgtmtuc4epcag886W9d44L", + self.assertEqual(tools.wif_to_private_key("93A1vGXSGoDHotruGmgyRgtV8hgfFjgtmtuc4epcag886W9d44L", hex=1),p) - self.assertEqual(tools.WIF_to_private_key("cUWo47XLYiyFByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6", + self.assertEqual(tools.wif_to_private_key("cUWo47XLYiyFByuFicFS3y4FAza3r3R5XA7Bm7wA3dgSKDYox7h6", hex=1),p) def test_create_private_key(self): p = tools.create_private_key() - pw = tools.private_key_to_WIF(p) - self.assertEqual(tools.is_WIF_valid(pw), True) + pw = tools.private_key_to_wif(p) + self.assertEqual(tools.is_wif_valid(pw), True) diff --git a/tests/test/transaction_deserialize.py b/tests/test/transaction_deserialize.py index 5454bd3..5833257 100644 --- a/tests/test/transaction_deserialize.py +++ b/tests/test/transaction_deserialize.py @@ -1,14 +1,16 @@ import unittest -import os, sys +import os +import sys import time parentPath = os.path.abspath("..") if parentPath not in sys.path: sys.path.insert(0, parentPath) + from pybtc.tools import * from pybtc.hash import * from pybtc.transaction import * from binascii import unhexlify -from pybtc import address_to_hash as address2hash160 +from pybtc import address_to_hash as address2hash160 def decode_block_tx(block): From 3d381807b2b7d68b48804245539d9c5f646d3bb3 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Tue, 29 May 2018 18:19:52 +0400 Subject: [PATCH 04/13] PEP8 formating --- pybtc/address.py | 16 +- pybtc/block.py | 28 + pybtc/blockchain.py | 1 - pybtc/tools.py | 2 +- pybtc/transaction.py | 4 +- tests/test/block.py | 1160 +++++++++++++++++++++--------------------- 6 files changed, 619 insertions(+), 592 deletions(-) diff --git a/pybtc/address.py b/pybtc/address.py index 2f46a43..f16da58 100644 --- a/pybtc/address.py +++ b/pybtc/address.py @@ -1,5 +1,6 @@ from .tools import * + class PrivateKey(): def __init__(self, key=None, compressed=True, testnet=False): if key is None: @@ -34,7 +35,7 @@ class PrivateKey(): def hex(self): return hexlify(self.raw_key).decode() - def WIF(self, compressed=None, testnet=None): + def wif(self, compressed=None, testnet=None): if compressed is None: compressed = self.compressed if testnet is None: @@ -60,18 +61,16 @@ class PublicKey(): self.compressed = False self.raw_key = key - def hex(self): return hexlify(self.raw_key).decode() - class Address(): def __init__(self, key = None, - address_type="P2WPKH", testnet=False, compressed = True): + address_type="P2WPKH", testnet=False, compressed=True): if key is None: - self.private_key = PrivateKey(testnet = testnet, - compressed = compressed) + self.private_key = PrivateKey(testnet=testnet, + compressed=compressed) self.public_key = PublicKey(self.private_key) elif type(key) == PrivateKey: self.private_key = key @@ -92,7 +91,6 @@ class Address(): else: self.witness_version = None self.compressed = compressed - if address_type == "P2SH_P2WPKH": self.script_hash = True self.redeem_script = public_key_to_p2sh_p2wpkh_script(self.public_key.raw_key) @@ -102,5 +100,5 @@ class Address(): self.script_hash = False self.hash = hash160(self.public_key.raw_key) self.address = hash_to_address(self.hash, - script_hash = self.script_hash, - witness_version = self.witness_version) + script_hash=self.script_hash, + witness_version=self.witness_version) diff --git a/pybtc/block.py b/pybtc/block.py index e69de29..525d5bb 100644 --- a/pybtc/block.py +++ b/pybtc/block.py @@ -0,0 +1,28 @@ +from .tools import * +from .transaction import Transaction +from struct import pack, unpack + + +class Block(dict): + def __init__(self, block): + s = get_stream(block) + self["header"] = s.read(80) + self["hash"] = double_sha256(self["header"]) + self["version"] = unpack(">>",block.bits) From d40f6ecc0acbb40855562752bf3f704b8c4838bb Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 12:52:35 +0400 Subject: [PATCH 05/13] PEP8 formating --- README.md | 87 ++--------------------------------------------- doc/img/pybtc.svg | 12 +++++++ pybtc/__init__.py | 1 + pybtc/block.py | 4 +-- 4 files changed, 17 insertions(+), 87 deletions(-) create mode 100644 doc/img/pybtc.svg diff --git a/README.md b/README.md index c1747e6..2acac70 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,5 @@ +![pybtc logo](doc/img/pybtc.svg) + # pybtc Python bitcoin library - -### Basic Examples - -#### Create private key - - >>> from pybtc import * - >>> create_priv() - b'\xc8\xf5tGf\x00+4\x1c\xe3\xb6\x00\xf4\x14w\x1d\xf0{jiY&4`v\xd4\tmv!\x0f\x1f' - >>> - >>> priv = create_priv() - >>> priv - b'_`\xd7@\x9e\xdb\xbbB5O%@\xd6\x92\xb1\x0e*\xcd\xb6\x89!\xa3JE\xb0\xb6:\x8c\x04\x88\xc9\xa5' - >>> - >>> priv2WIF(priv) - 'KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW' - >>> - >>> priv2WIF(priv, compressed = False) # Mainnet compressed WIF format - '5JYHtgBjYbLT3ZkhGHHCivscdMdDKeVTZBgq5ZK51fyKpqKDhYv' # Mainnet uncompressed WIF format - >>> - >>> priv2WIF(priv, testnet = True) - 'cQn71zxEDWF77m386rfe7PHTshLF3kaJH6KGKY5GD5fcLsCqpPbg' - >>> - >>> priv2WIF(priv, compressed = True, testnet = True) # Testnet compressed WIF format - 'cQn71zxEDWF77m386rfe7PHTshLF3kaJH6KGKY5GD5fcLsCqpPbg' - >>> - >>> priv2WIF(priv, compressed = False, testnet = True) # Testnet uncompressed WIF format - '92JvUR1H8pQb1dFytdB7bXRaH1yvUp2eu8YnABfaMQiNbuKiPVL' - >>> - >>> WIF2priv("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW") - b'_`\xd7@\x9e\xdb\xbbB5O%@\xd6\x92\xb1\x0e*\xcd\xb6\x89!\xa3JE\xb0\xb6:\x8c\x04\x88\xc9\xa5' - >>> - -#### Public key from private key - - >>> from pybtc import * - >>> priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW") - b'\x02\xb1-\xc2\x03u\xda\x00*7t\xb9c\xe4A\xdb\x1c\xe0\x89\xb8W\x13\x86\xbe\x82\xee(\x11nrj\xb06' - >>> - >>> priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW", hex = True) - '02b12dc20375da002a3774b963e441db1ce089b8571386be82ee28116e726ab036' - >>> - >>> priv = WIF2priv("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW") - >>> priv - b'_`\xd7@\x9e\xdb\xbbB5O%@\xd6\x92\xb1\x0e*\xcd\xb6\x89!\xa3JE\xb0\xb6:\x8c\x04\x88\xc9\xa5' - >>> - >>> priv2pub(priv, hex = True) - '02b12dc20375da002a3774b963e441db1ce089b8571386be82ee28116e726ab036' - >>> - -#### Address from public key/private key - - >>> from pybtc import * - >>> # address in bech32 format - ... - >>> pub2address(priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW")) - 'bc1q3hs6985qftzrvfl7aqcshsf7equapuuxzr2kcv' - >>> - >>> # address in legacy format - ... - >>> pub2address(priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW"), witness_version = None) - '1DwCaTcMTT5kZmH4wCevDe5nyzffi2Bz9p' - >>> - >>> # uncompressed public key deprecated for bech32 segwit addresses fromat - ... - >>> pub2address(priv2pub("5JYHtgBjYbLT3ZkhGHHCivscdMdDKeVTZBgq5ZK51fyKpqKDhYv")) - Traceback (most recent call last): - File "", line 1, in - File "/usr/local/lib/python3.6/site-packages/pybtc/tools.py", line 233, in pub2address - assert len(pubkey) == 33 - AssertionError - >>> - >>> # uncompressed public key legacy format - ... - >>> pub2address(priv2pub("5JYHtgBjYbLT3ZkhGHHCivscdMdDKeVTZBgq5ZK51fyKpqKDhYv"), witness_version = None) - '1EbTeoa1QgZaSHZFznrhNdKrRbbQupVwuZ' - >>> - >>> # testnet addresses - ... - >>> pub2address(priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW"), testnet = True) - 'tb1q3hs6985qftzrvfl7aqcshsf7equapuuxg939rl' - >>> - >>> pub2address(priv2pub("KzR7Z5xNnSYqxKZriSrWk4nQFU2qPJUcD4AoD7ckhy1c68A4zvkW"), witness_version = None, testnet = True) - 'mtT9sWhLGUX1LskgemdJ3ZJ7qzGNaygcXP' - >>> - diff --git a/doc/img/pybtc.svg b/doc/img/pybtc.svg new file mode 100644 index 0000000..29b1592 --- /dev/null +++ b/doc/img/pybtc.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + pybtc + \ No newline at end of file diff --git a/pybtc/__init__.py b/pybtc/__init__.py index 4c5a1c4..feaa99a 100644 --- a/pybtc/__init__.py +++ b/pybtc/__init__.py @@ -2,5 +2,6 @@ from .tools import * from .opcodes import * from .consensus import * from .transaction import * +from .address import * version = "2.0.1" diff --git a/pybtc/block.py b/pybtc/block.py index 525d5bb..de899f5 100644 --- a/pybtc/block.py +++ b/pybtc/block.py @@ -15,8 +15,8 @@ class Block(dict): self["bits"] = s.read(4), self["nonce"] = unpack(" Date: Wed, 30 May 2018 12:54:48 +0400 Subject: [PATCH 06/13] PEP8 formating --- README.md | 2 +- doc/img/pybtc.png | Bin 0 -> 48394 bytes doc/img/pybtc.svg | 12 ------------ 3 files changed, 1 insertion(+), 13 deletions(-) create mode 100644 doc/img/pybtc.png delete mode 100644 doc/img/pybtc.svg diff --git a/README.md b/README.md index 2acac70..a7d91e4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![pybtc logo](doc/img/pybtc.svg) +![pybtc logo](doc/img/pybtc.png) # pybtc Python bitcoin library diff --git a/doc/img/pybtc.png b/doc/img/pybtc.png new file mode 100644 index 0000000000000000000000000000000000000000..21a2da26608fee47cd5bbca8741ba441147b72ec GIT binary patch literal 48394 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%0Ad57qI2xnT2|nGN)$!LnQP!B7fXsGf+(&Y724 zxzIln1AezKpWpSj%xQh^Y(lG!67DR59!mw0Zq(FskHo(EyH2s+WAFW6W7X>VD~^6y z9kkk9^~k=e`zOVFuo#d~iQzfbuocfcKZi;J;pZ63`I`7jaIe~8I(79g0`b3hWy&VF z)55m@Y^6;P@C=5b;=@$pG_Ler(UK%Hb|duvqwQefJ8J}7)SWeklmRMO4f2Gd@*VHZ zjI_8VlFnJM-t8p6ujAkTh~+FJ%P5@X+7<=QzM9(}Z{P zr%meBosIJIb;leCA|at?-o1axzrr(PYI$n9OhrZp)M7|hXWQc&UQ;5ST% zSm|;**syi?cog)Pu|PBjKuk(VIA`1i`Ogg}F9a+6a`>>v=;3Y><8XT;Pq_`Dx)K03 zzQFiZqLFzQ?OyKB$U|;<93tTq)#haaggA8)F zEnHBMQzmzOAO?UK4Y)SVp^7n9B7jLDbLs&}rh72XA76`vNsJ8u%o2xnD?*#aV-U2q zE%W8e7jPk z$pZ?thFBV_FuOu(Ig3dFM)jf={=k zdi{f%$H$hl2Sf9dg*A9?KC{3Rrm&^Q>1eyBI+@rCs0EeM&4!|aij4`tl zEuyV(`u0hlbQPgX8$u~0RX^hKE`!m ze`FH{5cHLqwri9fzD2mXOcP{`6mexy=!gBK8{h*lxPcflH$d}%r|W?u^%nQ59N$Up zPUAd2-3g+ZYslTss`+M1GWEfl=HF@wox=gysbh;FKmhm!guts*G>hiU_YHB@IBDd| zz(McF2cc`|ng$gqs18>dU^|SxaH4V^CJ@;w-(kkv7IsGK%M!2Tg(*>HB6UL+o$!lG_7_Yi#SUW{nVkjW3 z^MFKW - - - - - - - - - - pybtc - \ No newline at end of file From 675662434211c0fac5d1685132379bcdb1e6b5e2 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 13:00:36 +0400 Subject: [PATCH 07/13] pybtc logo --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a7d91e4..4db1e1c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![pybtc logo](doc/img/pybtc.png) + -# pybtc -Python bitcoin library +### Python bitcoin library From 2bfb431d216e6d49a726770dd33dd6f014031276 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 13:02:23 +0400 Subject: [PATCH 08/13] pybtc logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4db1e1c..08da9bf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + ### Python bitcoin library From 89f43799787ba2b17347fa89fef72c7f5884eedf Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 13:04:32 +0400 Subject: [PATCH 09/13] pybtc logo --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 08da9bf..4f036d9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -### Python bitcoin library - +## Python bitcoin library From 2f15a74b325354ec4959ec24204e23ee0adf8858 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 14:01:11 +0400 Subject: [PATCH 10/13] empty script decode fix --- pybtc/tools.py | 2 ++ tests/test/transaction_deserialize.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pybtc/tools.py b/pybtc/tools.py index 5a4c586..1ec7e1c 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -256,6 +256,8 @@ def public_key_to_address(pubkey, testnet=False, def parse_script(script, segwit=True): + if not script: + return {"nType": 7, "type": "NON_STANDARD", "reqSigs": 0, "script": b""} if type(script) == str: try: script = unhexlify(script) diff --git a/tests/test/transaction_deserialize.py b/tests/test/transaction_deserialize.py index 5833257..2872daf 100644 --- a/tests/test/transaction_deserialize.py +++ b/tests/test/transaction_deserialize.py @@ -62,3 +62,5 @@ class TransactionDeserializeTests(unittest.TestCase): self.assertEqual(s.serialize(segwit=True, hex = True), segwit_view) self.assertEqual(ns.serialize(segwit=False, hex = True), non_segwit_view) + tx = "01000000014cee27ba570d2cca50bb9b3f7374c7eb24ec16ffec0a077c84c1cc23b0161804010000008b48304502200f1100f78596c8d46fb2f39c570ce6945956a3dd33c48fbdbe53af1c383182ed022100a85b528ea21ee7f39b2ec1568ac19f26f4dd4fb9d3dbf70587986de3c2c90fa801410426e4d0890ad5272b2b9a10ca3f518f7e025932caa62f13467e444df89ed25f24f4fc5075cad32f468c8f7f913e30057449d65623726e7102f5eaa326d486ebf7ffffffff020010000000000000006020e908000000001976a914947236437233a71cb033a53932008dbfe346388e88ac00000000" + t = Transaction(tx) \ No newline at end of file From c9c05385d387e3b921388eaee7658d4841498377 Mon Sep 17 00:00:00 2001 From: 4tochka Date: Wed, 30 May 2018 15:01:59 +0400 Subject: [PATCH 11/13] decode OP_PUSHDATA2/4 fix --- pybtc/tools.py | 4 ++-- pybtc/transaction.py | 3 --- tests/test/transaction_deserialize.py | 4 +++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pybtc/tools.py b/pybtc/tools.py index 1ec7e1c..b331214 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -321,9 +321,9 @@ def parse_script(script, segwit=True): elif script[s] == OPCODE["OP_PUSHDATA1"]: s += 1 + script[s + 1] elif script[s] == OPCODE["OP_PUSHDATA2"]: - s += 2 + struct.unpack(' Date: Wed, 30 May 2018 15:33:36 +0400 Subject: [PATCH 12/13] decode OP_PUSHDATA2/4 fix for broken opcodes script tx ebc9fa1196a59e192352d76c0f6e73167046b9d37b8302b6bb6968dfd279b767 --- pybtc/tools.py | 15 ++++++++++++--- pybtc/transaction.py | 6 +++--- tests/test/transaction_deserialize.py | 2 ++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pybtc/tools.py b/pybtc/tools.py index b331214..459b890 100644 --- a/pybtc/tools.py +++ b/pybtc/tools.py @@ -319,11 +319,20 @@ def parse_script(script, segwit=True): if m > 16: n, m = 0, 0 elif script[s] == OPCODE["OP_PUSHDATA1"]: - s += 1 + script[s + 1] + try: + s += 1 + script[s + 1] + except: + break elif script[s] == OPCODE["OP_PUSHDATA2"]: - s += 2 + struct.unpack(' Date: Fri, 1 Jun 2018 15:21:38 +0400 Subject: [PATCH 13/13] segwit flag --- pybtc/constants.py | 1 + pybtc/encode.py | 7 ++ pybtc/transaction.py | 102 +++++++++++++++++- tests/test/transaction_deserialize.py | 144 ++++++++++++++++++++++++-- 4 files changed, 241 insertions(+), 13 deletions(-) diff --git a/pybtc/constants.py b/pybtc/constants.py index 352906c..7001e04 100644 --- a/pybtc/constants.py +++ b/pybtc/constants.py @@ -1,6 +1,7 @@ from secp256k1 import lib as secp256k1 import random +MAX_AMOUNT = 2100000000000000 SIGHASH_ALL = 0x00000001 SIGHASH_NONE = 0x00000002 SIGHASH_SINGLE = 0x00000003 diff --git a/pybtc/encode.py b/pybtc/encode.py index 9f7a155..65e9e7f 100644 --- a/pybtc/encode.py +++ b/pybtc/encode.py @@ -36,12 +36,15 @@ def rebasebits(data, frombits, tobits, pad=True): raise Exception("invalid padding") return ret + def rebase_5_to_8(data, pad = True): return rebasebits(data, 5, 8, pad) + def rebase_8_to_5(data, pad = True): return rebasebits(data, 8, 5, pad) + def rebase_32_to_5(data): if type(data) == bytes: data = data.decode() @@ -53,12 +56,14 @@ def rebase_32_to_5(data): raise Exception("Non base32 characters") return b + def rebase_5_to_32(data, bytes = True): r = bytearray() for i in data: r.append(base32_int_map[i]) return r.decode() if not bytes else r + def bech32_polymod(values): """Internal function that computes the Bech32 checksum.""" generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] @@ -70,6 +75,7 @@ def bech32_polymod(values): chk ^= generator[i] if ((top >> i) & 1) else 0 return chk ^ 1 + def encode_base58(b): """Encode bytes to a base58-encoded string""" # Convert big-endian bytes to integer @@ -90,6 +96,7 @@ def encode_base58(b): break return b58_digits[0] * pad + res + def decode_base58(s): """Decode a base58-encoding string, returning bytes""" if not s: diff --git a/pybtc/transaction.py b/pybtc/transaction.py index 724c090..70a3206 100644 --- a/pybtc/transaction.py +++ b/pybtc/transaction.py @@ -2,11 +2,13 @@ from struct import unpack import json from .tools import * +from .address import PrivateKey from binascii import hexlify, unhexlify class Transaction(dict): - def __init__(self, raw_tx = None): + def __init__(self, raw_tx=None): + self["format"] = "raw" self["txId"] = None self["hash"] = None self["version"] = None @@ -22,7 +24,7 @@ class Transaction(dict): self["time"] = None self["blockTime"] = None self["blockIndex"] = None - self["coinbase"] = None + self["coinbase"] = False self["fee"] = None self["data"] = None self["amount"] = 0 @@ -79,15 +81,19 @@ class Transaction(dict): self["vIn"][0]["txId"] == b'\x00' * 32 and \ self["vIn"][0]["vOut"] == 0xffffffff: self["coinbase"] = True + else: + self["coinbase"] = False if sw: self["segwit"] = True self["hash"] = double_sha256(b) self["txId"] = double_sha256(b[:4] + b[6:sw] + b[-4:]) else: + self["segwit"] = False self["txId"] = double_sha256(b) self["hash"] = self["txId"] def decode(self, testnet = False): + self["format"] = "decoded" if type(self["txId"]) == bytes: self["txId"] = rh2s(self["txId"]) if "flag" in self: @@ -174,7 +180,7 @@ class Transaction(dict): def serialize(self, segwit=True, hex=False): chunks = [] chunks.append(struct.pack('= 0 + # assert type(sequence) == int + # assert sequence <= 0xffffffff and sequence >= 0 + # if type(script_sig) == str: + # script_sig = unhexlify(script_sig) + # else: + # assert type(script_sig) == bytes + # assert len(script_sig) <= 520 + # if private_key: + # if type(private_key) != PrivateKey: + # private_key = PrivateKey(private_key) + # if amount: + # assert type(amount) == int + # assert amount >= 0 and amount <= MAX_AMOUNT + # if tx_in_witness: + # assert type(tx_in_witness) == list + # l = 0 + # witness = [] + # for w in tx_in_witness: + # if type(w) == str: + # witness.append(unhexlify(w) if self["format"] == "raw" else w) + # else: + # witness.append(w if self["format"] == "raw" else unhexlify(w)) + # l += 1 + len(w) + # if len(w) >= 0x4c: + # l += 1 + # if len(w) > 0xff: + # l += 1 + # # witness script limit + # assert l <= 10000 + # if tx_id == b"\x00" * 32: + # assert v_out == 0 and sequence == 0xffffffff and len(script_sig) <= 100 + # self["coinbase"] = True + # + # k = len(self["vIn"]) + # self["vIn"][k] = dict() + # self["vIn"][k]["vOut"] = v_out + # self["vIn"][k]["sequence"] = sequence + # if self["format"] == "raw": + # self["vIn"][k]["txId"] = tx_id + # self["vIn"][k]["scriptSig"] = script_sig + # if tx_in_witness: + # self["segwit"] = True + # self["vIn"][k]["txInWitness"] = witness + # else: + # self["vIn"][k]["txId"] = rh2s(tx_id) + # self["vIn"][k]["scriptSig"] = script_sig + # self["vIn"][i]["scriptSigOpcodes"] = decode_script(script_sig) + # self["vIn"][i]["scriptSigAsm"] = decode_script(script_sig, 1) + # if tx_in_witness: + # self["segwit"] = True + # self["vIn"][k]["txInWitness"] = witness + # if amount: + # self["value"] = amount + # if private_key: + # self["privateKey"] = private_key + # + # # todo + # # if self["vOut"]: + # # self.__refresh_tx__() + # + # """ + # написать сценарии использования + # """ diff --git a/tests/test/transaction_deserialize.py b/tests/test/transaction_deserialize.py index 85a2098..ec41c0d 100644 --- a/tests/test/transaction_deserialize.py +++ b/tests/test/transaction_deserialize.py @@ -44,8 +44,15 @@ class TransactionDeserializeTests(unittest.TestCase): # print("OK") def test_segwit_deserialize(self): - non_segwit_view = "020000000140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000017a9144a1154d50b03292b3024370901711946cb7cccc38700000000" - segwit_view = "0200000000010140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000017a9144a1154d50b03292b3024370901711946cb7cccc387024830450221008604ef8f6d8afa892dee0f31259b6ce02dd70c545cfcfed8148179971876c54a022076d771d6e91bed212783c9b06e0de600fab2d518fad6f15a2b191d7fbd262a3e0121039d25ab79f41f75ceaf882411fd41fa670a4c672c23ffaf0e361a969cde0692e800000000" + non_segwit_view = "020000000140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420" \ + "100000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f50500" \ + "00000017a9144a1154d50b03292b3024370901711946cb7cccc38700000000" + segwit_view = "0200000000010140d43a99926d43eb0e619bf0b3d83b4a31f60c176beecfb9d35bf45e54d0f7420" \ + "00000017160014a4b4ca48de0b3fffc15404a1acdc8dbaae226955ffffffff0100e1f5050000000" \ + "017a9144a1154d50b03292b3024370901711946cb7cccc387024830450221008604ef8f6d8afa89" \ + "2dee0f31259b6ce02dd70c545cfcfed8148179971876c54a022076d771d6e91bed212783c9b06e0" \ + "de600fab2d518fad6f15a2b191d7fbd262a3e0121039d25ab79f41f75ceaf882411fd41fa670a4c" \ + "672c23ffaf0e361a969cde0692e800000000" print("Deserialize Segwit transaction") ns = Transaction(non_segwit_view) s = Transaction(segwit_view) @@ -53,8 +60,83 @@ class TransactionDeserializeTests(unittest.TestCase): self.assertEqual(s.serialize(segwit=True, hex = True), segwit_view) self.assertEqual(ns.serialize(segwit=False, hex = True), non_segwit_view) - non_segwit_view = "01000000060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93f554e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6eda0147304402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680ddf022012c52160ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee60147522103ee30ea502d692ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52db885d04dc43ca8562471759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab020ee0a80a818e4d20a52aa7ba367a0a2d430d22c26ccb4572527e259e14a01000000d900473044022064745ac8cae859bb19305a550981b8d39b69efec55b9a423dca645cd3b5c845502205cf375839d7f056235feb550a8138a03e75fa140503a2ce61fe239a3bfe42145014730440220728b0393d5427d8abb56a608c6b1a0b14c5455f3abeb56ce8a5c7f70035af71d022052a99e4e389790b364f6180caf1523d6da2b3daabe85952705023be2b5409b360147522103e296937dbdafdae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a251f74bd7e2103ead7ad0c398f928cbe851a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae0000000067a6c2e2f14fc412b3f5627fafac2fe43009bc403ec680839579444df2ce042b00000000da00483045022100d3bdc055fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e233102200e7ebb43fd39fb98c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c0147304402202f4338d2710edb1960dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216feb31051f7798297317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25e6ce5fdb716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a2bf9fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c419b2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781def27f25e91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f59627287a6c32180ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeadea55d17160bc6d11d47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb54ecd82d86efe83c0a1d35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c65bb26ec7581ad8652ae0000000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a5b4ac02ab682fdb301000000232200206ea344e9a4a8f8a8983479af2ae3ed29fab153955af14457780a304a6832b9c50000000016dcc4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e580d21578e35002502000000002322002049ea1f7c280b32fee0dce2e1801df2218df59d64614c4fe76c043ee2c80116700000000002005ed0b20000000017a91495c5c19257aa52bd4b702ba1a5e29b8d72a75a3a876d6b4e010000000017a91487b6255a5df746188f0bd22ed0194a40ec98f2de87be810700" - segwit_view = "010000000001060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93f554e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6eda0147304402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680ddf022012c52160ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee60147522103ee30ea502d692ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52db885d04dc43ca8562471759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab020ee0a80a818e4d20a52aa7ba367a0a2d430d22c26ccb4572527e259e14a01000000d900473044022064745ac8cae859bb19305a550981b8d39b69efec55b9a423dca645cd3b5c845502205cf375839d7f056235feb550a8138a03e75fa140503a2ce61fe239a3bfe42145014730440220728b0393d5427d8abb56a608c6b1a0b14c5455f3abeb56ce8a5c7f70035af71d022052a99e4e389790b364f6180caf1523d6da2b3daabe85952705023be2b5409b360147522103e296937dbdafdae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a251f74bd7e2103ead7ad0c398f928cbe851a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae0000000067a6c2e2f14fc412b3f5627fafac2fe43009bc403ec680839579444df2ce042b00000000da00483045022100d3bdc055fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e233102200e7ebb43fd39fb98c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c0147304402202f4338d2710edb1960dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216feb31051f7798297317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25e6ce5fdb716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a2bf9fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c419b2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781def27f25e91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f59627287a6c32180ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeadea55d17160bc6d11d47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb54ecd82d86efe83c0a1d35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c65bb26ec7581ad8652ae0000000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a5b4ac02ab682fdb301000000232200206ea344e9a4a8f8a8983479af2ae3ed29fab153955af14457780a304a6832b9c50000000016dcc4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e580d21578e35002502000000002322002049ea1f7c280b32fee0dce2e1801df2218df59d64614c4fe76c043ee2c80116700000000002005ed0b20000000017a91495c5c19257aa52bd4b702ba1a5e29b8d72a75a3a876d6b4e010000000017a91487b6255a5df746188f0bd22ed0194a40ec98f2de87000000000400473044022100d0d2ded141c9369bcc99de23d3d41d1d99d6cff47126df1b0c4d4797f947eacf021f790f1c112b3425ebc3251d719aae6ef0f9830b688585275591a5353f1f973801483045022100bf06c762e6ab64258d2f2777a66fe32ddd8f36e232b80bf5afa6ff9b9aa73ee0022049caf991fce808e60a9b17499f5e0dc11f6163e3ef7bca8109b72b5695d674210147522103edd556806048b319d71f43466c4415001bb32d8afe3aac06532d3ac210fd0e86210215e16727cf1389b4ee377487385f3ec595841a6bb747eb9c3a5cd559e9b1c8dc52ae040047304402204e9cc87526e148d236d692fa70104d26b8df632f30f4e3be38a2e99cec76d0f80220354ae575c3537c0ad2399a6037a9164b0cb147b12f262efc906649ca7950e2eb0147304402203553bcd1565804ec71c997c87006bd91c639b74a004b19a239c7f551aab5635a0220753f74e065c0b7cdf67d16b00f6a20dda7159a49f20aa493a7889b2851b2fea30147522103b09ac1fa65a55fa4feadea57c4cf417d7490065d8b844ada60c242a441e0e3a42103c0625169b46dbbde3492db7c62f1be8f582131467620cef17335306bad7ef88a52aebe810700" + non_segwit_view = "01000000060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00" \ + "000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93" \ + "f554e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6e" \ + "da0147304402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680d" \ + "df022012c52160ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee6014752" \ + "2103ee30ea502d692ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52d" \ + "b885d04dc43ca8562471759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab0" \ + "20ee0a80a818e4d20a52aa7ba367a0a2d430d22c26ccb4572527e259e14a01000000d9004730" \ + "44022064745ac8cae859bb19305a550981b8d39b69efec55b9a423dca645cd3b5c845502205c" \ + "f375839d7f056235feb550a8138a03e75fa140503a2ce61fe239a3bfe4214501473044022072" \ + "8b0393d5427d8abb56a608c6b1a0b14c5455f3abeb56ce8a5c7f70035af71d022052a99e4e38" \ + "9790b364f6180caf1523d6da2b3daabe85952705023be2b5409b360147522103e296937dbdaf" \ + "dae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a251f74bd7e2103ead7ad0c398f928cbe85" \ + "1a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae0000000067a6c2e2f14fc412b3f5" \ + "627fafac2fe43009bc403ec680839579444df2ce042b00000000da00483045022100d3bdc055" \ + "fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e233102200e7ebb43fd39fb98" \ + "c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c0147304402202f4338d2710edb19" \ + "60dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216feb31051f77982" \ + "97317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25e6ce5fdb" \ + "716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a2bf9" \ + "fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d" \ + "72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c" \ + "419b2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781d" \ + "ef27f25e91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f5962" \ + "7287a6c32180ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeade" \ + "a55d17160bc6d11d47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb" \ + "54ecd82d86efe83c0a1d35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c" \ + "65bb26ec7581ad8652ae0000000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a" \ + "5b4ac02ab682fdb301000000232200206ea344e9a4a8f8a8983479af2ae3ed29fab153955af1" \ + "4457780a304a6832b9c50000000016dcc4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e58" \ + "0d21578e35002502000000002322002049ea1f7c280b32fee0dce2e1801df2218df59d64614c" \ + "4fe76c043ee2c80116700000000002005ed0b20000000017a91495c5c19257aa52bd4b702ba1" \ + "a5e29b8d72a75a3a876d6b4e010000000017a91487b6255a5df746188f0bd22ed0194a40ec98" \ + "f2de87be810700" + segwit_view = "010000000001060c02c24bbfefd94cdc4f58a3f83f93e05b14ad968ec6aba54190c3dcba6eef1b00" \ + "000000da00483045022100f4dbf2ca7b5da97bd78818635d48004e6bf1a6f7e5e24bcecb7d93f554" \ + "e49eaf02200a05025d93475b6372d14bd8fe8366fe10570ade772b19d124d3b0175b9f6eda014730" \ + "4402202290feb53fc4cb077c5d3eed0ed5367fef4011ac708c1acaaa5003e4ed680ddf022012c521" \ + "60ae6b85fc59ceed80c7cacd5358b80712371733de7c76fef552114ee60147522103ee30ea502d69" \ + "2ecfc79dfcb6fb2d6914fc70ba80db971beebacccdbaed40d7c52102f52db885d04dc43ca8562471" \ + "759dd621b899faf508caba70963d9228bfd2415e52ae00000000dab020ee0a80a818e4d20a52aa7b" \ + "a367a0a2d430d22c26ccb4572527e259e14a01000000d900473044022064745ac8cae859bb19305a" \ + "550981b8d39b69efec55b9a423dca645cd3b5c845502205cf375839d7f056235feb550a8138a03e7" \ + "5fa140503a2ce61fe239a3bfe42145014730440220728b0393d5427d8abb56a608c6b1a0b14c5455" \ + "f3abeb56ce8a5c7f70035af71d022052a99e4e389790b364f6180caf1523d6da2b3daabe85952705" \ + "023be2b5409b360147522103e296937dbdafdae4d0a45b33d9a691b0c0e71d97bd2ffc82da895a25" \ + "1f74bd7e2103ead7ad0c398f928cbe851a6b387a5e74d139487caf4d4ac3dc3d478254dbbb4452ae" \ + "0000000067a6c2e2f14fc412b3f5627fafac2fe43009bc403ec680839579444df2ce042b00000000" \ + "da00483045022100d3bdc055fa5dcce335a1d572b1c3ccb8cc9ba91960c6361c77e29ded716e2331" \ + "02200e7ebb43fd39fb98c714098d4fda32d94cdbefdd96c0c918b788aacc6164953c014730440220" \ + "2f4338d2710edb1960dcf7136411f065a16bee4e44b86604d64c660315bc55040220238c1c3216fe" \ + "b31051f7798297317819da1dfa830d24a6a9e38a37367a68ebd101475221037b2987df626510ce25" \ + "e6ce5fdb716705e23fecb7398b2cbb0a1c0af7ca5da717210345e358653b4580b5bd68d263089a0a" \ + "2bf9fcc1e145fcf2a3d4b9ab5cd7e1a76752ae000000004f28d63103dfb86a5d92d2daf328bbb35d" \ + "72239766a5853b7076a90d1745813200000000da00483045022100e89ac8215ee87186de284c419b" \ + "2522ebfb2ecb8063d0f91942f2ad63f383d3d4022036485902bb1f2e0b2cc645aab8781def27f25e" \ + "91d8256d48dd48d5cfca1a21c20147304402201449379f1d57f2b7ad1dc0882f59627287a6c32180" \ + "ffa7637941b0eaa666dd4b022028eb0eed77e1b92de046098c855834a5feeadea55d17160bc6d11d" \ + "47184e8b51014752210283db605dc305201ab9be509a22d2c83b388002fb54ecd82d86efe83c0a1d" \ + "35822103146f745eff0ae31fe899aafd27d51d2c0f5b0c03f2f47b3c65bb26ec7581ad8652ae0000" \ + "000021cb3b00d1f22455e76e86872e00ef556578bcc112071e6a5b4ac02ab682fdb3010000002322" \ + "00206ea344e9a4a8f8a8983479af2ae3ed29fab153955af14457780a304a6832b9c50000000016dc" \ + "c4b40a514c43ed61d6c01a9006d7f21a6d30b99b3e580d21578e35002502000000002322002049ea" \ + "1f7c280b32fee0dce2e1801df2218df59d64614c4fe76c043ee2c80116700000000002005ed0b200" \ + "00000017a91495c5c19257aa52bd4b702ba1a5e29b8d72a75a3a876d6b4e010000000017a91487b6" \ + "255a5df746188f0bd22ed0194a40ec98f2de87000000000400473044022100d0d2ded141c9369bcc" \ + "99de23d3d41d1d99d6cff47126df1b0c4d4797f947eacf021f790f1c112b3425ebc3251d719aae6e" \ + "f0f9830b688585275591a5353f1f973801483045022100bf06c762e6ab64258d2f2777a66fe32ddd" \ + "8f36e232b80bf5afa6ff9b9aa73ee0022049caf991fce808e60a9b17499f5e0dc11f6163e3ef7bca" \ + "8109b72b5695d674210147522103edd556806048b319d71f43466c4415001bb32d8afe3aac06532d" \ + "3ac210fd0e86210215e16727cf1389b4ee377487385f3ec595841a6bb747eb9c3a5cd559e9b1c8dc" \ + "52ae040047304402204e9cc87526e148d236d692fa70104d26b8df632f30f4e3be38a2e99cec76d0" \ + "f80220354ae575c3537c0ad2399a6037a9164b0cb147b12f262efc906649ca7950e2eb0147304402" \ + "203553bcd1565804ec71c997c87006bd91c639b74a004b19a239c7f551aab5635a0220753f74e065" \ + "c0b7cdf67d16b00f6a20dda7159a49f20aa493a7889b2851b2fea30147522103b09ac1fa65a55fa4" \ + "feadea57c4cf417d7490065d8b844ada60c242a441e0e3a42103c0625169b46dbbde3492db7c62f1" \ + "be8f582131467620cef17335306bad7ef88a52aebe810700" print("Deserialize Segwit transaction") ns = Transaction(non_segwit_view) s = Transaction(segwit_view) @@ -62,9 +144,55 @@ class TransactionDeserializeTests(unittest.TestCase): self.assertEqual(s.serialize(segwit=True, hex = True), segwit_view) self.assertEqual(ns.serialize(segwit=False, hex = True), non_segwit_view) - tx = "01000000014cee27ba570d2cca50bb9b3f7374c7eb24ec16ffec0a077c84c1cc23b0161804010000008b48304502200f1100f78596c8d46fb2f39c570ce6945956a3dd33c48fbdbe53af1c383182ed022100a85b528ea21ee7f39b2ec1568ac19f26f4dd4fb9d3dbf70587986de3c2c90fa801410426e4d0890ad5272b2b9a10ca3f518f7e025932caa62f13467e444df89ed25f24f4fc5075cad32f468c8f7f913e30057449d65623726e7102f5eaa326d486ebf7ffffffff020010000000000000006020e908000000001976a914947236437233a71cb033a53932008dbfe346388e88ac00000000" + tx = "01000000014cee27ba570d2cca50bb9b3f7374c7eb24ec16ffec0a077c84c1cc23b0161804010000008b48304" \ + "502200f1100f78596c8d46fb2f39c570ce6945956a3dd33c48fbdbe53af1c383182ed022100a85b528ea21ee7" \ + "f39b2ec1568ac19f26f4dd4fb9d3dbf70587986de3c2c90fa801410426e4d0890ad5272b2b9a10ca3f518f7e0" \ + "25932caa62f13467e444df89ed25f24f4fc5075cad32f468c8f7f913e30057449d65623726e7102f5eaa326d4" \ + "86ebf7ffffffff020010000000000000006020e908000000001976a914947236437233a71cb033a53932008db" \ + "fe346388e88ac00000000" Transaction(tx) - tx = "01000000011e6c87805dad469ed72b42668b858df90e6b007c6410a7bde65bb1cf687d8409000000006b48304502204f3353cf129bb805fb90315aeb6d5ab8e0937129c4b0f1422495e42b6bf0e928022100cbdd2811a4c94397aa731c224ee0c7290fea6bc17a9b3ce5957d9937c60f4f97012103e2a0e6a91fa985ce4dda7f048fca5ec8264292aed9290594321aa53d37fdea32ffffffff0160e3160000000000fdaf0563ff054effffffff4da30546726f6d2061336136316665663433333039623966623233323235646637393130623033616663353436356239204d6f6e205365702031372030303a30303a303020323030310a46726f6d3a205361746f736869204e616b616d6f746f203c7361746f7368696e40676d782e636f6d3e0a446174653a204d6f6e2c2031322041756720323031332030323a32383a3032202d303230300a5375626a6563743a205b50415443485d2052656d6f7665202853494e474c457c444f55424c4529425954450a0a492072656d6f76656420746869732066726f6d20426974636f696e20696e20663165316662346264656638373863386663313536346661343138643434653735343161376538330a696e2053657074203720323031302c20616c6d6f73742074687265652079656172732061676f2e204265207761726e6564207468617420492068617665206e6f740a61637475616c6c792074657374656420746869732070617463682e0a2d2d2d0a206261636b656e64732f626974636f696e642f646573657269616c697a652e7079207c2020202038202b2d2d2d2d2d2d2d0a20312066696c65206368616e6765642c203120696e73657274696f6e282b292c20372064656c6574696f6e73282d290a0a64696666202d2d67697420612f6261636b656e64732f626974636f696e642f646573657269616c697a652e707920622f6261636b656e64732f626974636f696e642f646573657269616c697a652e70790a696e64657820363632303538332e2e38396239623162203130303634340a2d2d2d20612f6261636b656e64732f626974636f696e642f646573657269616c697a652e70790a2b2b2b20622f6261636b656e64732f626974636f696e642f646573657269616c697a652e70790a4040202d3238302c3130202b3238302c38204040206f70636f646573203d20456e756d65726174696f6e28224f70636f646573222c205b0a2020202020224f505f57495448494e222c20224f505f524950454d44313630222c20224f505f53484131222c20224f505f534841323536222c20224f505f48415348313630222c0a2020202020224f505f48415348323536222c20224f505f434f4445534550415241544f52222c20224f505f434845434b534947222c20224f505f434845434b534947564552494659222c20224f505f434845434b4d554c5449534947222c0a2020202020224f505f434845434b4d554c5449534947564552494659222c0a2d2020202028224f505f53494e474c45425954455f454e44222c2030784630292c0a2d2020202028224f505f444f55424c45425954455f424547494e222c20307846303030292c0a2020202020224f505f5055424b4559222c20224f505f5055424b455948415348222c0a2d2020202028224f505f494e56414c49444f50434f4445222c20307846464646292c0a2b2020202028224f505f494e56414c49444f50434f4445222c2030784646292c0a205d290a200a200a4040202d3239332c3130202b3239312c3620404020646566207363726970745f4765744f70286279746573293a0a202020202020202020766368203d204e6f6e650a2020202020202020206f70636f6465203d206f72642862797465735b695d290a20202020202020202069202b3d20310a2d20202020202020206966206f70636f6465203e3d206f70636f6465732e4f505f53494e474c45425954455f454e4420616e642069203c206c656e286279746573293a0a2d2020202020202020202020206f70636f6465203c3c3d20380a2d2020202020202020202020206f70636f6465207c3d206f72642862797465735b695d290a2d20202020202020202020202069202b3d20310a200a2020202020202020206966206f70636f6465203c3d206f70636f6465732e4f505f5055534844415441343a0a202020202020202020202020206e53697a65203d206f70636f64650a2d2d200a312e372e392e340a0a6800000000" + tx = "01000000011e6c87805dad469ed72b42668b858df90e6b007c6410a7bde65bb1cf687d8409000000006b48304" \ + "502204f3353cf129bb805fb90315aeb6d5ab8e0937129c4b0f1422495e42b6bf0e928022100cbdd2811a4c943" \ + "97aa731c224ee0c7290fea6bc17a9b3ce5957d9937c60f4f97012103e2a0e6a91fa985ce4dda7f048fca5ec82" \ + "64292aed9290594321aa53d37fdea32ffffffff0160e3160000000000fdaf0563ff054effffffff4da3054672" \ + "6f6d2061336136316665663433333039623966623233323235646637393130623033616663353436356239204" \ + "d6f6e205365702031372030303a30303a303020323030310a46726f6d3a205361746f736869204e616b616d6f" \ + "746f203c7361746f7368696e40676d782e636f6d3e0a446174653a204d6f6e2c2031322041756720323031332" \ + "030323a32383a3032202d303230300a5375626a6563743a205b50415443485d2052656d6f7665202853494e47" \ + "4c457c444f55424c4529425954450a0a492072656d6f76656420746869732066726f6d20426974636f696e206" \ + "96e20663165316662346264656638373863386663313536346661343138643434653735343161376538330a69" \ + "6e2053657074203720323031302c20616c6d6f73742074687265652079656172732061676f2e2042652077617" \ + "26e6564207468617420492068617665206e6f740a61637475616c6c7920746573746564207468697320706174" \ + "63682e0a2d2d2d0a206261636b656e64732f626974636f696e642f646573657269616c697a652e7079207c202" \ + "0202038202b2d2d2d2d2d2d2d0a20312066696c65206368616e6765642c203120696e73657274696f6e282b29" \ + "2c20372064656c6574696f6e73282d290a0a64696666202d2d67697420612f6261636b656e64732f626974636" \ + "f696e642f646573657269616c697a652e707920622f6261636b656e64732f626974636f696e642f6465736572" \ + "69616c697a652e70790a696e64657820363632303538332e2e38396239623162203130303634340a2d2d2d206" \ + "12f6261636b656e64732f626974636f696e642f646573657269616c697a652e70790a2b2b2b20622f6261636b" \ + "656e64732f626974636f696e642f646573657269616c697a652e70790a4040202d3238302c3130202b3238302" \ + "c38204040206f70636f646573203d20456e756d65726174696f6e28224f70636f646573222c205b0a20202020" \ + "20224f505f57495448494e222c20224f505f524950454d44313630222c20224f505f53484131222c20224f505" \ + "f534841323536222c20224f505f48415348313630222c0a2020202020224f505f48415348323536222c20224f" \ + "505f434f4445534550415241544f52222c20224f505f434845434b534947222c20224f505f434845434b53494" \ + "7564552494659222c20224f505f434845434b4d554c5449534947222c0a2020202020224f505f434845434b4d" \ + "554c5449534947564552494659222c0a2d2020202028224f505f53494e474c45425954455f454e44222c20307" \ + "84630292c0a2d2020202028224f505f444f55424c45425954455f424547494e222c20307846303030292c0a20" \ + "20202020224f505f5055424b4559222c20224f505f5055424b455948415348222c0a2d2020202028224f505f4" \ + "94e56414c49444f50434f4445222c20307846464646292c0a2b2020202028224f505f494e56414c49444f5043" \ + "4f4445222c2030784646292c0a205d290a200a200a4040202d3239332c3130202b3239312c362040402064656" \ + "6207363726970745f4765744f70286279746573293a0a202020202020202020766368203d204e6f6e650a2020" \ + "202020202020206f70636f6465203d206f72642862797465735b695d290a20202020202020202069202b3d203" \ + "10a2d20202020202020206966206f70636f6465203e3d206f70636f6465732e4f505f53494e474c4542595445" \ + "5f454e4420616e642069203c206c656e286279746573293a0a2d2020202020202020202020206f70636f64652" \ + "03c3c3d20380a2d2020202020202020202020206f70636f6465207c3d206f72642862797465735b695d290a2d" \ + "20202020202020202020202069202b3d20310a200a2020202020202020206966206f70636f6465203c3d206f7" \ + "0636f6465732e4f505f5055534844415441343a0a202020202020202020202020206e53697a65203d206f7063" \ + "6f64650a2d2d200a312e372e392e340a0a6800000000" + Transaction(tx) + tx = "0100000001c86c4ddc01f59b748e6a55a6d09c5bce7574fbdec721ca468768b5d6d9e3fb00000000006b48304" \ + "5022076b5504ad7aff614e32159ac055362a1197c9a5e50de48cf4f05c7547b39c0b5022100ea67efb2585aae" \ + "40a53363f48e3e38dfc7387b238f8716fa53a5f0ed51b7c314012102452928340bc618777d217a52f30d8e144" \ + "0d98f561c523a31834a1614106f3c15ffffffff025cf5352f0d0000001976a9141855d5890b8aec536ffc3e59" \ + "cb586e98b34b5b9288ac0bd32204000000001976a9146f3f6845da01e856a426c31dfeef188c06bf574d88ac0" \ + "0000000" Transaction(tx) - tx = "0100000001c86c4ddc01f59b748e6a55a6d09c5bce7574fbdec721ca468768b5d6d9e3fb00000000006b483045022076b5504ad7aff614e32159ac055362a1197c9a5e50de48cf4f05c7547b39c0b5022100ea67efb2585aae40a53363f48e3e38dfc7387b238f8716fa53a5f0ed51b7c314012102452928340bc618777d217a52f30d8e1440d98f561c523a31834a1614106f3c15ffffffff025cf5352f0d0000001976a9141855d5890b8aec536ffc3e59cb586e98b34b5b9288ac0bd32204000000001976a9146f3f6845da01e856a426c31dfeef188c06bf574d88ac00000000" - Transaction(tx) \ No newline at end of file