diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pybtc/__init__.py b/pybtc/__init__.py new file mode 100644 index 0000000..4987c0f --- /dev/null +++ b/pybtc/__init__.py @@ -0,0 +1,6 @@ +# from .tools import * +# from .opcodes import * +from .consensus import * +from .blockchain import * + +version = "2.0.1" diff --git a/pybtc/blockchain.py b/pybtc/blockchain.py new file mode 100644 index 0000000..2d59f6f --- /dev/null +++ b/pybtc/blockchain.py @@ -0,0 +1,612 @@ +import io +import json +import math +from .opcodes import * +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) + + + +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.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.address.append(b"\x00"+self.script[1].data) + elif self.pattern == "OP_0 <32>": + self.type = "P2WSH" + self.ntype = 6 + self.address.append(b"\x00"+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) + + +class Output: + """ Transactin output class """ + def __init__(self, value, script): + self.value = value + self.pk_script = Script(script) + + @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(): + def __init__(self, version = 1, tx_in = [], tx_out = [] , lock_time = 0, + hash=None, size = 0, timestamp = None, + marker = None, flag = None, witness = [], + whash = None, vsize = None): + self.hash = hash + self.whash = whash + self.vsize = vsize + self.witness = witness + self.marker = marker + self.flag = flag + self.valid = True + self.lock = False + self.orphaned = False + self.in_sum = None + self.tx_fee = None + self.version = version + self.tx_in_count = len(tx_in) + self.tx_in = tx_in + self.tx_out_count = len (tx_out) + self.tx_out = tx_out + self.lock_time = lock_time + if self.tx_in: + self.coinbase = self.tx_in[0].coinbase + else: + self.coinbase = False + self.double_spend = 0 + self.data = None + self.ip = None + self.size = size + if timestamp is not None : self.timestamp = timestamp + else: self.timestamp = int(time.time()) + self.op_sig_count = 0 + self.sum_value_age = 0 + self.total_outs_value = 0 + for i in self.tx_out: + self.op_sig_count += i.pk_script.op_sig_count + if i.pk_script.type=="NULL_DATA": + self.data = i.pk_script.data + for out in self.tx_out: + self.total_outs_value += out.value + if witness is None: + self.witness = [Witness.deserialize(b"\x00") for i in range(len(tx_in))] + if hash is None: + self.recalculate_txid() + + def recalculate_txid(self): + self.hash = double_sha256(self.serialize(segwit=False)) + self.whash = double_sha256(self.serialize(segwit=True)) + + def add_input(self, tx_hash, output_number, + sequence = 0xffffffff, + sig_script = b"", + amount = None, + private_key = None): + self.tx_in.append(Input((tx_hash, output_number), sig_script, sequence, amount, private_key)) + self.witness.append(Witness.deserialize(b"\x00")) + self.tx_in_count += 1 + self.recalculate_txid() + + def add_P2SH_output(self, amount, p2sh_address): + if type(p2sh_address)==str: + p2sh_address = decode_base58(p2sh_address)[1:-4] + if len(p2sh_address) != 20: + raise Exception("Invalid output hash160") + self.tx_out.append(Output(amount, + OPCODE["OP_HASH160"] + b'\x14' + p2sh_address + OPCODE["OP_EQUAL"])) + self.tx_out_count += 1 + self.recalculate_txid() + + def add_P2PKH_output(self, amount, p2pkh_address): + if type(p2pkh_address)==str: + p2pkh_address = decode_base58(p2pkh_address)[1:-4] + if len(p2pkh_address) != 20: + raise p2pkh_address("Invalid output hash160") + self.tx_out.append(Output(amount, + OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + b'\x14' + \ + p2pkh_address + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"])) + self.tx_out_count += 1 + self.recalculate_txid() + + + + + def __str__(self): + return 'Transaction object [%s] [%s]'% (hexlify(self.hash[::-1]),id(self)) + + + def serialize(self, segwit = False, hex = False): + version = self.version.to_bytes(4,'little') + ninputs = to_var_int(self.tx_in_count) + inputs = [] + for number, i in enumerate(self.tx_in): + input = i.outpoint[0]+i.outpoint[1].to_bytes(4,'little') + input += to_var_int(len(i.sig_script.raw)) + i.sig_script.raw + input += i.sequence.to_bytes(4,'little') + inputs.append(input) + nouts = to_var_int(self.tx_out_count) + outputs = [] + for number, i in enumerate(self.tx_out): + outputs.append(i.value.to_bytes(8,'little')+to_var_int(len(i.pk_script.raw))+i.pk_script.raw) + marke_flag = b"\x00\x01" if segwit else b"" + witness = b"" + if segwit: + for w in self.witness: + witness += w.serialize() + result = version + marke_flag + ninputs + b''.join(inputs) +\ + nouts + b''.join(outputs) + witness + self.lock_time.to_bytes(4,'little') + if hex: + return hexlify(result).decode() + else: + return result + + def sign_P2SHP2WPKH_input(self, sighash_type, input_index, private_key = None, amount = None): + if type(private_key) == str: + private_key = WIF2priv(private_key) + if amount is not None: + self.tx_in[input_index].amount = amount + else: + amount = self.tx_in[input_index].amount + if private_key is not None: + 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_hash160 = hash160(pubkey) + scriptCode = b"\x19" + OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + scriptCode += b'\x14' + pubkey_hash160 + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] + self.tx_in[input_index].sig_script = Script(b'\x16\x00\x14' + pubkey_hash160) # P2WPKHredeemScript + sighash = self.sighash_segwit(sighash_type, input_index, scriptCode, amount) + signature = sign_message(sighash, private_key) + sighash_type.to_bytes(1,'little') + self.witness[input_index] = Witness([signature, pubkey]) + self.recalculate_txid() + + def sign_P2PKH_input(self, sighash_type, input_index, compressed = True, private_key = None): + if private_key is not None: + 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_hash160 = hash160(pubkey) + scriptCode = OPCODE["OP_DUP"] + OPCODE["OP_HASH160"] + b'\x14' + \ + pubkey_hash160 + OPCODE["OP_EQUALVERIFY"] + OPCODE["OP_CHECKSIG"] + sighash = self.sighash(sighash_type, input_index, scriptCode) + signature = sign_message(sighash, private_key) + sighash_type.to_bytes(1, 'little') + sig_script = len(signature).to_bytes(1, 'little') + signature + \ + len(pubkey).to_bytes(1, 'little') + pubkey + self.tx_in[input_index].sig_script = Script(sig_script) + self.recalculate_txid() + + def sighash(self, sighash_type, input_index, scriptCode, hex = False): + if type(scriptCode) == str: + scriptCode = unhexlify(scriptCode) + if len(self.tx_in) - 1 < input_index: + raise Exception('Input not exist') + preimage = bytearray() + 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) + 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') + 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)) + 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) + 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() + + + def sighash_segwit(self, sighash_type, input_index, scriptCode, amount, hex = False): + if type(scriptCode) == str: + scriptCode = unhexlify(scriptCode) + if len(self.tx_in)-1 < input_index: + raise Exception('Input not exist') + preimage = bytearray() + # 1. nVersion of the transaction (4-byte little endian) + preimage += self.version.to_bytes(4,'little') + # 2. hashPrevouts (32-byte hash) + # 3. hashSequence (32-byte hash) + # 4. outpoint (32-byte hash + 4-byte little endian) + # 5. scriptCode of the input (serialized as scripts inside CTxOuts) + # 6. value of the output spent by this input (8-byte little endian) + # 7. nSequence of the input (4-byte little endian) + hp = bytearray() + hs = bytearray() + for n, i in enumerate(self.tx_in): + if not (sighash_type & SIGHASH_ANYONECANPAY): + hp += i.outpoint[0] + i.outpoint[1].to_bytes(4,'little') + if (sighash_type&31) != SIGHASH_SINGLE and (sighash_type&31) != SIGHASH_NONE: + hs += i.sequence.to_bytes(4,'little') + if n == input_index: + outpoint = i.outpoint[0]+i.outpoint[1].to_bytes(4,'little') + nSequence = i.sequence.to_bytes(4,'little') + hashPrevouts = double_sha256(hp) if hp else b'\x00'*32 + hashSequence = double_sha256(hs) if hs else b'\x00'*32 + value = amount.to_bytes(8,'little') + # 8. hashOutputs (32-byte hash) + 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 + 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 + 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') + preimage += sighash_type.to_bytes(4, 'little') + return double_sha256(preimage) if not hex else hexlify(double_sha256(preimage)).decode() + + + def json(self): + r = dict() + r["txid"] = rh2s(self.hash) + r["wtxid"] = r["txid"] if self.whash is None else rh2s(self.whash) + r["size"] = self.size + r["vsize"] = self.vsize + r["version"] = self.version + r["locktime"] = self.lock_time + r["vin"] = list() + r["vout"] = list() + for i in self.tx_in: + input = {"txid": rh2s(i.outpoint[0]), + "vout": i.outpoint[1], + "scriptSig": {"hex": hexlify(i.sig_script.raw).decode(), + "asm": i.sig_script.asm}, + "sequence": i.sequence} + if i.coinbase: + input["coinbase"] = hexlify(i.sig_script.raw).decode() + r["vin"].append(input) + if self.witness is not None: + for index, w in enumerate(self.witness): + r["vin"][index]["witness"] = w.hex() + for index, o in enumerate(self.tx_out): + out = {"value": o.value, + "n": index, + "scriptPubKey": {"hex": hexlify(o.pk_script.raw).decode()}, + "asm": o.pk_script.asm, + "type": o.pk_script.type} + r["vout"].append(out) + + return json.dumps(r) + + + @classmethod + def deserialize(cls, stream): + stream = get_stream(stream) + raw_tx = bytearray() + raw_wtx = bytearray() + start = stream.tell() + version = int.from_bytes(stream.read(4), 'little') + marker = stream.read(1) + flag = stream.read(1) + if marker == b"\x00" and flag == b"\x01": + # segwit format + point1 = stream.tell() + tx_in = read_var_list(stream, Input) + tx_out = read_var_list(stream, Output) + point2 = stream.tell() + inputs_count = len(tx_in) + witness = [Witness.deserialize(stream) for i in range(inputs_count)] + point3 = stream.tell() + lock_time = int.from_bytes(stream.read(4), 'little') + # calculate tx_id hash + size = stream.tell() - start + stream.seek(start) + raw_tx += stream.read(4) + stream.seek(2,1) + raw_tx += stream.read(point2 - point1) + stream.seek(point3-point2, 1) + raw_tx += stream.read(4) + tx_id = double_sha256(raw_tx) + for w in witness: + if not w.empty: + # caluculate wtx_id + stream.seek(start) + data = stream.read(size) + wtx_id = double_sha256(data) + break + else: + wtx_id = tx_id + vsize = math.ceil((len(raw_tx) * 3 + size) / 4) + else: + stream.seek(start) + marker = b"\x00" + flag = b"\x01" + version = int.from_bytes(stream.read(4), 'little') + tx_in = read_var_list(stream, Input) + tx_out = read_var_list(stream, Output) + witness = [Witness.deserialize(b"\x00") for i in range(len(tx_in))] + lock_time = int.from_bytes(stream.read(4), 'little') + size = stream.tell() - start + stream.seek(start) + data = stream.read(size) + tx_id = double_sha256(data) + wtx_id = None + vsize = size + + return cls(version, tx_in, tx_out, lock_time, + hash = tx_id, size = size, + marker = marker, flag = flag, + witness = witness, whash = wtx_id, vsize = vsize) + + +class Block(): + def __init__(self, version, prev_block, merkle_root, + timestamp, bits, nonce, txs, block_size,hash=None): + self.hash = hash + self.version = version + self.prev_block = prev_block + self.merkle_root = merkle_root + self.timestamp = timestamp + self.bits = bits + self.nonce = nonce + self.txs = txs + self.block_size = block_size + self.height = None + self.id = None + self.chain = None + self.amount = 0 + self.mountpoint = None + self.side_branch_set = None + self.tx_hash_list = list() + self.op_sig_count = 0 + for t in txs: + if t.hash in txs: + raise Exception("CVE-2012-2459") # merkle tree malleability + self.op_sig_count += t.op_sig_count + self.tx_hash_list.append(t.hash) + self.target = None + self.fee = 0 + + @classmethod + def deserialize(cls, stream): + stream = get_stream(stream) + header = stream.read(80) + stream.seek(-80, 1) + kwargs = { + 'hash': hashlib.sha256(hashlib.sha256(header).digest()).digest(), + 'version': int.from_bytes(stream.read(4), 'little'), + 'prev_block': stream.read(32), + 'merkle_root': stream.read(32), + 'timestamp': int.from_bytes(stream.read(4), 'little'), + 'bits': int.from_bytes(stream.read(4), 'little'), + 'nonce': int.from_bytes(stream.read(4), 'little'), + 'txs': read_var_list(stream, Transaction), + 'block_size': stream.tell() + } + return cls(**kwargs) + diff --git a/pybtc/consensus.py b/pybtc/consensus.py new file mode 100644 index 0000000..2bd39d6 --- /dev/null +++ b/pybtc/consensus.py @@ -0,0 +1,68 @@ +MAX_BLOCK_SIZE = 1000000 +MAX_STANDARD_TX_SIZE = 100000 +MAX_P2SH_SIGOPS = 15 +MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE/50 +MAX_STANDARD_TX_SIGOPS = MAX_BLOCK_SIGOPS/5 +MIN_FEE = 10 +MAX_SCRIPT_ELEMENT_SIZE = 520 +MAX_OPS_PER_SCRIPT = 201 +MAX_PUBKEYS_PER_MULTISIG = 20 +NULL_DATA_LIMIT = 80 + +# SCRIPT VERIFICATION FLAGS +SCRIPT_VERIFY_NONE = 0b0000000000000001 +# ?? +SCRIPT_VERIFY_P2SH = 0b0000000000000010 +# Evaluate P2SH subscripts (softfork safe, BIP16). +SCRIPT_VERIFY_STRICTENC = 0b0000000000000100 +# Passing a non-strict-DER signature or one with undefined hashtype to a +# checksig operation causes script failure. +# Evaluating a pubkey that is not (0x04 + 64 bytes) or (0x02 or 0x03 + 32 bytes) +# by checksig causes script failure. +# (softfork safe, but not used or intended as a consensus rule). +SCRIPT_VERIFY_DERSIG = 0b0000000000001000 +# Passing a non-strict-DER signature to a checksig operation causes script failure +# (softfork safe, BIP62 rule 1) +SCRIPT_VERIFY_LOW_S = 0b0000000000010000 +# Passing a non-strict-DER signature or one with S > order/2 to a checksig operation +# causes script failure +# (softfork safe, BIP62 rule 5). +SCRIPT_VERIFY_NULLDUMMY = 0b0000000000100000 +# verify dummy stack item consumed by CHECKMULTISIG is of zero-length +# (softfork safe, BIP62 rule 7). +SCRIPT_VERIFY_SIGPUSHONLY = 0b0000000001000000 +# Using a non-push operator in the scriptSig causes script failure +# (softfork safe, BIP62 rule 2). +SCRIPT_VERIFY_MINIMALDATA = 0b0000000010000000 +# Require minimal encodings for all push operations (OP_0... OP_16, OP_1NEGATE +# where possible, direct pushes up to 75 bytes, OP_PUSHDATA up to 255 bytes, +# OP_PUSHDATA2 for anything larger). Evaluating any other push causes the script +# to fail (BIP62 rule 3). In addition, whenever a stack element is interpreted +# as a number, it must be of minimal length (BIP62 rule 4). +SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = 0b0000000100000000 +# Discourage use of NOPs reserved for upgrades (NOP1-10) +# Provided so that nodes can avoid accepting or mining transactions +# containing executed NOP's whose meaning may change after a soft-fork, +# thus rendering the script invalid; with this flag set executing +# discouraged NOPs fails the script. This verification flag will never be +# a mandatory flag applied to scripts in a block. NOPs that are not +# executed, e.g. within an unexecuted IF ENDIF block, are *not* rejected. +SCRIPT_VERIFY_CLEANSTACK = 0b0000001000000000 +# Require that only a single stack element remains after evaluation. This changes the success criterion from +# "At least one stack element must remain, and when interpreted as a boolean, it must be true" to +# "Exactly one stack element must remain, and when interpreted as a boolean, it must be true". +# (softfork safe, BIP62 rule 6) +# Note: CLEANSTACK should never be used without P2SH. +SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY = 0b0000010000000000 +# See BIP65 for details. +SCRIPT_VERIFY_CHECKSEQUENCEVERIFY = 0b0000100000000000 +# See BIP112 for details + + +MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH +STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VERIFY_FLAGS |\ + SCRIPT_VERIFY_STRICTENC |\ + SCRIPT_VERIFY_MINIMALDATA |\ + SCRIPT_VERIFY_NULLDUMMY |\ + SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS |\ + SCRIPT_VERIFY_CLEANSTACK \ No newline at end of file diff --git a/pybtc/opcodes.py b/pybtc/opcodes.py new file mode 100644 index 0000000..95d8df6 --- /dev/null +++ b/pybtc/opcodes.py @@ -0,0 +1,148 @@ + +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"] + )) + diff --git a/pybtc/tools.py b/pybtc/tools.py new file mode 100644 index 0000000..56b6c93 --- /dev/null +++ b/pybtc/tools.py @@ -0,0 +1,586 @@ +import hashlib +from binascii import hexlify, unhexlify +import time +import random +import struct +import hmac +from secp256k1 import lib as secp256k1 +from secp256k1 import ffi + + +SIGHASH_ALL = 0x00000001 +SIGHASH_NONE = 0x00000002 +SIGHASH_SINGLE = 0x00000003 +SIGHASH_ANYONECANPAY = 0x00000080 +MAX_INT_PRIVATE_KEY = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 + +EC_COMPRESSED = secp256k1.SECP256K1_EC_COMPRESSED +EC_UNCOMPRESSED = secp256k1.SECP256K1_EC_UNCOMPRESSED + +FLAG_SIGN = secp256k1.SECP256K1_CONTEXT_SIGN +FLAG_VERIFY = secp256k1.SECP256K1_CONTEXT_VERIFY +ALL_FLAGS = FLAG_SIGN | FLAG_VERIFY +NO_FLAGS = secp256k1.SECP256K1_CONTEXT_NONE + +HAS_RECOVERABLE = hasattr(secp256k1, 'secp256k1_ecdsa_sign_recoverable') +HAS_SCHNORR = hasattr(secp256k1, 'secp256k1_schnorr_sign') +HAS_ECDH = hasattr(secp256k1, 'secp256k1_ecdh') + +ECDSA_CONTEXT_SIGN = secp256k1.secp256k1_context_create(FLAG_SIGN) +ECDSA_CONTEXT_VERIFY = secp256k1.secp256k1_context_create(FLAG_VERIFY) +ECDSA_CONTEXT_ALL = secp256k1.secp256k1_context_create(ALL_FLAGS) +secp256k1.secp256k1_context_randomize(ECDSA_CONTEXT_SIGN, + random.SystemRandom().randint(0,MAX_INT_PRIVATE_KEY).to_bytes(32,byteorder="big")) + +SCRIPT_TYPES = { "P2PKH": 0, + "P2SH" : 1, + "PUBKEY": 2, + "NULL_DATA": 3, + "MULTISIG": 4, + "NON_STANDART": 5, + "SP2PKH": 6 + } + +b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +# +# Encoding functions +# +def encode_base58(b): + """Encode bytes to a base58-encoded string""" + # Convert big-endian bytes to integer + n = int('0x0' + hexlify(b).decode('utf8'), 16) + # Divide that integer into bas58 + res = [] + while n > 0: + n, r = divmod(n, 58) + res.append(b58_digits[r]) + res = ''.join(res[::-1]) + # Encode leading zeros as base58 zeros + czero = 0 + pad = 0 + for c in b: + if c == czero: + pad += 1 + else: + break + return b58_digits[0] * pad + res + +def decode_base58(s): + """Decode a base58-encoding string, returning bytes""" + if not s: + return b'' + # Convert the string to an integer + n = 0 + for c in s: + n *= 58 + if c not in b58_digits: + raise Exception('Character %r is not a valid base58 character' % c) + digit = b58_digits.index(c) + n += digit + # Convert the integer to bytes + h = '%x' % n + if len(h) % 2: + h = '0' + h + res = unhexlify(h.encode('utf8')) + # Add padding back. + pad = 0 + for c in s[:-1]: + if c == b58_digits[0]: + pad += 1 + else: + break + return b'\x00' * pad + res + +# +# Hash functions +# +def sha256(bytes): + return hashlib.sha256(bytes).digest() + +def double_sha256(bytes): + return sha256(sha256(bytes)) + +def hmac_sha512(key, data): + return hmac.new(key, data, hashlib.sha512).digest() + +def ripemd160(bytes): + h = hashlib.new('ripemd160') + h.update(bytes) + return h.digest() + +def hash160(bytes): + return ripemd160(sha256(bytes)) + + +# +# Bitcoin keys/ addresses +# +def create_priv(): + """ + :return: 32 bytes private key + """ + q = time.time() + rnd = random.SystemRandom() + a = rnd.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 + else: + if int.from_bytes(h,byteorder="big") 73): + return False + # A signature is of type 0x30 (compound). + if sig[0] != 0x30: + return False + # Make sure the length covers the entire signature. + if sig[1] != (length - 3): + return False + # Extract the length of the R element. + lenR = sig[3] + # Make sure the length of the S element is still inside the signature. + if (5 + lenR) >= length: + return False + # Extract the length of the S element. + lenS = sig[5 + lenR] + # Verify that the length of the signature matches the sum of the length + # of the elements. + if (lenR + lenS + 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: + 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): + return False + # Check whether the S element is an integer. + if sig[lenR + 4] != 0x02: + return False + # Zero-length integers are not allowed for S. + if lenS == 0: + return False + # Negative numbers are not allowed for S. + if sig[lenR + 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): + return False + return True + + +# +# Transaction encoding +# + +def rh2s(tthash): + return hexlify(tthash[::-1]).decode() + +def s2rh(hash_string): + return unhexlify(hash_string)[::-1] + +def merkleroot(tx_hash_list): + tx_hash_list = list(tx_hash_list) + if len(tx_hash_list) == 1: + return tx_hash_list[0] + while True: + new_hash_list = list() + while tx_hash_list: + h1 = tx_hash_list.pop() + try: + h2 = tx_hash_list.pop() + except: + h2 = h1 + new_hash_list.insert(0, double_sha256(h1 + h2)) + if len(new_hash_list) > 1: + tx_hash_list = new_hash_list + else: + return new_hash_list[0] + + + + + +# +# +# + + +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 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 + + +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') + if i <= 0xffff: + return b'\xfd' + i.to_bytes(2, byteorder='little') + if i <= 0xffffffff: + return b'\xfe' + i.to_bytes(4, byteorder='little') + return b'\xff' + i.to_bytes(8, byteorder='little') + + +def read_var_int(stream): + l = stream.read(1) + bytes_length = var_int_len(l[0]) + return l + stream.read(bytes_length - 1) + + +def read_var_list(stream, data_type): + count = from_var_int(read_var_int(stream)) + return [data_type.deserialize(stream) for i in range(count)] + + + + +# generic big endian MPI format +def bn_bytes(v, have_ext=False): + ext = 0 + if have_ext: + ext = 1 + return ((v.bit_length() + 7) // 8) + ext + + +def bn2bin(v): + s = bytearray() + i = bn_bytes(v) + while i > 0: + s.append((v >> ((i - 1) * 8)) & 0xff) + i -= 1 + return s + + +def bin2bn(s): + l = 0 + for ch in s: + l = (l << 8) | ch + return l + + +def bn2mpi(v): + have_ext = False + if v.bit_length() > 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: + ext.append(0) + v_bin = bn2bin(v) + if neg: + if have_ext: + ext[0] |= 0x80 + else: + v_bin[0] |= 0x80 + return s + ext + v_bin + + +def mpi2bn(s): + if len(s) < 4: + return None + s_size = bytes(s[:4]) + v_len = struct.unpack(b">I", s_size)[0] + if len(s) != (v_len + 4): + return None + if v_len == 0: + return 0 + + v_str = bytearray(s[4:]) + neg = False + i = v_str[0] + if i & 0x80: + neg = True + i &= ~0x80 + v_str[0] = i + + v = bin2bn(v_str) + + if neg: + return -v + return v + +# bitcoin-specific little endian format, with implicit size + + +def mpi2vch(s): + r = s[4:] # strip size + # if r: + r = r[::-1] # reverse string, converting BE->LE + # else: r=b'\x00' + return r + + +def bn2vch(v): + return bytes(mpi2vch(bn2mpi(v))) + + +def vch2mpi(s): + r = struct.pack(b">I", len(s)) # size + r += s[::-1] # reverse string, converting LE->BE + return r + + +def vch2bn(s): + return mpi2bn(vch2mpi(s)) + + +def i2b(i): return bn2vch(i) + + +def b2i(b): return vch2bn(b) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1785cb7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +secp256k1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1fcd128 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 +# coding: utf-8 +# todo install libsec256 part +from distutils.core import setup + +setup(name='pybtc', + version='1.0.1', + description='Bitcoin library', + author='Alexsei Karpov', + author_email='admin@bitaps.com', + url='https://github.com/bitaps-com/pybtc', + packages=['pybtc', ], + ) \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..fec37ce --- /dev/null +++ b/test.py @@ -0,0 +1,8 @@ +import unittest +import test + +testLoad = unittest.TestLoader() +suites = testLoad.loadTestsFromModule(test) + +runner = unittest.TextTestRunner(verbosity=2) +runner.run(suites) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..63ac9cc --- /dev/null +++ b/test/__init__.py @@ -0,0 +1,6 @@ +from .script_deserialize import * +from .hash_functions import * +from .address_functions import * +from .transaction_deserialize import * +from .sighash import * +from .ecdsa import * \ No newline at end of file diff --git a/test/address_functions.py b/test/address_functions.py new file mode 100644 index 0000000..4eda16f --- /dev/null +++ b/test/address_functions.py @@ -0,0 +1,14 @@ +import unittest +from pybtc import tools +from binascii import unhexlify + + +class AddressFunctionsTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting address functions:\n") + + def test_pub2segwit(self): + print("pub2segwit") + self.assertEqual(tools.pub2segwit(unhexlify("03db633162d49193d1178a5bbb90bde2f3c196ba0296f010b12a2320a7c6568582")), + "3PjV3gFppqmDEHjLvqDWv3Y4riLMQg7X1y") \ No newline at end of file diff --git a/test/ecdsa.py b/test/ecdsa.py new file mode 100644 index 0000000..8c195a6 --- /dev/null +++ b/test/ecdsa.py @@ -0,0 +1,26 @@ +import unittest +from pybtc import * + +class ECDSATests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting ECDSA:\n") + def test_private_to_public(self): + """ + ["raw_transaction, script, input_index, hashType, signature_hash (result)"], + :return: + """ + print("\nPrivate key to Public key ") + k = bytearray.fromhex("eb696a065ef48a2192da5b28b694f87544b30fae8327c4510137a922f32c6dcf") + + self.assertEqual(priv2pub(k, hex=True), + "03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873") + print("Sign message") + msg = bytearray.fromhex('64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6') + self.assertEqual(sign_message(msg, k, True), + "3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb") + print("Verify signature") + s = '3044022047ac8e878352d3ebbde1c94ce3a10d057c24175747116f8288e5d794d12d482f0220217f36a485cae903c713331d877c1f64677e3622ad4010726870540656fe9dcb' + self.assertEqual(verify_signature(s,priv2pub(k, hex=True), msg), True) + + diff --git a/test/hash_functions.py b/test/hash_functions.py new file mode 100644 index 0000000..4416afd --- /dev/null +++ b/test/hash_functions.py @@ -0,0 +1,25 @@ +import unittest +from pybtc import tools +from binascii import unhexlify + + + +class HashFunctionsTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting hash functions:\n") + + def test_double_sha256(self): + print("Double SHA256") + self.assertEqual(tools.double_sha256(b"test double sha256"), + unhexlify("1ab3067efb509c48bda198f48c473f034202537c28b7b4c3b2ab2c4bf4a95c8d")) + + def test_ripemd160(self): + print("RIPEMD160") + self.assertEqual(tools.ripemd160(b"test ripemd160"), + unhexlify("45b17861a7defaac439f740d890f3dac4813cc37")) + + def test_hash160(self): + print("HASH160") + self.assertEqual(tools.ripemd160(b"test hash160"), + unhexlify("46a80bd289028559818a222eea64552d7a6a966f")) \ No newline at end of file diff --git a/test/script_deserialize.py b/test/script_deserialize.py new file mode 100644 index 0000000..b967cce --- /dev/null +++ b/test/script_deserialize.py @@ -0,0 +1,165 @@ +import unittest +from pybtc import blockchain +from binascii import unhexlify +from pybtc import address2hash160 + + +class ScriptDeserializeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting Script class deserialization:\n") + def test_p2pkh(self): + print("Deserialize P2PKH") + s = blockchain.Script("76a9143520dd524f6ca66f63182bb23efff6cc8ee3ee6388ac") + self.assertEqual(s.type, "P2PKH") + self.assertEqual(s.ntype, 0) + self.assertEqual(s.asm, "OP_DUP OP_HASH160 3520dd524f6ca66f63182bb23efff6cc8ee3ee63 OP_EQUALVERIFY OP_CHECKSIG") + self.assertEqual(s.address[0], address2hash160("15qvBdqSWQCuLQPXVoWViG2GvjeARmpYPw")) + self.assertEqual(s.pattern, "OP_DUP OP_HASH160 <20> OP_EQUALVERIFY OP_CHECKSIG") + self.assertEqual(s.op_sig_count, 1) + + def test_p2sh(self): + print("Deserialize P2SH") + s = blockchain.Script("a91469f37572ab1b69f304f987b119e2450e0b71bf5c87") + self.assertEqual(s.type, "P2SH") + self.assertEqual(s.ntype, 1) + self.assertEqual(s.asm, "OP_HASH160 69f37572ab1b69f304f987b119e2450e0b71bf5c OP_EQUAL") + self.assertEqual(s.address[0], address2hash160("3BMEXVsYyfKB5h3m53XRSFHkqi1zPwsvcK")) + self.assertEqual(s.pattern, "OP_HASH160 <20> OP_EQUAL") + self.assertEqual(s.op_sig_count, 0) + + def test_null_data(self): + print("Deserialize NULL_DATA") + # 20 bytes valid + s = blockchain.Script("6a144279b52d6ee8393a9a755e8c6f633b5dd034bd67") + self.assertEqual(s.type, "NULL_DATA") + self.assertEqual(s.ntype, 3) + self.assertEqual(s.asm, "OP_RETURN 4279b52d6ee8393a9a755e8c6f633b5dd034bd67") + self.assertEqual(len(s.address), 0) + self.assertEqual(s.pattern, "OP_RETURN <20>") + self.assertEqual(s.op_sig_count, 0) + # 81 bytes invalid + s = blockchain.Script("6a4c51000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000") + self.assertEqual(s.asm, "OP_RETURN 000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000") + self.assertEqual(s.pattern, "OP_RETURN <81>") + self.assertEqual(len(s.address), 0) + self.assertEqual(s.op_sig_count, 0) + self.assertEqual(s.type, "NON_STANDARD") + self.assertEqual(s.ntype, 7) + + def test_multisig(self): + print("Deserialize MULTISIG") + # 15 from 15 + s = blockchain.Script("5f210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "71210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c" + "715fae") + self.assertEqual(s.pattern, "OP_15 <33> <33> <33> <33> <33> <33> <33> <33> <33> <33> <33> <33> " + "<33> <33> <33> OP_15 OP_CHECKMULTISIG") + self.assertEqual(s.type, "MULTISIG") + self.assertEqual(s.ntype, 4) + self.assertEqual(s.asm, "OP_15 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab" + "35c71 OP_15 OP_CHECKMULTISIG") + self.assertEqual(len(s.address), 15) + self.assertEqual(s.op_sig_count, 15) + + # 1 from 3 + s = blockchain.Script("512102953397b893148acec2a9da8341159e9e7fb3d32987c3563e8bdf22116213623" + "441048da561da64584fb1457e906bc2840a1f963b401b632ab98761d12d74dd795bbf" + "410c7b6d6fd39acf9d870cb9726578eaf8ba430bb296eac24957f3fb3395b8b042060" + "466616fb675310aeb024f957b4387298dc28305bc7276bf1f7f662a6764bcdffb6a97" + "40de596f89ad8000f8fa6741d65ff1338f53eb39e98179dd18c6e6be8e3953ae") + self.assertEqual(s.pattern, "OP_1 <33> <65> <66> OP_3 OP_CHECKMULTISIG") + self.assertEqual(s.type, "MULTISIG") + self.assertEqual(s.ntype, 4) + self.assertEqual(s.asm, "OP_1 02953397b893148acec2a9da8341159e9e7fb3d32987c3563e8bdf22116213" + "6234 048da561da64584fb1457e906bc2840a1f963b401b632ab98761d12d74dd79" + "5bbf410c7b6d6fd39acf9d870cb9726578eaf8ba430bb296eac24957f3fb3395b8b" + "0 060466616fb675310aeb024f957b4387298dc28305bc7276bf1f7f662a6764bcd" + "ffb6a9740de596f89ad8000f8fa6741d65ff1338f53eb39e98179dd18c6e6be8e39" + " OP_3 OP_CHECKMULTISIG") + self.assertEqual(len(s.address), 3) + self.assertEqual(s.op_sig_count, 3) + + def test_segwit_p2wpkh(self): + print("Deserialize segwit P2WPKH") + s = blockchain.Script("00144160bb1870159a08724557f75c7bb665a3a132e0") + self.assertEqual(s.type, "P2WPKH") + self.assertEqual(s.ntype, 5) + self.assertEqual(s.asm, "OP_0 4160bb1870159a08724557f75c7bb665a3a132e0") + self.assertEqual(s.address[0], unhexlify("004160bb1870159a08724557f75c7bb665a3a132e0")) + self.assertEqual(s.pattern, "OP_0 <20>") + self.assertEqual(s.op_sig_count, 1) + s = blockchain.Script("00144160bb1870159a08724557f75c7bb665a3a132e0", segwit=False) + self.assertEqual(s.type, "NON_STANDARD") + self.assertEqual(s.ntype, 7) + self.assertEqual(s.asm, "OP_0 4160bb1870159a08724557f75c7bb665a3a132e0") + self.assertEqual(len(s.address), 0) + self.assertEqual(s.pattern, "OP_0 <20>") + self.assertEqual(s.op_sig_count, 0) + + + def test_segwit_p2wsh(self): + print("Deserialize segwit P2WSH") + s = blockchain.Script("0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70") + self.assertEqual(s.type, "P2WSH") + self.assertEqual(s.ntype, 6) + self.assertEqual(s.asm, "OP_0 cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70") + self.assertEqual(s.address[0], unhexlify("00cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70")) + self.assertEqual(s.pattern, "OP_0 <32>") + self.assertEqual(s.op_sig_count, 0) + s = blockchain.Script("0020cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70", segwit=False) + self.assertEqual(s.type, "NON_STANDARD") + self.assertEqual(s.ntype, 7) + self.assertEqual(s.asm, "OP_0 cdbf909e935c855d3e8d1b61aeb9c5e3c03ae8021b286839b1a72f2e48fdba70") + self.assertEqual(len(s.address), 0) + self.assertEqual(s.pattern, "OP_0 <32>") + self.assertEqual(s.op_sig_count, 0) + +# class TransactionDeserializeTests(unittest.TestCase): +# @classmethod +# def setUpClass(cls): +# print("\nTesting Transaction class deserialization:\n") +# +# def test_p2pkh(self): +# print("Deserialize P2PKH") +# tx = blockchain.Transaction.deserialize("0200000001df2d507c1c765e526f8dbe6527fc34be1c30b27fdb88c521060b82" +# "45dd9f4e2e010000006b483045022100b00c4efde901f2efb92679681bef359c" +# "7639ba79979e0c436cb10a266d0b4c5d0220730d854aa51fbe715387256956f3" +# "71d2199297f7b4ea6bf9b4b87bf8bba8417e012102530c548d402670b13ad888" +# "7ff99c294e67fc18097d236d57880c69261b42def70000000001b88800000000" +# "00001600144160bb1870159a08724557f75c7bb665a3a132e000000000") +# "0200000001df2d507c1c765e526f8dbe6527fc34be1c30b27fdb88c521060b8245dd9f4e2e010000006b483045022100b00c4efde901f2efb92679681bef359c7639ba79979e0c436cb10a266d0b4c5d0220730d854aa51fbe715387256956f371d2199297f7b4ea6bf9b4b87bf8bba8417e012102530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def70000000001b8880000000000001600144160bb1870159a08724557f75c7bb665a3a132e000000000" +# t = "0100000000010198f4f454fbb0aeae5bd167879ca65560b3b7073cd57aaee9cb5903d6f0f883860000000000ffffffff01204e0000000000001976a9143e2585f7b9e642ad9030c0812490ea5b83c511fa88ac02483045022100849b9929f619933224743d14dde08ad521540d9c2b1b85a20ec1ce5db77e4fa1022035222f713f5753119783e049e1c3ef8f819b2af4946cf150c98fe66fd1898409012102530c548d402670b13ad8887ff99c294e67fc18097d236d57880c69261b42def700000000" +# tx = blockchain.Transaction.deserialize(t) +# print(tx.json()) diff --git a/test/sighash.py b/test/sighash.py new file mode 100644 index 0000000..9c627b3 --- /dev/null +++ b/test/sighash.py @@ -0,0 +1,88 @@ +import unittest +from pybtc import * +from binascii import unhexlify +from pybtc import address2hash160 + +class SighashTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting sighash:\n") + def test_sighash_segwit(self): + """ + ["raw_transaction, script, input_index, hashType, signature_hash (result)"], + :return: + """ + print("\nNative P2WPKH") + raw_tx = "0100000002fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f0000000000eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac11000000" + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_ALL, + 1, + "1976a9141d0f172a0ecb48aee1be1f2687d2963ae33f71a188ac", + 600000000, + True)), + "c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670") + print(Script("c37af31116d1b27caf68aae9e3ac82f1477929014d5b917657d0eb49478cb670").type) + print("P2SH-P2WPKH") + raw_tx = "0100000001db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a54770100000000feffffff02b8b4eb0b000000001976a914a457b684d7f0d539a46a45bbc043f35b59d0d96388ac0008af2f000000001976a914fd270b1ee6abcaea97fea7ad0402e8bd8ad6d77c88ac92040000" + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_ALL, + 0, + "1976a91479091972186c449eb1ded22b78e40d009bdf008988ac", + 1000000000, + True)), + "64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6") + print("Native P2WSH") + raw_tx = "0100000002fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e0000000000ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac00000000" + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_SINGLE, + 1, + "23210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac", + 4900000000, + True)), + "fef7bd749cce710c5c052bd796df1af0d935e59cea63736268bcbe2d2134fc47") + + print("P2SH-P2WSH SIGHASH_ALL") + raw_tx = "010000000136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000000ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac00000000" + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_ALL, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "185c0be5263dce5b4bb50a047973c1b6272bfbd0103a89444597dc40b248ee7c") + print("P2SH-P2WSH SIGHASH_NONE") + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_NONE, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "e9733bc60ea13c95c6527066bb975a2ff29a925e80aa14c213f686cbae5d2f36") + print("P2SH-P2WSH SIGHASH_SINGLE") + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_SINGLE, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "1e1f1c303dc025bd664acb72e583e933fae4cff9148bf78c157d1e8f78530aea") + + print("P2SH-P2WSH SIGHASH_ALL + SIGHASH_ANYONECANPAY") + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_ALL + SIGHASH_ANYONECANPAY, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "2a67f03e63a6a422125878b40b82da593be8d4efaafe88ee528af6e5a9955c6e") + print("P2SH-P2WSH SIGHASH_NONE + SIGHASH_ANYONECANPAY") + + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_NONE + SIGHASH_ANYONECANPAY, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "781ba15f3779d5542ce8ecb5c18716733a5ee42a6f51488ec96154934e2c890a") + print("P2SH-P2WSH SIGHASH_SINGLE + SIGHASH_ANYONECANPAY") + + self.assertEqual((Transaction.deserialize(raw_tx).sighash_segwit(SIGHASH_SINGLE + SIGHASH_ANYONECANPAY, + 0, + "cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae", + 987654321, + True)), + "511e8e52ed574121fc1b654970395502128263f62662e076dc6baf05c2e6a99b") + + diff --git a/test/transaction_deserialize.py b/test/transaction_deserialize.py new file mode 100644 index 0000000..dc8c7d4 --- /dev/null +++ b/test/transaction_deserialize.py @@ -0,0 +1,31 @@ +import unittest +from pybtc import blockchain +from binascii import unhexlify +from pybtc import address2hash160 + + +class TransactionDeserializeTests(unittest.TestCase): + @classmethod + def setUpClass(cls): + print("\nTesting Transaction class deserialization:\n") + 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) + + 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) +