URI: 
       tMerge branch 'master' of git://github.com/spesmilo/electrum - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit bbb7d6be92ab8cdc4cff1c86e3cbd3fc1491cf4b
   DIR parent 75cb5ddf2fabd9685be9c9d5c0fee8cf1e2c765f
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Sat, 23 Feb 2013 16:09:13 +0100
       
       Merge branch 'master' of git://github.com/spesmilo/electrum
       
       Diffstat:
         M electrum                            |      24 ++++++++++++++++--------
         M lib/bitcoin.py                      |     107 +++++++++++++++++++++++++------
         M lib/deserialize.py                  |       2 +-
         M lib/gui_qt.py                       |       4 ++--
         M lib/wallet.py                       |     200 +++++++++++++------------------
       
       5 files changed, 190 insertions(+), 147 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -247,7 +247,7 @@ if __name__ == '__main__':
                        wallet.gap_limit = gap
                        if len(seed) == 128:
                            wallet.seed = ''
       -                    wallet.master_public_key = seed
       +                    wallet.sequence.master_public_key = seed
                        else:
                            wallet.init_seed(str(seed))
                    
       t@@ -332,7 +332,7 @@ if __name__ == '__main__':
        
                    if len(seed) == 128:
                        wallet.seed = None
       -                wallet.master_public_key = seed
       +                wallet.sequence.master_public_key = seed
                    else:
                        wallet.seed = str(seed)
                        wallet.init_mpk( wallet.seed )
       t@@ -488,12 +488,12 @@ if __name__ == '__main__':
                    except:
                        sys.exit("Error: Error with seed file")
        
       -            mpk = wallet.master_public_key
       +            mpk = wallet.get_master_public_key()
                    wallet.seed = seed
                    wallet.imported_keys = imported_keys
                    wallet.use_encryption = False
                    wallet.init_mpk(seed)
       -            if mpk == wallet.master_public_key:
       +            if mpk == wallet.get_master_public_key():
                        wallet.save()
                        print_msg("Done: " + wallet.config.path)
                    else:
       t@@ -501,7 +501,16 @@ if __name__ == '__main__':
        
            elif cmd == 'validateaddress':
                addr = args[1]
       -        print_msg(wallet.is_valid(addr))
       +        is_valid = wallet.is_valid(addr)
       +        out = { 'isvalid':is_valid }
       +        if is_valid:
       +            is_mine = wallet.is_mine(addr)
       +            out['address'] = addr
       +            out['ismine'] = is_mine
       +            if is_mine:
       +                out['pubkey'] = wallet.get_public_key(addr)
       +            
       +        print_json(out)
        
            elif cmd == 'balance':
                try:
       t@@ -779,12 +788,11 @@ if __name__ == '__main__':
                    private_keys = pk
        
                tx.sign( private_keys )
       -        print_msg(tx)
       +        print_json({ "hex":str(tx),"complete":tx.is_complete})
        
        
            elif cmd == 'listunspent':
       -        unspent = map(lambda x: {"txid":x[0].split(':')[0],"vout":x[0].split(':')[1],"amount":x[1]*1.e-8},  wallet.prevout_values.items() )
       -        print_json(unspent)
       +        print_json(wallet.get_unspent_coins())
                
        
            if cmd not in offline_commands and not options.offline:
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -398,7 +398,57 @@ def CKD_prime(K, c, n):
        
        
        
       +class DeterministicSequence:
       +    """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
        
       +    def __init__(self, master_public_key):
       +        self.master_public_key = master_public_key
       +
       +    @classmethod
       +    def from_seed(klass, seed):
       +        curve = SECP256k1
       +        secexp = klass.stretch_key(seed)
       +        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       +        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       +        self = klass(master_public_key)
       +        return self
       +
       +    @classmethod
       +    def stretch_key(self,seed):
       +        oldseed = seed
       +        for i in range(100000):
       +            seed = hashlib.sha256(seed + oldseed).digest()
       +        return string_to_number( seed )
       +
       +    def get_sequence(self,n,for_change):
       +        return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
       +
       +    def get_pubkey(self, n, for_change):
       +        curve = SECP256k1
       +        z = self.get_sequence(n, for_change)
       +        master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), 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_private_key(self, n, for_change, seed):
       +        order = generator_secp256k1.order()
       +        secexp = self.stretch_key(seed)
       +        secexp = ( secexp + self.get_sequence(n,for_change) ) % order
       +        pk = number_to_string( secexp, generator_secp256k1.order() )
       +        compressed = False
       +        return SecretToASecret( pk, compressed )
       +
       +    def check_seed(self, seed):
       +        curve = SECP256k1
       +        secexp = self.stretch_key(seed)
       +        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       +        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       +        if master_public_key != self.master_public_key:
       +            print_error('invalid password (mpk)')
       +            raise BaseException('Invalid password')
       +
       +        return True
        
        ################################## transactions
        
       t@@ -535,6 +585,7 @@ class Transaction:
        
                for i in range(len(self.inputs)):
                    txin = self.inputs[i]
       +            tx_for_sig = raw_tx( self.inputs, self.outputs, for_sig = i )
        
                    if txin.get('redeemScript'):
                        # 1 parse the redeem script
       t@@ -549,26 +600,43 @@ class Transaction:
                            pubkey = GetPubKey(pkey.pubkey, compressed)
                            keypairs[ pubkey.encode('hex') ] = sec
        
       -                # list of signatures
       +                # list of already existing signatures
                        signatures = txin.get("signatures",[])
       -                
       +                found = False
       +                complete = True
       +
                        # 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 pubkey in redeem_pubkeys:
       +                    public_key = ecdsa.VerifyingKey.from_string(pubkey[2:].decode('hex'), curve = SECP256k1)
       +
       +                    for s in signatures:
       +                        try:
       +                            public_key.verify_digest( s.decode('hex')[:-1], Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
       +                            break
       +                        except ecdsa.keys.BadSignatureError:
       +                            continue
       +                    else:
       +                        if pubkey in keypairs.keys():
       +                            # add signature
       +                            sec = keypairs[pubkey]
       +                            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()
       +                            sig = private_key.sign_digest( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
       +                            assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
       +                            signatures.append( sig.encode('hex') )
       +                            found = True
       +                        else:
       +                            complete = False
       +                        
       +                if not found:
       +                    raise BaseException("public key not found", keypairs.keys(), redeem_pubkeys)
        
                        # for p2sh, pubkeysig is a tuple (may be incomplete)
                        self.inputs[i]["signatures"] = signatures
       +                self.is_complete = complete
        
                    else:
                        sec = private_keys[txin['address']]
       t@@ -580,11 +648,11 @@ class Transaction:
                        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)
       +                sig = private_key.sign_digest( Hash( tx_for_sig.decode('hex') ), sigencode = ecdsa.util.sigencode_der )
       +                assert public_key.verify_digest( sig, Hash( tx_for_sig.decode('hex') ), sigdecode = ecdsa.util.sigdecode_der)
        
                        self.inputs[i]["pubkeysig"] = [(pubkey, sig)]
       +                self.is_complete = True
        
                self.raw = raw_tx( self.inputs, self.outputs )
        
       t@@ -598,9 +666,6 @@ class Transaction:
            
        
            def has_address(self, addr):
       -        print self.inputs
       -        print self.outputs
       -        
                found = False
                for txin in self.inputs:
                    if addr == txin.get('address'): 
   DIR diff --git a/lib/deserialize.py b/lib/deserialize.py
       t@@ -315,7 +315,7 @@ 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:
       +        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
   DIR diff --git a/lib/gui_qt.py b/lib/gui_qt.py
       t@@ -1319,10 +1319,10 @@ class ElectrumWindow(QMainWindow):
                dialog.setWindowTitle(_("Master Public Key"))
        
                main_text = QTextEdit()
       -        main_text.setText(self.wallet.master_public_key)
       +        main_text.setText(self.wallet.get_master_public_key())
                main_text.setReadOnly(True)
                main_text.setMaximumHeight(170)
       -        qrw = QRCodeWidget(self.wallet.master_public_key, 6)
       +        qrw = QRCodeWidget(self.wallet.get_master_public_key(), 6)
        
                ok_button = QPushButton(_("OK"))
                ok_button.setDefault(True)
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -43,6 +43,27 @@ urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x)
        EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
        DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
        
       +def pw_encode(s, password):
       +    if password:
       +        secret = Hash(password)
       +        return EncodeAES(secret, s)
       +    else:
       +        return s
       +
       +def pw_decode(s, password):
       +    if password is not None:
       +        secret = Hash(password)
       +        try:
       +            d = DecodeAES(secret, s)
       +        except:
       +            raise BaseException('Invalid password')
       +        return d
       +    else:
       +        return s
       +
       +
       +
       +
        
        from version import ELECTRUM_VERSION, SEED_VERSION
        
       t@@ -60,7 +81,6 @@ class Wallet:
                self.use_change            = config.get('use_change',True)
                self.fee                   = int(config.get('fee',100000))
                self.num_zeros             = int(config.get('num_zeros',0))
       -        self.master_public_key     = config.get('master_public_key','')
                self.use_encryption        = config.get('use_encryption', False)
                self.addresses             = config.get('addresses', [])          # receiving addresses visible for user
                self.change_addresses      = config.get('change_addresses', [])   # addresses used as change
       t@@ -76,6 +96,9 @@ class Wallet:
                self.history               = config.get('addr_history',{})        # address -> list(txid, height)
                self.tx_height             = config.get('tx_height',{})
        
       +        master_public_key     = config.get('master_public_key','')
       +        self.sequence = DeterministicSequence(master_public_key)
       +
                self.transactions = {}
                tx = config.get('transactions',{})
                try:
       t@@ -122,19 +145,15 @@ class Wallet:
                while not self.is_up_to_date(): time.sleep(0.1)
        
            def import_key(self, sec, password):
       -        # try password
       -        try:
       -            seed = self.decode_seed(password)
       -        except:
       -            raise BaseException("Invalid password")
       -
       +        # check password
       +        seed = self.decode_seed(password)
                address = address_from_private_key(sec)
        
                if address in self.all_addresses():
                    raise BaseException('Address already in wallet')
                
                # store the originally requested keypair into the imported keys table
       -        self.imported_keys[address] = self.pw_encode(sec, password )
       +        self.imported_keys[address] = pw_encode(sec, password )
                return address
                
        
       t@@ -149,11 +168,8 @@ class Wallet:
        
            def init_mpk(self,seed):
                # public key
       -        curve = SECP256k1
       -        secexp = self.stretch_key(seed)
       -        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       -        self.master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       -        self.config.set_key('master_public_key', self.master_public_key, True)
       +        self.sequence = DeterministicSequence.from_seed(seed)
       +        self.config.set_key('master_public_key', self.sequence.master_public_key, True)
        
            def all_addresses(self):
                return self.addresses + self.change_addresses + self.imported_keys.keys()
       t@@ -173,23 +189,35 @@ class Wallet:
                    return False
                return addr == hash_160_to_bc_address(h, addrtype)
        
       -    def stretch_key(self,seed):
       -        oldseed = seed
       -        for i in range(100000):
       -            seed = hashlib.sha256(seed + oldseed).digest()
       -        return string_to_number( seed )
       +    def get_master_public_key(self):
       +        return self.sequence.master_public_key
       +
       +    def get_public_key(self, address):
       +        if address in self.imported_keys.keys():
       +            raise BaseException("imported key")
        
       -    def get_sequence(self,n,for_change):
       -        return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.master_public_key.decode('hex') ) )
       +        if address in self.addresses:
       +            n = self.addresses.index(address)
       +            for_change = False
       +        elif address in self.change_addresses:
       +            n = self.change_addresses.index(address)
       +            for_change = True
        
       +        return self.sequence.get_pubkey(n, for_change)
       +
       +
       +    def decode_seed(self, password):
       +        seed = pw_decode(self.seed, password)
       +        self.sequence.check_seed(seed)
       +        return seed
       +        
            def get_private_key(self, address, password):
       -        """  Privatekey(type,n) = Master_private_key + H(n|S|type)  """
        
       -        # decode seed in any case, in order to make test the password
       +        # decode seed in any case, in order to test the password
                seed = self.decode_seed(password)
        
                if address in self.imported_keys.keys():
       -            return self.pw_decode( self.imported_keys[address], password )
       +            return pw_decode( self.imported_keys[address], password )
                else:
                    if address in self.addresses:
                        n = self.addresses.index(address)
       t@@ -199,13 +227,8 @@ class Wallet:
                        for_change = True
                    else:
                        raise BaseException("unknown address", address)
       -
       -            order = generator_secp256k1.order()
       -            secexp = self.stretch_key(seed)
       -            secexp = ( secexp + self.get_sequence(n,for_change) ) % order
       -            pk = number_to_string( secexp, generator_secp256k1.order() )
       -            compressed = False
       -            return SecretToASecret( pk, compressed )
       +            
       +            return self.sequence.get_private_key(n, for_change, seed)
        
        
            def sign_message(self, address, message, password):
       t@@ -225,16 +248,10 @@ class Wallet:
                return address
                
            def get_new_address(self, n, for_change):
       -        """   Publickey(type,n) = Master_public_key + H(n|S|type)*point  """
       -        curve = SECP256k1
       -        z = self.get_sequence(n, for_change)
       -        master_public_key = ecdsa.VerifyingKey.from_string( self.master_public_key.decode('hex'), curve = SECP256k1 )
       -        pubkey_point = master_public_key.pubkey.point + z*curve.generator
       -        public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
       -        address = public_key_to_bc_address( '04'.decode('hex') + public_key2.to_string() )
       -        print address
       +        pubkey = self.sequence.get_pubkey(n, for_change)
       +        address = public_key_to_bc_address( pubkey.decode('hex') )
       +        print_msg( address )
                return address
       -                                                                      
        
            def change_gap_limit(self, value):
                if value >= self.gap_limit:
       t@@ -303,7 +320,7 @@ class Wallet:
                
        
            def synchronize(self):
       -        if not self.master_public_key:
       +        if not self.sequence.master_public_key:
                    return []
                new_addresses = []
                new_addresses += self.synchronize_sequence(self.addresses, self.gap_limit, False)
       t@@ -341,7 +358,7 @@ class Wallet:
                import datetime
                if not tx_hash: return ''
                tx = self.transactions.get(tx_hash)
       -        is_mine, v, fee = self.get_tx_value(tx_hash)
       +        is_mine, v, fee = self.get_tx_value(tx)
                conf, timestamp = self.verifier.get_confirmations(tx_hash)
        
                if timestamp:
       t@@ -349,8 +366,8 @@ class Wallet:
                else:
                    time_str = 'pending'
        
       -        inputs = map(lambda x: x.get('address'), tx['inputs'])
       -        outputs = map(lambda x: x.get('address'), tx['outputs'])
       +        inputs = map(lambda x: x.get('address'), tx.inputs)
       +        outputs = map(lambda x: x.get('address'), tx.d['outputs'])
                tx_details = "Transaction Details" +"\n\n" \
                    + "Transaction ID:\n" + tx_hash + "\n\n" \
                    + "Status: %d confirmations\n"%conf
       t@@ -448,45 +465,40 @@ class Wallet:
                return conf, unconf
        
        
       -    def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
       -        """ todo: minimize tx size """
       -        total = 0
       -        fee = self.fee if fixed_fee is None else fixed_fee
       -
       +    def get_unspent_coins(self, domain=None):
                coins = []
       -        prioritized_coins = []
       -        domain = [from_addr] if from_addr else self.all_addresses()
       -        for i in self.frozen_addresses:
       -            if i in domain: domain.remove(i)
       -
       -        for i in self.prioritized_addresses:
       -            if i in domain: domain.remove(i)
       -
       +        if domain is None: domain = self.all_addresses()
                for addr in domain:
                    h = self.history.get(addr, [])
                    if h == ['*']: continue
                    for tx_hash, tx_height in h:
                        tx = self.transactions.get(tx_hash)
       -                for output in tx.get('outputs'):
       +                for output in tx.d.get('outputs'):
                            if output.get('address') != addr: continue
                            key = tx_hash + ":%d" % output.get('index')
                            if key in self.spent_outputs: continue
                            output['tx_hash'] = tx_hash
                            coins.append(output)
       +        return coins
        
        
       -        for addr in self.prioritized_addresses:
       -            h = self.history.get(addr, [])
       -            if h == ['*']: continue
       -            for tx_hash, tx_height in h:
       -                tx = self.transactions.get(tx_hash)
       -                for output in tx.get('outputs'):
       -                    if output.get('address') != addr: continue
       -                    key = tx_hash + ":%d" % output.get('index')
       -                    if key in self.spent_outputs: continue
       -                    output['tx_hash'] = tx_hash
       -                    prioritized_coins.append(output)
        
       +    def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
       +        """ todo: minimize tx size """
       +        total = 0
       +        fee = self.fee if fixed_fee is None else fixed_fee
       +
       +        coins = []
       +        prioritized_coins = []
       +        domain = [from_addr] if from_addr else self.all_addresses()
       +        for i in self.frozen_addresses:
       +            if i in domain: domain.remove(i)
       +
       +        for i in self.prioritized_addresses:
       +            if i in domain: domain.remove(i)
       +
       +        coins = self.get_unspent_coins(domain)
       +        prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
        
                inputs = []
                coins = prioritized_coins + coins
       t@@ -516,39 +528,6 @@ class Wallet:
                return outputs
        
        
       -    def pw_encode(self, s, password):
       -        if password:
       -            secret = Hash(password)
       -            return EncodeAES(secret, s)
       -        else:
       -            return s
       -
       -    def pw_decode(self, s, password):
       -        if password is not None:
       -            secret = Hash(password)
       -            try:
       -                d = DecodeAES(secret, s)
       -            except:
       -                raise BaseException('Invalid password')
       -            return d
       -        else:
       -            return s
       -
       -    def decode_seed(self, password):
       -        seed = self.pw_decode(self.seed, password)
       -
       -        # check decoded seed with master public key
       -        curve = SECP256k1
       -        secexp = self.stretch_key(seed)
       -        master_private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       -        master_public_key = master_private_key.get_verifying_key().to_string().encode('hex')
       -        if master_public_key != self.master_public_key:
       -            print_error('invalid password (mpk)')
       -            raise BaseException('Invalid password')
       -
       -        return seed
       -
       -
            def get_history(self, address):
                with self.lock:
                    return self.history.get(address)
       t@@ -605,7 +584,7 @@ class Wallet:
            def get_tx_history(self):
                with self.lock:
                    history = self.transactions.items()
       -        history.sort(key = lambda x: self.tx_height.get(x[0],1e12) )
       +        history.sort(key = lambda x: self.tx_height.get(x[0]) if self.tx_height.get(x[0]) else 1e12)
                result = []
            
                balance = 0
       t@@ -629,16 +608,6 @@ class Wallet:
        
                return result
        
       -    def get_transactions_at_height(self, height):
       -        with self.lock:
       -            values = self.transactions.values()[:]
       -
       -        out = []
       -        for tx in values:
       -            if tx['height'] == height:
       -                out.append(tx['tx_hash'])
       -        return out
       -
        
            def get_label(self, tx_hash):
                label = self.labels.get(tx_hash)
       t@@ -646,6 +615,7 @@ class Wallet:
                if is_default: label = self.get_default_label(tx_hash)
                return label, is_default
        
       +
            def get_default_label(self, tx_hash):
                tx = self.transactions.get(tx_hash)
                default_label = ''
       t@@ -791,12 +761,12 @@ class Wallet:
            def update_password(self, seed, old_password, new_password):
                if new_password == '': new_password = None
                self.use_encryption = (new_password != None)
       -        self.seed = self.pw_encode( seed, new_password)
       +        self.seed = pw_encode( seed, new_password)
                self.config.set_key('seed', self.seed, True)
                for k in self.imported_keys.keys():
                    a = self.imported_keys[k]
       -            b = self.pw_decode(a, old_password)
       -            c = self.pw_encode(b, new_password)
       +            b = pw_decode(a, old_password)
       +            c = pw_encode(b, new_password)
                    self.imported_keys[k] = c
                self.save()