#!/usr/bin/env python # # Electrum - lightweight Bitcoin client # Copyright (C) 2011 thomasv@gitorious # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Note: The deserialization code originally comes from ABE. import bitcoin from bitcoin import * from util import print_error import time import struct # # Workalike python implementation of Bitcoin's CDataStream class. # import struct import StringIO import mmap NO_SIGNATURE = 'ff' class SerializationError(Exception): """ Thrown when there's a problem deserializing or serializing """ class BCDataStream(object): def __init__(self): self.input = None self.read_cursor = 0 def clear(self): self.input = None self.read_cursor = 0 def write(self, bytes): # Initialize with string of bytes if self.input is None: self.input = bytes else: self.input += bytes def map_file(self, file, start): # Initialize with bytes from file self.input = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) self.read_cursor = start def seek_file(self, position): self.read_cursor = position def close_file(self): self.input.close() def read_string(self): # Strings are encoded depending on length: # 0 to 252 : 1-byte-length followed by bytes (if any) # 253 to 65,535 : byte'253' 2-byte-length followed by bytes # 65,536 to 4,294,967,295 : byte '254' 4-byte-length followed by bytes # ... and the Bitcoin client is coded to understand: # greater than 4,294,967,295 : byte '255' 8-byte-length followed by bytes of string # ... but I don't think it actually handles any strings that big. if self.input is None: raise SerializationError("call write(bytes) before trying to deserialize") try: length = self.read_compact_size() except IndexError: raise SerializationError("attempt to read past end of buffer") return self.read_bytes(length) def write_string(self, string): # Length-encoded as with read-string self.write_compact_size(len(string)) self.write(string) def read_bytes(self, length): try: result = self.input[self.read_cursor:self.read_cursor+length] self.read_cursor += length return result except IndexError: raise SerializationError("attempt to read past end of buffer") return '' def read_boolean(self): return self.read_bytes(1)[0] != chr(0) def read_int16(self): return self._read_num('= opcodes.OP_SINGLEBYTE_END: opcode <<= 8 opcode |= ord(bytes[i]) i += 1 if opcode <= opcodes.OP_PUSHDATA4: nSize = opcode if opcode == opcodes.OP_PUSHDATA1: nSize = ord(bytes[i]) i += 1 elif opcode == opcodes.OP_PUSHDATA2: (nSize,) = struct.unpack_from(' 0: result += " " if opcode <= opcodes.OP_PUSHDATA4: result += "%d:"%(opcode,) result += short_hex(vch) else: result += script_GetOpName(opcode) return result def match_decoded(decoded, to_match): if len(decoded) != len(to_match): return False; for i in range(len(decoded)): if to_match[i] == opcodes.OP_PUSHDATA4 and decoded[i][0] <= opcodes.OP_PUSHDATA4 and decoded[i][0]>0: continue # Opcodes below OP_PUSHDATA4 all just push data onto stack, and are equivalent. if to_match[i] != decoded[i][0]: return False return True def parse_sig(x_sig): s = [] for sig in x_sig: if sig[-2:] == '01': s.append(sig[:-2]) else: assert sig == NO_SIGNATURE s.append(None) return s def is_extended_pubkey(x_pubkey): return x_pubkey[0:2] in ['fe', 'ff'] def x_to_xpub(x_pubkey): if x_pubkey[0:2] == 'ff': from account import BIP32_Account xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) return xpub def parse_xpub(x_pubkey): if x_pubkey[0:2] == 'ff': from account import BIP32_Account xpub, s = BIP32_Account.parse_xpubkey(x_pubkey) pubkey = BIP32_Account.derive_pubkey_from_xpub(xpub, s[0], s[1]) elif x_pubkey[0:2] == 'fe': from account import OldAccount mpk, s = OldAccount.parse_xpubkey(x_pubkey) pubkey = OldAccount.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1]) else: pubkey = x_pubkey return pubkey def parse_scriptSig(d, bytes): try: decoded = [ x for x in script_GetOp(bytes) ] except Exception: # coinbase transactions raise an exception print_error("cannot find address in input script", bytes.encode('hex')) return # payto_pubkey match = [ opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): sig = decoded[0][1].encode('hex') d['address'] = "(pubkey)" d['signatures'] = [sig] d['num_sig'] = 1 d['x_pubkeys'] = ["(pubkey)"] d['pubkeys'] = ["(pubkey)"] return # non-generated TxIn transactions push a signature # (seventy-something bytes) and then their public key # (65 bytes) onto the stack: match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): sig = decoded[0][1].encode('hex') x_pubkey = decoded[1][1].encode('hex') try: signatures = parse_sig([sig]) pubkey = parse_xpub(x_pubkey) except: import traceback traceback.print_exc(file=sys.stdout) print_error("cannot find address in input script", bytes.encode('hex')) return d['signatures'] = signatures d['x_pubkeys'] = [x_pubkey] d['num_sig'] = 1 d['pubkeys'] = [pubkey] d['address'] = public_key_to_bc_address(pubkey.decode('hex')) return # p2sh transaction, 2 of n match = [ opcodes.OP_0 ] while len(match) < len(decoded): match.append(opcodes.OP_PUSHDATA4) if not match_decoded(decoded, match): print_error("cannot find address in input script", bytes.encode('hex')) return x_sig = map(lambda x:x[1].encode('hex'), decoded[1:-1]) d['signatures'] = parse_sig(x_sig) d['num_sig'] = 2 dec2 = [ x for x in script_GetOp(decoded[-1][1]) ] match_2of2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] match_2of3 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] if match_decoded(dec2, match_2of2): x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ] elif match_decoded(dec2, match_2of3): x_pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] else: print_error("cannot find address in input script", bytes.encode('hex')) return d['x_pubkeys'] = x_pubkeys pubkeys = map(parse_xpub, x_pubkeys) d['pubkeys'] = pubkeys redeemScript = Transaction.multisig_script(pubkeys,2) d['redeemScript'] = redeemScript d['address'] = hash_160_to_bc_address(hash_160(redeemScript.decode('hex')), 20) def get_address_from_output_script(bytes): decoded = [ x for x in script_GetOp(bytes) ] # The Genesis Block, self-payments, and pay-by-IP-address payments look like: # 65 BYTES:... CHECKSIG match = [ opcodes.OP_PUSHDATA4, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): return 'pubkey', decoded[0][1].encode('hex') # Pay-by-Bitcoin-address TxOuts look like: # DUP HASH160 20 BYTES:... EQUALVERIFY CHECKSIG match = [ opcodes.OP_DUP, opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUALVERIFY, opcodes.OP_CHECKSIG ] if match_decoded(decoded, match): return 'address', hash_160_to_bc_address(decoded[2][1]) # p2sh match = [ opcodes.OP_HASH160, opcodes.OP_PUSHDATA4, opcodes.OP_EQUAL ] if match_decoded(decoded, match): return 'address', hash_160_to_bc_address(decoded[1][1],20) return "(None)", "(None)" def parse_input(vds): d = {} prevout_hash = hash_encode(vds.read_bytes(32)) prevout_n = vds.read_uint32() scriptSig = vds.read_bytes(vds.read_compact_size()) sequence = vds.read_uint32() if prevout_hash == '00'*32: d['is_coinbase'] = True else: d['is_coinbase'] = False d['prevout_hash'] = prevout_hash d['prevout_n'] = prevout_n d['sequence'] = sequence d['pubkeys'] = [] d['signatures'] = {} d['address'] = None if scriptSig: parse_scriptSig(d, scriptSig) return d def parse_output(vds, i): d = {} d['value'] = vds.read_int64() scriptPubKey = vds.read_bytes(vds.read_compact_size()) type, address = get_address_from_output_script(scriptPubKey) d['type'] = type d['address'] = address d['scriptPubKey'] = scriptPubKey.encode('hex') d['prevout_n'] = i return d def deserialize(raw): vds = BCDataStream() vds.write(raw.decode('hex')) d = {} start = vds.read_cursor d['version'] = vds.read_int32() d['timestamp'] = vds.read_int32() n_vin = vds.read_compact_size() d['inputs'] = [] for i in xrange(n_vin): d['inputs'].append(parse_input(vds)) n_vout = vds.read_compact_size() d['outputs'] = [] for i in xrange(n_vout): d['outputs'].append(parse_output(vds, i)) d['lockTime'] = vds.read_uint32() return d push_script = lambda x: op_push(len(x)/2) + x class Transaction: def __str__(self): if self.raw is None: self.raw = self.serialize(self.timestamp, self.inputs, self.outputs, for_sig = None) # for_sig=-1 means do not sign return self.raw def __init__(self, timestamp, inputs, outputs, locktime=0): self.timestamp = timestamp self.inputs = inputs self.outputs = outputs self.locktime = locktime self.raw = None @classmethod def deserialize(klass, raw): self = klass(0, [],[]) self.update(raw) return self def update(self, raw): d = deserialize(raw) self.raw = raw self.timestamp = d['timestamp'] self.inputs = d['inputs'] self.outputs = map(lambda x: (x['type'], x['address'], x['value']), d['outputs']) self.locktime = d['lockTime'] @classmethod def sweep(klass, privkeys, network, to_address, fee): inputs = [] for privkey in privkeys: pubkey = public_key_from_private_key(privkey) address = address_from_private_key(privkey) u = network.synchronous_get([ ('blockchain.address.listunspent',[address])])[0] pay_script = klass.pay_script('address', address) for item in u: item['scriptPubKey'] = pay_script item['redeemPubkey'] = pubkey item['address'] = address item['prevout_hash'] = item['tx_hash'] item['prevout_n'] = item['tx_pos'] inputs += u if not inputs: return total = sum( map(lambda x:int(x.get('value')), inputs) ) - fee outputs = [('address', to_address, total)] self = klass(inputs, outputs) self.sign({ pubkey:privkey }) return self @classmethod def multisig_script(klass, public_keys, num=None): n = len(public_keys) if num is None: num = n # supports only "2 of 2", and "2 of 3" transactions assert num <= n and n in [2,3] if num==2: s = '52' elif num == 3: s = '53' else: raise for k in public_keys: s += op_push(len(k)/2) s += k if n==2: s += '52' elif n==3: s += '53' else: raise s += 'ae' return s @classmethod def pay_script(self, type, payto): assert type == 'address' or type == 'pubkey' or type == 'op_return' if type == 'op_return': h = payto.encode('hex') return '6a' + push_script(h) elif type == 'pubkey': script = push_script(payto) script += 'ac' # op_checksig return script else: addrtype, hash_160 = bc_address_to_hash_160(payto) if addrtype == 8: script = '76a9' # op_dup, op_hash_160 script += push_script(hash_160.encode('hex')) script += '88ac' # op_equalverify, op_checksig elif addrtype == 20: script = 'a9' # op_hash_160 script += push_script(hash_160.encode('hex')) script += '87' # op_equal else: raise return script @classmethod def serialize(klass, timestamp, inputs, outputs, for_sig = None ): s = int_to_hex(1,4) # version s += int_to_hex(timestamp, 4) # timestamp s += var_int( len(inputs) ) # number of inputs for i in range(len(inputs)): txin = inputs[i] s += txin['prevout_hash'].decode('hex')[::-1].encode('hex') # prev hash s += int_to_hex(txin['prevout_n'],4) # prev index p2sh = txin.get('redeemScript') is not None num_sig = txin['num_sig'] payto = txin['address'] if txin["type"] == 'address' else txin['pubkeys'][0] x_signatures = txin['signatures'] signatures = filter(lambda x: x is not None, x_signatures) is_complete = len(signatures) == num_sig if for_sig is None: # if we have enough signatures, we use the actual pubkeys # use extended pubkeys (with bip32 derivation) sig_list = [] if is_complete: pubkeys = txin['pubkeys'] for signature in signatures: sig_list.append(signature + '01') else: pubkeys = txin['x_pubkeys'] for signature in x_signatures: sig_list.append((signature + '01') if signature is not None else NO_SIGNATURE) sig_list = ''.join( map( lambda x: push_script(x), sig_list)) if not p2sh: script = sig_list if txin["type"] != 'pubkey': script += push_script(pubkeys[0]) else: script = '00' # op_0 script += sig_list redeem_script = klass.multisig_script(pubkeys,2) script += push_script(redeem_script) elif for_sig==i: script = txin['redeemScript'] if p2sh else klass.pay_script(txin["type"], payto) print script else: script = '' s += var_int( len(script)/2 ) # script length s += script s += "ffffffff" # sequence s += var_int( len(outputs) ) # number of outputs for output in outputs: type, addr, amount = output s += int_to_hex( amount, 8) # amount script = klass.pay_script(type, addr) s += var_int( len(script)/2 ) # script length s += script # script s += int_to_hex(0,4) # lock time if for_sig is not None and for_sig != -1: s += int_to_hex(1, 4) # hash type return s def tx_for_sig(self,i): return self.serialize(self.timestamp, self.inputs, self.outputs, for_sig = i) def hash(self): return Hash(self.raw.decode('hex') )[::-1].encode('hex') def add_signature(self, i, pubkey, sig): print_error("adding signature for", pubkey) txin = self.inputs[i] pubkeys = txin['pubkeys'] ii = pubkeys.index(pubkey) txin['signatures'][ii] = sig txin['x_pubkeys'][ii] = pubkey self.inputs[i] = txin self.raw = self.serialize(self.timestamp, self.inputs, self.outputs) def signature_count(self): r = 0 s = 0 for txin in self.inputs: signatures = filter(lambda x: x is not None, txin['signatures']) s += len(signatures) r += txin['num_sig'] return s, r def is_complete(self): s, r = self.signature_count() return r == s def inputs_to_sign(self): from account import BIP32_Account, OldAccount xpub_list = [] addr_list = set() for txin in self.inputs: x_signatures = txin['signatures'] signatures = filter(lambda x: x is not None, x_signatures) if len(signatures) == txin['num_sig']: # input is complete continue for k, x_pubkey in enumerate(txin['x_pubkeys']): if x_signatures[k] is not None: # this pubkey already signed continue if x_pubkey[0:2] == 'ff': xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) xpub_list.append((xpub,sequence)) elif x_pubkey[0:2] == 'fe': xpub, sequence = OldAccount.parse_xpubkey(x_pubkey) xpub_list.append((xpub,sequence)) else: addr_list.add(txin['address']) return addr_list, xpub_list def sign(self, keypairs): print_error("tx.sign(), keypairs:", keypairs) for i, txin in enumerate(self.inputs): # continue if this txin is complete signatures = filter(lambda x: x is not None, txin['signatures']) num = txin['num_sig'] if len(signatures) == num: continue redeem_pubkeys = txin['pubkeys'] for_sig = Hash(self.tx_for_sig(i).decode('hex')) for pubkey in redeem_pubkeys: if pubkey in keypairs.keys(): # add signature sec = keypairs[pubkey] pkey = regenerate_key(sec) secexp = pkey.secret private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) public_key = private_key.get_verifying_key() sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der_canonize ) assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der) self.add_signature(i, pubkey, sig.encode('hex')) print_error("is_complete", self.is_complete()) self.raw = self.serialize( self.timestamp, self.inputs, self.outputs ) def add_pubkey_addresses(self, txlist): for i in self.inputs: i["pubkey"] = False if i.get("address") == "(pubkey)": prev_tx = txlist.get(i.get('prevout_hash')) if prev_tx: type, address, value = prev_tx.get_outputs()[i.get('prevout_n')] print_error("found pay-to-pubkey address:", address) i["address"] = address i["type"] = type def get_outputs(self): """convert pubkeys to addresses""" o = [] for type, x, v in self.outputs: if type == 'address': addr = x elif type == 'pubkey': addr = public_key_to_bc_address(x.decode('hex')) else: addr = "(None)" o.append((type,addr,v)) # print o return o def get_output_addresses(self): return map(lambda x:x[1], self.get_outputs()) def has_address(self, addr): found = False for txin in self.inputs: if addr == txin.get('address'): found = True break if addr in self.get_output_addresses(): found = True return found def get_value(self, addresses, prevout_values): # return the balance for that tx is_relevant = False is_send = False is_pruned = False is_partial = False v_in = v_out = v_out_mine = 0 for item in self.inputs: addr = item.get('address') if addr in addresses: is_send = True is_relevant = True key = item['prevout_hash'] + ':%d'%item['prevout_n'] value = prevout_values.get( key ) if value is None: is_pruned = True else: v_in += value else: is_partial = True if not is_send: is_partial = False for type, addr, value in self.get_outputs(): v_out += value if addr in addresses: v_out_mine += value is_relevant = True if is_pruned: # some inputs are mine: fee = None if is_send: v = v_out_mine - v_out else: # no input is mine v = v_out_mine else: v = v_out_mine - v_in if is_partial: # some inputs are mine, but not all fee = None is_send = v < 0 else: # all inputs are mine fee = v_out - v_in return is_relevant, is_send, v, fee def as_dict(self): import json out = { "hex":self.raw, "complete":self.is_complete() } return out def requires_fee(self, verifier): threshold = 57600000 size = len(self.raw)/2 if size >= 1000: return True for o in self.get_outputs(): value = o[2] if value < 10000: return True sum = 0 for i in self.inputs: age = verifier.get_confirmations(i["prevout_hash"])[0] sum += i["value"] * age priority = sum / size print_error(priority, threshold) return priority < threshold