#!/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. 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 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 get_address_from_input_script(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 [], {}, "(None)" # payto_pubkey match = [ opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): return None, {}, "(pubkey)" # 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') pubkey = decoded[1][1].encode('hex') if sig[-2:] == '01': sig = sig[:-2] return [pubkey], {pubkey:sig}, public_key_to_bc_address(pubkey.decode('hex')) else: print_error("cannot find address in input script", bytes.encode('hex')) return [], {}, "(None)" # p2sh transaction, 2 of n match = [ opcodes.OP_0 ] while len(match) < len(decoded): match.append(opcodes.OP_PUSHDATA4) if match_decoded(decoded, match): redeemScript = decoded[-1][1] num = len(match) - 2 signatures = map(lambda x:x[1][:-1].encode('hex'), decoded[1:-1]) dec2 = [ x for x in script_GetOp(redeemScript) ] # 2 of 2 match2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] if match_decoded(dec2, match2): pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ] return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5) # 2 of 3 match2 = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] if match_decoded(dec2, match2): pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] return pubkeys, signatures, hash_160_to_bc_address(hash_160(redeemScript), 5) print_error("cannot find address in input script", bytes.encode('hex')) return [], {}, "(None)" 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 True, public_key_to_bc_address(decoded[0][1]) # 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 False, 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 False, hash_160_to_bc_address(decoded[1][1],5) return False, "(None)" class Transaction: def __init__(self, raw): self.raw = raw self.deserialize() self.inputs = self.d['inputs'] self.outputs = self.d['outputs'] self.outputs = map(lambda x: (x['address'],x['value']), self.outputs) self.locktime = self.d['lockTime'] def __str__(self): return self.raw @classmethod def from_io(klass, inputs, outputs): raw = klass.serialize(inputs, outputs, for_sig = None) # for_sig=-1 means do not sign self = klass(raw) self.inputs = inputs self.outputs = outputs return self @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) 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 = [(to_address, total)] self = klass.from_io(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 += var_int(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, addr): addrtype, hash_160 = bc_address_to_hash_160(addr) if addrtype == 0: script = '76a9' # op_dup, op_hash_160 script += '14' # push 0x14 bytes script += hash_160.encode('hex') script += '88ac' # op_equalverify, op_checksig elif addrtype == 5: script = 'a9' # op_hash_160 script += '14' # push 0x14 bytes script += hash_160.encode('hex') script += '87' # op_equal else: raise return script @classmethod def serialize( klass, inputs, outputs, for_sig = None ): push_script = lambda x: op_push(len(x)/2) + x s = int_to_hex(1,4) # version 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 signatures = txin.get('signatures', {}) if for_sig is None and not signatures: script = '' elif for_sig is None: pubkeys = txin['pubkeys'] sig_list = '' for pubkey in pubkeys: sig = signatures.get(pubkey) if not sig: continue sig = sig + '01' sig_list += push_script(sig) if not txin.get('redeemScript'): script = sig_list script += push_script(pubkeys[0]) else: script = '00' # op_0 script += sig_list redeem_script = klass.multisig_script(pubkeys,2) assert redeem_script == txin.get('redeemScript') script += push_script(redeem_script) elif for_sig==i: if txin.get('redeemScript'): script = txin['redeemScript'] # p2sh uses the inner script else: script = txin['scriptPubKey'] # scriptsig 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: addr, amount = output s += int_to_hex( amount, 8) # amount script = klass.pay_script(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.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): txin = self.inputs[i] signatures = txin.get("signatures",{}) signatures[pubkey] = sig txin["signatures"] = signatures self.inputs[i] = txin print_error("adding signature for", pubkey) self.raw = self.serialize( self.inputs, self.outputs ) def is_complete(self): for i, txin in enumerate(self.inputs): redeem_script = txin.get('redeemScript') num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) signatures = txin.get("signatures",{}) if len(signatures) == num: continue else: return False return True def sign(self, keypairs): print_error("tx.sign(), keypairs:", keypairs) for i, txin in enumerate(self.inputs): # if the input is multisig, parse redeem script redeem_script = txin.get('redeemScript') num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) # add pubkeys txin["pubkeys"] = redeem_pubkeys # get list of already existing signatures signatures = txin.get("signatures",{}) # continue if this txin is complete if len(signatures) == num: continue 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 ) 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.inputs, self.outputs ) def deserialize(self): vds = BCDataStream() vds.write(self.raw.decode('hex')) d = {} start = vds.read_cursor d['version'] = vds.read_int32() n_vin = vds.read_compact_size() d['inputs'] = [] for i in xrange(n_vin): d['inputs'].append(self.parse_input(vds)) n_vout = vds.read_compact_size() d['outputs'] = [] for i in xrange(n_vout): d['outputs'].append(self.parse_output(vds, i)) d['lockTime'] = vds.read_uint32() self.d = d return self.d def parse_input(self, 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 if scriptSig: pubkeys, signatures, address = get_address_from_input_script(scriptSig) else: pubkeys = [] signatures = {} address = None d['address'] = address d['pubkeys'] = pubkeys d['signatures'] = signatures return d def parse_output(self, vds, i): d = {} d['value'] = vds.read_int64() scriptPubKey = vds.read_bytes(vds.read_compact_size()) is_pubkey, address = get_address_from_output_script(scriptPubKey) d['is_pubkey'] = is_pubkey d['address'] = address d['scriptPubKey'] = scriptPubKey.encode('hex') d['prevout_n'] = i return d def add_extra_addresses(self, txlist): for i in self.inputs: if i.get("address") == "(pubkey)": prev_tx = txlist.get(i.get('prevout_hash')) if prev_tx: address, value = prev_tx.outputs[i.get('prevout_n')] print_error("found pay-to-pubkey address:", address) i["address"] = address def has_address(self, addr): found = False for txin in self.inputs: if addr == txin.get('address'): found = True break for txout in self.outputs: if addr == txout[0]: found = True break 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 item in self.outputs: addr, value = item 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 get_input_info(self): keys = ['prevout_hash', 'prevout_n', 'address', 'KeyID', 'scriptPubKey', 'redeemScript', 'redeemPubkey', 'pubkeys', 'signatures', 'is_coinbase'] info = [] for i in self.inputs: item = {} for k in keys: v = i.get(k) if v is not None: item[k] = v info.append(item) return info def as_dict(self): import json out = { "hex":self.raw, "complete":self.is_complete() } if not self.is_complete(): input_info = self.get_input_info() out['input_info'] = json.dumps(input_info).replace(' ','') return out def requires_fee(self, verifier): # see https://en.bitcoin.it/wiki/Transaction_fees threshold = 57600000 size = len(self.raw)/2 if size >= 10000: return True for o in self.outputs: value = o[1] if value < 1000000: 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 def add_input_info(self, input_info): for i, txin in enumerate(self.inputs): item = input_info[i] txin['scriptPubKey'] = item['scriptPubKey'] txin['redeemScript'] = item.get('redeemScript') txin['redeemPubkey'] = item.get('redeemPubkey') txin['KeyID'] = item.get('KeyID') txin['signatures'] = item.get('signatures',{})