compact serialized format for unsigned and partially signed transactions.
authorThomasV <thomasv@gitorious>
Sat, 21 Jun 2014 19:06:09 +0000 (21:06 +0200)
committerThomasV <thomasv@gitorious>
Sat, 21 Jun 2014 19:06:09 +0000 (21:06 +0200)
electrum
gui/qt/main_window.py
gui/qt/transaction_dialog.py
lib/account.py
lib/commands.py
lib/transaction.py
lib/wallet.py
plugins/qrscanner.py

index 8aad5d3..15edf60 100755 (executable)
--- a/electrum
+++ b/electrum
@@ -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])]
index cd288e9..927da4f 100644 (file)
@@ -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
 
@@ -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_()
 
@@ -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"))
 
 
 
@@ -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):
index ada3b8e..70643b5 100644 (file)
@@ -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()
 
 
index 3ef6400..a99fc2e 100644 (file)
 # 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):
@@ -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):
@@ -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 )
@@ -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):
@@ -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]:
@@ -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"
index 5e6d9dc..8485ed1 100644 (file)
@@ -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):
index e67f1a7..2c31c39 100644 (file)
@@ -20,6 +20,7 @@
 # Note: The deserialization code originally comes from ABE.
 
 
+import bitcoin
 from bitcoin import *
 from util import print_error
 import time
@@ -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) ]
@@ -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)
 
 
 
@@ -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'
@@ -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
@@ -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
@@ -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
@@ -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
 
 
@@ -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',{})
index edda943..e1adcd7 100644 (file)
@@ -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
@@ -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 = {}
@@ -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)
 
@@ -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
@@ -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):
index 8879c22..66a05d9 100644 (file)
@@ -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)