tcompact serialized format for unsigned and partially signed transactions. - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 0636ef8b329505cfb04ef767d5e97d8637d996f9 DIR parent 378633e6fa9a67c74462c697e20a7799106f308c HTML Author: ThomasV <thomasv@gitorious> Date: Sat, 21 Jun 2014 21:06:09 +0200 compact serialized format for unsigned and partially signed transactions. Diffstat: M electrum | 2 +- M gui/qt/main_window.py | 33 ++++++++++++++++++------------- M gui/qt/transaction_dialog.py | 8 +++----- M lib/account.py | 73 ++++++++++++++++++++++++++----- M lib/commands.py | 4 ++-- M lib/transaction.py | 191 ++++++++++++++++++------------- M lib/wallet.py | 109 +++++++++++++------------------ M plugins/qrscanner.py | 10 +++++++++- 8 files changed, 252 insertions(+), 178 deletions(-) --- DIR diff --git a/electrum b/electrum t@@ -336,7 +336,7 @@ if __name__ == '__main__': args.append(prompt_password('Enter PrivateKey (will not echo):', False)) elif cmd.name == 'signrawtransaction': - args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []] + args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [] ] elif cmd.name == 'createmultisig': args = [cmd, int(args[1]), json.loads(args[2])] DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py t@@ -1024,7 +1024,7 @@ class ElectrumWindow(QMainWindow): def sign_thread(): time.sleep(0.1) keypairs = {} - self.wallet.add_keypairs_from_wallet(tx, keypairs, password) + self.wallet.add_keypairs(tx, keypairs, password) self.wallet.sign_transaction(tx, keypairs, password) return tx, fee, label t@@ -1814,7 +1814,6 @@ class ElectrumWindow(QMainWindow): def show_qrcode(self, data, title = _("QR code")): if not data: return - print_error("qrcode:", data) d = QRDialog(data, self, title) d.exec_() t@@ -2046,24 +2045,29 @@ class ElectrumWindow(QMainWindow): "json or raw hexadecimal" try: txt.decode('hex') - tx = Transaction(txt) - return tx - except Exception: - pass + is_hex = True + except: + is_hex = False + + if is_hex: + try: + return Transaction(txt) + except: + traceback.print_exc(file=sys.stdout) + QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction")) + return try: tx_dict = json.loads(str(txt)) assert "hex" in tx_dict.keys() tx = Transaction(tx_dict["hex"]) - if tx_dict.has_key("input_info"): - input_info = json.loads(tx_dict['input_info']) - tx.add_input_info(input_info) + #if tx_dict.has_key("input_info"): + # input_info = json.loads(tx_dict['input_info']) + # tx.add_input_info(input_info) return tx except Exception: traceback.print_exc(file=sys.stdout) - pass - - QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction")) + QMessageBox.critical(None, _("Unable to parse transaction"), _("Electrum was unable to parse your transaction")) t@@ -2081,10 +2085,11 @@ class ElectrumWindow(QMainWindow): @protected - def sign_raw_transaction(self, tx, input_info, password): + def sign_raw_transaction(self, tx, password): try: - self.wallet.signrawtransaction(tx, input_info, [], password) + self.wallet.signrawtransaction(tx, [], password) except Exception as e: + traceback.print_exc(file=sys.stdout) QMessageBox.warning(self, _("Error"), str(e)) def do_process_from_text(self): DIR diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py t@@ -105,17 +105,15 @@ class TxDialog(QDialog): def show_qr(self): + text = self.tx.raw.decode('hex') try: - json_text = json.dumps(self.tx.as_dict()).replace(' ', '') - self.parent.show_qrcode(json_text, 'Transaction') + self.parent.show_qrcode(text, 'Transaction') except Exception as e: self.show_message(str(e)) def sign(self): - tx_dict = self.tx.as_dict() - input_info = json.loads(tx_dict["input_info"]) - self.parent.sign_raw_transaction(self.tx, input_info) + self.parent.sign_raw_transaction(self.tx) self.update() DIR diff --git a/lib/account.py b/lib/account.py t@@ -16,10 +16,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +import bitcoin from bitcoin import * from i18n import _ -from transaction import Transaction - +from transaction import Transaction, is_extended_pubkey +from util import print_msg class Account(object): t@@ -38,7 +39,7 @@ class Account(object): n = len(addresses) address = self.get_address( for_change, n) addresses.append(address) - print address + print_msg(address) return address def get_address(self, for_change, n): t@@ -149,26 +150,30 @@ class OldAccount(Account): seed = hashlib.sha256(seed + oldseed).digest() return string_to_number( seed ) - def get_sequence(self, for_change, n): - return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) ) + @classmethod + def get_sequence(self, mpk, for_change, n): + return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk ) ) def get_address(self, for_change, n): pubkey = self.get_pubkey(for_change, n) address = public_key_to_bc_address( pubkey.decode('hex') ) return address - def get_pubkey(self, for_change, n): + @classmethod + def get_pubkey_from_mpk(self, mpk, for_change, n): curve = SECP256k1 - mpk = self.mpk - z = self.get_sequence(for_change, n) + z = self.get_sequence(mpk, for_change, n) master_public_key = ecdsa.VerifyingKey.from_string( mpk, curve = SECP256k1 ) pubkey_point = master_public_key.pubkey.point + z*curve.generator public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 ) return '04' + public_key2.to_string().encode('hex') + def get_pubkey(self, for_change, n): + return self.get_pubkey_from_mpk(self.mpk, for_change, n) + def get_private_key_from_stretched_exponent(self, for_change, n, secexp): order = generator_secp256k1.order() - secexp = ( secexp + self.get_sequence(for_change, n) ) % order + secexp = ( secexp + self.get_sequence(self.mpk, for_change, n) ) % order pk = number_to_string( secexp, generator_secp256k1.order() ) compressed = False return SecretToASecret( pk, compressed ) t@@ -206,6 +211,25 @@ class OldAccount(Account): a, b = sequence return 'old(%s,%d,%d)'%(self.mpk.encode('hex'),a,b) + def get_xpubkeys(self, sequence): + s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), sequence)) + mpk = self.mpk.encode('hex') + x_pubkey = 'fe' + mpk + s + return [ x_pubkey ] + + @classmethod + def parse_xpubkey(self, x_pubkey): + assert is_extended_pubkey(x_pubkey) + pk = x_pubkey[2:] + mpk = pk[0:128] + dd = pk[128:] + s = [] + while dd: + n = int(bitcoin.rev_hex(dd[0:4]), 16) + dd = dd[4:] + s.append(n) + assert len(s) == 2 + return mpk, s class BIP32_Account(Account): t@@ -230,6 +254,7 @@ class BIP32_Account(Account): def get_master_pubkeys(self): return [self.xpub] + @classmethod def get_pubkey_from_x(self, xpub, for_change, n): _, _, _, c, cK = deserialize_xkey(xpub) for i in [for_change, n]: t@@ -264,9 +289,33 @@ class BIP32_Account(Account): def get_type(self): return _('Standard 1 of 1') - def get_keyID(self, sequence): - s = '/' + '/'.join( map(lambda x:str(x), sequence) ) - return '&'.join( map(lambda x: 'bip32(%s,%s)'%(x, s), self.get_master_pubkeys() ) ) + def get_xpubkeys(self, sequence): + s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), sequence)) + mpks = self.get_master_pubkeys() + out = [] + for xpub in mpks: + pubkey = self.get_pubkey_from_x(xpub, *sequence) + x_pubkey = 'ff' + bitcoin.DecodeBase58Check(xpub).encode('hex') + s + out.append( (pubkey, x_pubkey ) ) + # sort it, so that x_pubkeys are in the same order as pubkeys + out.sort() + return map(lambda x:x[1], out ) + + @classmethod + def parse_xpubkey(self, pubkey): + assert is_extended_pubkey(pubkey) + pk = pubkey.decode('hex') + pk = pk[1:] + xkey = bitcoin.EncodeBase58Check(pk[0:78]) + dd = pk[78:] + s = [] + while dd: + n = int( bitcoin.rev_hex(dd[0:2].encode('hex')), 16) + dd = dd[2:] + s.append(n) + assert len(s) == 2 + return xkey, s + def get_name(self, k): name = "Unnamed account" DIR diff --git a/lib/commands.py b/lib/commands.py t@@ -170,9 +170,9 @@ class Commands: return tx - def signrawtransaction(self, raw_tx, input_info, private_keys): + def signrawtransaction(self, raw_tx, private_keys): tx = Transaction(raw_tx) - self.wallet.signrawtransaction(tx, input_info, private_keys, self.password) + self.wallet.signrawtransaction(tx, private_keys, self.password) return tx def decoderawtransaction(self, raw): DIR diff --git a/lib/transaction.py b/lib/transaction.py t@@ -20,6 +20,7 @@ # Note: The deserialization code originally comes from ABE. +import bitcoin from bitcoin import * from util import print_error import time t@@ -295,6 +296,33 @@ def match_decoded(decoded, to_match): 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 == 'ff' + return s + +def is_extended_pubkey(x_pubkey): + return x_pubkey[0:2] in ['fe', 'ff'] + +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.get_pubkey_from_x(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) ] t@@ -314,41 +342,52 @@ def parse_scriptSig(d, bytes): 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] - d['pubkeys'] = [pubkey] - d['signatures'] = {pubkey:sig} - d['address'] = public_key_to_bc_address(pubkey.decode('hex')) - return - else: + 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 match_decoded(decoded, match): - redeemScript = decoded[-1][1] - num = len(match) - 2 - d['signatures'] = map(lambda x:x[1][:-1].encode('hex'), decoded[1:-1]) - d['address'] = hash_160_to_bc_address(hash_160(redeemScript), 5) - d['redeemScript'] = redeemScript.encode('hex') - dec2 = [ x for x in script_GetOp(redeemScript) ] - 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): - pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex') ] - elif match_decoded(dec2, match_2of3): - pubkeys = [ dec2[1][1].encode('hex'), dec2[2][1].encode('hex'), dec2[3][1].encode('hex') ] - else: - return - d['pubkeys'] = pubkeys + if not match_decoded(decoded, match): + print_error("cannot find address in input script", bytes.encode('hex')) return - print_error("cannot find address in input script", bytes.encode('hex')) + 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')), 5) t@@ -437,7 +476,7 @@ class Transaction: raise for k in public_keys: - s += var_int(len(k)/2) + s += op_push(len(k)/2) s += k if n==2: s += '52' t@@ -471,43 +510,50 @@ class Transaction: @classmethod def serialize( klass, inputs, outputs, for_sig = None ): + NO_SIGNATURE = 'ff' + 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 = '' + p2sh = txin.get('redeemScript') is not None + n_sig = 2 if p2sh else 1 + + pubkeys = txin['pubkeys'] # pubkeys should always be known + address = txin['address'] + + if for_sig is None: - 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'): + # list of signatures + signatures = txin.get('signatures',[]) + sig_list = [] + for signature in signatures: + sig_list.append(signature + '01') + if len(sig_list) > n_sig: + sig_list = sig_list[:n_sig] + while len(sig_list) < n_sig: + sig_list.append(NO_SIGNATURE) + sig_list = ''.join( map( lambda x: push_script(x), sig_list)) + + # extended pubkeys (with bip32 derivation) + x_pubkeys = txin['x_pubkeys'] + + if not p2sh: script = sig_list - script += push_script(pubkeys[0]) + script += push_script(x_pubkeys[0]) else: script = '00' # op_0 script += sig_list - redeem_script = klass.multisig_script(pubkeys,2) - assert redeem_script == txin.get('redeemScript') + redeem_script = klass.multisig_script(x_pubkeys,2) 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 + script = txin['redeemScript'] if p2sh else klass.pay_script(address) else: script = '' s += var_int( len(script)/2 ) # script length t@@ -536,20 +582,26 @@ class Transaction: def add_signature(self, i, pubkey, sig): txin = self.inputs[i] - signatures = txin.get("signatures",{}) - signatures[pubkey] = sig + signatures = txin.get("signatures",[]) + if sig not in signatures: + signatures.append(sig) txin["signatures"] = signatures self.inputs[i] = txin print_error("adding signature for", pubkey) + # replace x_pubkey + i = txin['pubkeys'].index(pubkey) + txin['x_pubkeys'][i] = 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')]) + #redeem_script = txin.get('redeemScript') + #num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) + pubkeys = txin['pubkeys'] signatures = txin.get("signatures",{}) - if len(signatures) == num: + if len(signatures) == txin['num_sig']: continue else: return False t@@ -563,11 +615,14 @@ class Transaction: 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')]) + #redeem_script = txin.get('redeemScript') + #num, redeem_pubkeys = parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')]) + redeem_pubkeys = txin['pubkeys'] + num = len(redeem_pubkeys) # add pubkeys - txin["pubkeys"] = redeem_pubkeys + ### txin["pubkeys"] = redeem_pubkeys + # get list of already existing signatures signatures = txin.get("signatures",{}) # continue if this txin is complete t@@ -724,30 +779,12 @@ class Transaction: 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 t@@ -772,11 +809,3 @@ class Transaction: - 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',{}) DIR diff --git a/lib/wallet.py b/lib/wallet.py t@@ -34,7 +34,7 @@ import math from util import print_msg, print_error, format_satoshis from bitcoin import * from account import * -from transaction import Transaction +from transaction import Transaction, is_extended_pubkey from plugins import run_hook import bitcoin from synchronizer import WalletSynchronizer t@@ -392,73 +392,56 @@ class Abstract_Wallet: return self.accounts[account_id].get_pubkeys(sequence) - def add_keypairs_from_wallet(self, tx, keypairs, password): + def add_keypairs(self, tx, keypairs, password): + # first check the provided password + seed = self.get_seed(password) + for txin in tx.inputs: + x_pubkeys = txin['x_pubkeys'] address = txin['address'] - if not self.is_mine(address): - continue - private_keys = self.get_private_key(address, password) - for sec in private_keys: - pubkey = public_key_from_private_key(sec) - keypairs[ pubkey ] = sec + if self.is_mine(address): + private_keys = self.get_private_key(address, password) + for sec in private_keys: + pubkey = public_key_from_private_key(sec) + keypairs[ pubkey ] = sec - def add_keypairs_from_KeyID(self, tx, keypairs, password): - # first check the provided password - seed = self.get_seed(password) + else: - for txin in tx.inputs: - keyid = txin.get('KeyID') - if keyid: - roots = [] - for s in keyid.split('&'): - m = re.match("bip32\((.*),(/\d+/\d+)\)", s) - if not m: continue - xpub = m.group(1) - sequence = m.group(2) - root = self.find_root_by_master_key(xpub) - if not root: continue - sequence = map(lambda x:int(x), sequence.strip('/').split('/')) - root = root + '%d'%sequence[0] - sequence = sequence[1:] - roots.append((root,sequence)) - - account_id = " & ".join( map(lambda x:x[0], roots) ) - account = self.accounts.get(account_id) - if not account: continue - addr = account.get_address(*sequence) - txin['address'] = addr # fixme: side effect - pk = self.get_private_key(addr, password) - for sec in pk: - pubkey = public_key_from_private_key(sec) - keypairs[pubkey] = sec + from account import BIP32_Account + print "scanning", x_pubkeys + for x_pubkey in x_pubkeys: + if not is_extended_pubkey(x_pubkey): + continue + xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey) + print "xpub", xpub - def signrawtransaction(self, tx, input_info, private_keys, password): + # look for account that can sign + for k, account in self.accounts.items(): + if xpub in account.get_master_pubkeys(): + break + else: + continue + print "found xpub", xpub, sequence + + addr = account.get_address(*sequence) + print addr, txin['address'] + assert txin['address'] == addr + pk = self.get_private_key(addr, password) + for sec in pk: + pubkey = public_key_from_private_key(sec) + keypairs[pubkey] = sec - # check that the password is correct - seed = self.get_seed(password) - # if input_info is not known, build it using wallet UTXOs - if not input_info: - input_info = [] - unspent_coins = self.get_unspent_coins() - for txin in tx.inputs: - for item in unspent_coins: - if txin['prevout_hash'] == item['prevout_hash'] and txin['prevout_n'] == item['prevout_n']: - info = { 'address':item['address'], 'scriptPubKey':item['scriptPubKey'] } - self.add_input_info(info) - input_info.append(info) - break - else: - print_error( "input not in UTXOs" ) - input_info.append(None) - # add input_info to the transaction - print_error("input_info", input_info) - tx.add_input_info(input_info) + + def signrawtransaction(self, tx, private_keys, password): + + # check that the password is correct + seed = self.get_seed(password) # build a list of public/private keys keypairs = {} t@@ -468,10 +451,9 @@ class Abstract_Wallet: pubkey = public_key_from_private_key(sec) keypairs[ pubkey ] = sec - # add private_keys from KeyID - self.add_keypairs_from_KeyID(tx, keypairs, password) - # add private keys from wallet - self.add_keypairs_from_wallet(tx, keypairs, password) + # add private_keys + self.add_keypairs(tx, keypairs, password) + # sign the transaction self.sign_transaction(tx, keypairs, password) t@@ -869,7 +851,7 @@ class Abstract_Wallet: def mktx(self, outputs, password, fee=None, change_addr=None, domain= None, coins = None ): tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain, coins) keypairs = {} - self.add_keypairs_from_wallet(tx, keypairs, password) + self.add_keypairs(tx, keypairs, password) if keypairs: self.sign_transaction(tx, keypairs, password) return tx t@@ -879,12 +861,15 @@ class Abstract_Wallet: address = txin['address'] account_id, sequence = self.get_address_index(address) account = self.accounts[account_id] - txin['KeyID'] = account.get_keyID(sequence) redeemScript = account.redeem_script(sequence) + txin['x_pubkeys'] = account.get_xpubkeys(sequence) + txin['pubkeys'] = account.get_pubkeys(sequence) if redeemScript: txin['redeemScript'] = redeemScript + txin['num_sig'] = 2 else: txin['redeemPubkey'] = account.get_pubkey(*sequence) + txin['num_sig'] = 1 def sign_transaction(self, tx, keypairs, password): DIR diff --git a/plugins/qrscanner.py b/plugins/qrscanner.py t@@ -82,7 +82,15 @@ class Plugin(BasePlugin): qrcode = self.scan_qr() if not qrcode: return - tx = self.win.tx_from_text(qrcode) + data = qrcode + + # transactions are binary, but qrcode seems to return utf8... + z = data.decode('utf8') + s = '' + for b in z: + s += chr(ord(b)) + data = s.encode('hex') + tx = self.win.tx_from_text(data) if not tx: return self.win.show_transaction(tx)