tsignrawtransaction: parse redeemScript - electrum - Electrum Bitcoin wallet (HTM) git clone https://git.parazyd.org/electrum (DIR) Log (DIR) Files (DIR) Refs (DIR) Submodules --- (DIR) commit 40b397dc0f0df7ee6f0dbaf7371ea743e0b85e85 (DIR) parent c400583443aa49bc88cafec240dbe51a8b26a8a2 (HTM) Author: thomasv <thomasv@gitorious> Date: Fri, 22 Feb 2013 13:40:42 +0100 signrawtransaction: parse redeemScript Diffstat: M electrum | 26 +++++++++++++++++++++----- M lib/bitcoin.py | 129 +++++++++++++++++++++----------- M lib/deserialize.py | 49 ++++++++++++++++++++++++------- M lib/wallet.py | 4 ++-- 4 files changed, 148 insertions(+), 60 deletions(-) --- (DIR) diff --git a/electrum b/electrum t@@ -729,22 +729,38 @@ if __name__ == '__main__': elif cmd == 'signrawtransaction': + from lib.bitcoin import * tx = Transaction(args[1]) txouts = ast.literal_eval(args[2]) if len(args)>2 else [] private_keys = ast.literal_eval(args[3]) if len(args)>3 else {} - # lookup addresses for txin in tx.inputs: txid = txin["prevout_hash"] index = txin["prevout_n"] - utx = wallet.transactions.get(txid) - txout = utx['outputs'][index] - txin['address'] = txout['address'] - txin['raw_output_script'] = txout['raw_output_script'] # convert to own format txin['tx_hash'] = txin['prevout_hash'] txin['index'] = txin['prevout_n'] + for txout in txouts: + if txout.get('txid') == txid and txout.get('vout') == index: + # compute addr from redeemScript + addr = hash_160_to_bc_address(hash_160(txout['redeemScript'].decode('hex')),5) + txin['address'] = addr + txin['raw_output_script'] = txout['scriptPubKey'] + txin['redeemScript'] = txout['redeemScript'] + break + + else: + if wallet.transactions.get(txid): + # lookup in my own list of transactions + txout = wallet.transactions[txid]['outputs'][index] + txin['address'] = txout['address'] + txin['raw_output_script'] = txout['raw_output_script'] + + else: + # if neither, we might want to get it from the server.. + raise + if not private_keys: for txin in tx.inputs: addr = txin['address'] (DIR) diff --git a/lib/bitcoin.py b/lib/bitcoin.py t@@ -39,6 +39,17 @@ def var_int(i): else: return "ff"+int_to_hex(i,8) +def op_push(i): + if i<0x4c: + return int_to_hex(i) + elif i<0xff: + return '4c' + int_to_hex(i) + elif i<0xffff: + return '4d' + int_to_hex(i,2) + else: + return '4e' + int_to_hex(i,4) + + Hash = lambda x: hashlib.sha256(hashlib.sha256(x).digest()).digest() hash_encode = lambda x: x[::-1].encode('hex') t@@ -326,11 +337,6 @@ def CKD_prime(K, c, n): ################################## transactions -def tx_filter(s): - out = re.sub('( [^\n]*|)\n','',s) - out = out.replace(' ','') - out = out.replace('\n','') - return out def raw_tx( inputs, outputs, for_sig = None ): t@@ -338,41 +344,39 @@ def raw_tx( inputs, outputs, for_sig = None ): s += var_int( len(inputs) ) # number of inputs for i in range(len(inputs)): txin = inputs[i] - s += txin['tx_hash'].decode('hex')[::-1].encode('hex') # prev hash - s += int_to_hex(txin['index'],4) # prev index + s += txin['tx_hash'].decode('hex')[::-1].encode('hex') # prev hash + s += int_to_hex(txin['index'],4) # prev index if for_sig is None: - pubkeysig = txin['pubkeysig'] - if len(pubkeysig) == 1: + pubkeysig = txin.get('pubkeysig') + if pubkeysig: pubkey, sig = pubkeysig[0] sig = sig + chr(1) # hashtype - script = int_to_hex( len(sig)) + script = op_push( len(sig)) script += sig.encode('hex') - script += int_to_hex( len(pubkey)) + script += op_push( len(pubkey)) script += pubkey.encode('hex') else: - n = txin['multisig_num'] - pubkeys = map(lambda x:x[0], pubkeysig) + signatures = txin['signatures'] + pubkeys = txin['pubkeys'] script = '00' # op_0 - for item in pubkeysig: - pubkey, sig = item - sig = sig + chr(1) - script += int_to_hex(len(sig)) - script += sig.encode('hex') - inner_script = multisig_script(pubkeys) - script += var_int(len(inner_script)/2) - script += inner_script + for sig in signatures: + sig = sig + '01' + script += op_push(len(sig)/2) + script += sig + + redeem_script = multisig_script(pubkeys,2) + script += op_push(len(redeem_script)/2) + script += redeem_script elif for_sig==i: - pubkeys = txin.get('pubkeys') - if pubkeys: - num = txin['p2sh_num'] - script = multisig_script(pubkeys, num) # p2sh uses the inner script + if txin.get('redeemScript'): + script = txin['redeemScript'] # p2sh uses the inner script else: script = txin['raw_output_script'] # scriptsig else: script='' - s += var_int( len(tx_filter(script))/2 ) # script length + s += var_int( len(script)/2 ) # script length s += script s += "ffffffff" # sequence t@@ -394,11 +398,12 @@ def raw_tx( inputs, outputs, for_sig = None ): else: raise - s += var_int( len(tx_filter(script))/2 ) # script length + 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 tx_filter(s) + if for_sig is not None and for_sig != -1: + s += int_to_hex(1, 4) # hash type + return s t@@ -460,22 +465,60 @@ class Transaction: return Hash(self.raw.decode('hex') )[::-1].encode('hex') def sign(self, private_keys): + import deserialize for i in range(len(self.inputs)): txin = self.inputs[i] - sec = private_keys[txin['address']] - compressed = is_compressed(sec) - pkey = regenerate_key(sec) - secexp = pkey.secret - - private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) - public_key = private_key.get_verifying_key() - pkey = EC_KEY(secexp) - pubkey = GetPubKey(pkey.pubkey, compressed) - tx = raw_tx( self.inputs, self.outputs, for_sig = i ) - sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) - assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) - self.inputs[i]["pubkeysig"] = [(pubkey, sig)] + + if txin.get('redeemScript'): + # 1 parse the redeem script + num, redeem_pubkeys = deserialize.parse_redeemScript(txin.get('redeemScript')) + self.inputs[i]["pubkeys"] = redeem_pubkeys + + # build list of public/private keys + keypairs = {} + for sec in private_keys.values(): + compressed = is_compressed(sec) + pkey = regenerate_key(sec) + pubkey = GetPubKey(pkey.pubkey, compressed) + keypairs[ pubkey.encode('hex') ] = sec + + # list of signatures + signatures = txin.get("signatures",[]) + + # check if we have a key corresponding to the redeem script + for pubkey, privkey in keypairs.items(): + if pubkey in redeem_pubkeys: + # add signature + compressed = is_compressed(sec) + pkey = regenerate_key(sec) + secexp = pkey.secret + private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + public_key = private_key.get_verifying_key() + + tx = raw_tx( self.inputs, self.outputs, for_sig = i ) + sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) + assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) + signatures.append( sig.encode('hex') ) + + # for p2sh, pubkeysig is a tuple (may be incomplete) + self.inputs[i]["signatures"] = signatures + + else: + sec = private_keys[txin['address']] + compressed = is_compressed(sec) + pkey = regenerate_key(sec) + secexp = pkey.secret + + private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 ) + public_key = private_key.get_verifying_key() + pkey = EC_KEY(secexp) + pubkey = GetPubKey(pkey.pubkey, compressed) + tx = raw_tx( self.inputs, self.outputs, for_sig = i ) + sig = private_key.sign_digest( Hash( tx.decode('hex') ), sigencode = ecdsa.util.sigencode_der ) + assert public_key.verify_digest( sig, Hash( tx.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der) + + self.inputs[i]["pubkeysig"] = [(pubkey, sig)] self.raw = raw_tx( self.inputs, self.outputs ) t@@ -542,7 +585,7 @@ def test_p2sh(): tx = Transaction.from_io( [{'tx_hash':'3c9018e8d5615c306d72397f8f5eef44308c98fb576a88e030c25456b4f3a7ac', 'index':0, - 'raw_output_script':'a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087', 'pubkeys':pubkeys, 'p2sh_num':2}], + 'raw_output_script':'a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087', 'redeemScript':multisig_script(pubkeys, 2)}], [('1GtpSrGhRGY5kkrNz4RykoqRQoJuG2L6DS',1000000)]) tx_for_sig = tx.for_sig(0) (DIR) diff --git a/lib/deserialize.py b/lib/deserialize.py t@@ -186,7 +186,17 @@ def parse_TxIn(vds): d['prevout_n'] = vds.read_uint32() scriptSig = vds.read_bytes(vds.read_compact_size()) d['sequence'] = vds.read_uint32() - d['address'] = get_address_from_input_script(scriptSig) + + if scriptSig: + pubkeys, signatures, address = get_address_from_input_script(scriptSig) + else: + pubkeys = [] + signatures = [] + address = None + + d['address'] = address + d['signatures'] = signatures + return d t@@ -215,6 +225,20 @@ def parse_Transaction(vds): d['lockTime'] = vds.read_uint32() return d +def parse_redeemScript(bytes): + dec = [ x for x in script_GetOp(bytes.decode('hex')) ] + + # 2 of 2 + match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_2, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec, match): + pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex') ] + return 2, pubkeys + + # 2 of 3 + match = [ opcodes.OP_2, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_3, opcodes.OP_CHECKMULTISIG ] + if match_decoded(dec, match): + pubkeys = [ dec[1][1].encode('hex'), dec[2][1].encode('hex'), dec[3][1].encode('hex') ] + return 3, pubkeys t@@ -299,33 +323,38 @@ def get_address_from_input_script(bytes): # (65 bytes) onto the stack: match = [ opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] if match_decoded(decoded, match): - return public_key_to_bc_address(decoded[1][1]) + return None, None, public_key_to_bc_address(decoded[1][1]) # p2sh transaction, 2 of n - match = [ opcodes.OP_0, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4, opcodes.OP_PUSHDATA4 ] + match = [ opcodes.OP_0 ] + while len(match) < len(decoded): + match.append(opcodes.OP_PUSHDATA4) + if match_decoded(decoded, match): - bytes = decoded[3][1] - dec2 = [ x for x in script_GetOp(bytes) ] + + redeemScript = decoded[-1][1] + num = len(match) - 2 + signatures = map(lambda x:x[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') ] s = multisig_script(pubkeys) - return hash_160_to_bc_address(hash_160(s.decode('hex')), 5) + return pubkeys, signatures, hash_160_to_bc_address(hash_160(s.decode('hex')), 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') ] s = multisig_script(pubkeys) - return hash_160_to_bc_address(hash_160(s.decode('hex')), 5) + return pubkeys, signatures, hash_160_to_bc_address(hash_160(s.decode('hex')), 5) - return "p2sh, unknown" + raise BaseException("no match for scriptsig") - return "(None)" - def get_address_from_output_script(bytes): decoded = [ x for x in script_GetOp(bytes) ] (DIR) diff --git a/lib/wallet.py b/lib/wallet.py t@@ -821,8 +821,8 @@ class Wallet: private_keys = {} for txin in tx.inputs: addr = txin['address'] - secexp, compressed = self.get_private_key(addr, password) - private_keys[addr] = (secexp,compressed) + sec = self.get_private_key_base58(addr, password) + private_keys[addr] = sec tx.sign(private_keys) return str(tx)