URI: 
       tExtend transaction serialization, format to handle unsigned inputs where only the address is known, the public key is unknown. - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 16f36ee6e2663a1fdcfd193f57d1fa25b6a45866
   DIR parent c4ce16e2b621e5103503471baea2e8e103751252
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Fri, 31 Oct 2014 13:01:16 +0100
       
       Extend transaction serialization, format to handle unsigned inputs where only the address is known, the public key is unknown.
       
       Diffstat:
         M lib/transaction.py                  |      77 ++++++++++++++-----------------
         M lib/wallet.py                       |     114 ++++++++++++++++---------------
       
       2 files changed, 93 insertions(+), 98 deletions(-)
       ---
   DIR diff --git a/lib/transaction.py b/lib/transaction.py
       t@@ -332,9 +332,16 @@ def parse_xpub(x_pubkey):
                from account import OldAccount
                mpk, s = OldAccount.parse_xpubkey(x_pubkey)
                pubkey = OldAccount.get_pubkey_from_mpk(mpk.decode('hex'), s[0], s[1])
       +    elif x_pubkey[0:2] == 'fd':
       +        addrtype = ord(x_pubkey[2:4].decode('hex'))
       +        hash160 = x_pubkey[4:].decode('hex')
       +        pubkey = None
       +        address = hash_160_to_bc_address(hash160, addrtype)
            else:
                raise BaseException("Cannnot parse pubkey")
       -    return pubkey
       +    if pubkey:
       +        address = public_key_to_bc_address(pubkey.decode('hex'))
       +    return pubkey, address
        
        
        def parse_scriptSig(d, bytes):
       t@@ -365,7 +372,7 @@ def parse_scriptSig(d, bytes):
                x_pubkey = decoded[1][1].encode('hex')
                try:
                    signatures = parse_sig([sig])
       -            pubkey = parse_xpub(x_pubkey)
       +            pubkey, address = parse_xpub(x_pubkey)
                except:
                    import traceback
                    traceback.print_exc(file=sys.stdout)
       t@@ -375,7 +382,7 @@ def parse_scriptSig(d, bytes):
                d['x_pubkeys'] = [x_pubkey]
                d['num_sig'] = 1
                d['pubkeys'] = [pubkey]
       -        d['address'] = public_key_to_bc_address(pubkey.decode('hex'))
       +        d['address'] = address
                return
        
            # p2sh transaction, 2 of n
       t@@ -639,7 +646,11 @@ class Transaction:
                        sig_list = ''.join( map( lambda x: push_script(x), sig_list))
                        if not p2sh:
                            script = sig_list
       -                    script += push_script(pubkeys[0])
       +                    x_pubkey = pubkeys[0]
       +                    if x_pubkey is None:
       +                        addrtype, h160 = bc_address_to_hash_160(txin['address'])
       +                        x_pubkey = 'fd' + (chr(addrtype) + h160).encode('hex')
       +                    script += push_script(x_pubkey)
                        else:
                            script = '00'                                    # op_0
                            script += sig_list
       t@@ -673,16 +684,6 @@ class Transaction:
            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()
       -
            def add_input(self, input):
                self.inputs.append(input)
                self.raw = None
       t@@ -707,66 +708,56 @@ class Transaction:
                    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()
       +        out = 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
       -
       +                out.add(x_pubkey)
       +        return out
        
            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 if this txin is complete
                        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():
       +            for x_pubkey in txin['x_pubkeys']:
       +                if x_pubkey in keypairs.keys():
       +                    print_error("adding signature for", x_pubkey)
       +                    # add pubkey to txin
       +                    txin = self.inputs[i]
       +                    x_pubkeys = txin['x_pubkeys']
       +                    ii = x_pubkeys.index(x_pubkey)
       +                    sec = keypairs[x_pubkey]
       +                    pubkey = public_key_from_private_key(sec)
       +                    txin['x_pubkeys'][ii] = pubkey
       +                    txin['pubkeys'][ii] = pubkey
       +                    self.inputs[i] = txin
                            # add signature
       -                    sec = keypairs[pubkey]
       +                    for_sig = Hash(self.tx_for_sig(i).decode('hex'))
                            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'))
       +                    txin['signatures'][ii] = sig.encode('hex')
       +                    self.inputs[i] = txin
        
                print_error("is_complete", self.is_complete())
                self.raw = self.serialize()
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -766,35 +766,15 @@ class Abstract_Wallet(object):
                    return
                # check that the password is correct. This will raise if it's not.
                self.check_password(password)
       -
       -
                keypairs = {}
       -
       -        # tx.inputs_to_sign() : return list of addresses or derivations
       -        # this list should be enriched by add_keypairs
       -        addr_list, xpub_list = tx.inputs_to_sign()
       -        for addr in addr_list:
       -            if self.is_mine(addr):
       -                private_keys = self.get_private_key(addr, password)
       -                for sec in private_keys:
       -                    pubkey = public_key_from_private_key(sec)
       -                    keypairs[ pubkey ] = sec
       -
       -        for xpub, sequence in xpub_list:
       -            # look for account that can sign
       -            for k, account in self.accounts.items():
       -                if xpub in account.get_master_pubkeys():
       -                    break
       -            else:
       -                continue
       -            pk = account.get_private_key(sequence, self, password)
       -            for sec in pk:
       -                pubkey = public_key_from_private_key(sec)
       -                keypairs[pubkey] = sec
       -
       +        x_pubkeys = tx.inputs_to_sign()
       +        for x in x_pubkeys:
       +            sec = self.get_private_key_from_xpubkey(x, password)
       +            print "sec", sec
       +            if sec:
       +                keypairs[ x ] = sec
                if keypairs:
                    tx.sign(keypairs)
       -
                run_hook('sign_transaction', tx, password)
        
            def sendtx(self, tx):
       t@@ -1013,7 +993,59 @@ class Abstract_Wallet(object):
                return age > age_limit
        
            def can_sign(self, tx):
       -        pass
       +        if self.is_watching_only():
       +            return False
       +        if tx.is_complete():
       +            return False
       +        for x in tx.inputs_to_sign():
       +            if self.can_sign_xpubkey(x):
       +                return True
       +        return False
       +
       +
       +    def get_private_key_from_xpubkey(self, x_pubkey, password):
       +        if x_pubkey[0:2] in ['02','03','04']:
       +            addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
       +            if self.is_mine(addr):
       +                return self.get_private_key(addr, password)[0]
       +        elif x_pubkey[0:2] == 'ff':
       +            xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       +            for k, account in self.accounts.items():
       +                if xpub in account.get_master_pubkeys():
       +                    pk = account.get_private_key(sequence, self, password)
       +                    return pk[0]
       +        elif x_pubkey[0:2] == 'fe':
       +            xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
       +            for k, account in self.accounts.items():
       +                if xpub in account.get_master_pubkeys():
       +                    pk = account.get_private_key(sequence, self, password)
       +                    return pk[0]
       +        elif x_pubkey[0:2] == 'fd':
       +            addrtype = ord(x_pubkey[2:4].decode('hex'))
       +            addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
       +            if self.is_mine(addr):
       +                return self.get_private_key(addr, password)[0]
       +        else:
       +            raise BaseException("z")
       +
       +
       +    def can_sign_xpubkey(self, x_pubkey):
       +        if x_pubkey[0:2] in ['02','03','04']:
       +            addr = bitcoin.public_key_to_bc_address(x_pubkey.decode('hex'))
       +            return self.is_mine(addr)
       +        elif x_pubkey[0:2] == 'ff':
       +            xpub, sequence = BIP32_Account.parse_xpubkey(x_pubkey)
       +            return xpub in [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
       +        elif x_pubkey[0:2] == 'fe':
       +            xpub, sequence = OldAccount.parse_xpubkey(x_pubkey)
       +            return xpub == self.get_master_public_key()
       +        elif x_pubkey[0:2] == 'fd':
       +            addrtype = ord(x_pubkey[2:4].decode('hex'))
       +            addr = hash_160_to_bc_address(x_pubkey[4:].decode('hex'), addrtype)
       +            return self.is_mine(addr)
       +        else:
       +            raise BaseException("z")
       +
        
            def is_watching_only(self):
                False
       t@@ -1255,21 +1287,6 @@ class BIP32_Wallet(Deterministic_Wallet):
                xprv, xpub = bip32_private_derivation(root_xprv, root, derivation)
                return xpub, xprv
        
       -    def can_sign(self, tx):
       -        if self.is_watching_only():
       -            return False
       -        if tx.is_complete():
       -            return False
       -        addr_list, xpub_list = tx.inputs_to_sign()
       -        for addr in addr_list:
       -            if self.is_mine(addr):
       -                return True
       -        mpk = [ self.master_public_keys[k] for k in self.master_private_keys.keys() ]
       -        for xpub, sequence in xpub_list:
       -            if xpub in mpk:
       -                return True
       -        return False
       -
            def create_master_keys(self, password):
                seed = self.get_seed(password)
                self.add_cosigner_seed(seed, self.root_name, password)
       t@@ -1564,19 +1581,6 @@ class OldWallet(Deterministic_Wallet):
                s = self.get_seed(password)
                return ' '.join(old_mnemonic.mn_encode(s))
        
       -    def can_sign(self, tx):
       -        if self.is_watching_only():
       -            return False
       -        if tx.is_complete():
       -            return False
       -        addr_list, xpub_list = tx.inputs_to_sign()
       -        for addr in addr_list:
       -            if self.is_mine(addr):
       -                return True
       -        for xpub, sequence in xpub_list:
       -            if xpub == self.get_master_public_key():
       -                return True
       -        return False