URI: 
       tMerge branch '1.9' of git://github.com/spesmilo/electrum into 1.9 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 238ed351349eb1a84cba0b4c940c72e5e6cd98c8
   DIR parent f4c26dfac0a8b518aeee4adb5215971b72209c39
  HTML Author: thomasv <thomasv@gitorious>
       Date:   Tue, 27 Aug 2013 13:59:20 +0200
       
       Merge branch '1.9' of git://github.com/spesmilo/electrum into 1.9
       
       Diffstat:
         M electrum                            |      95 +++++++------------------------
         M gui/gui_classic.py                  |     302 +++++++------------------------
         A gui/installwizard.py                |     183 +++++++++++++++++++++++++++++++
         A gui/password_dialog.py              |     104 +++++++++++++++++++++++++++++++
         M gui/plugins.py                      |       1 +
         A gui/seed_dialog.py                  |      82 +++++++++++++++++++++++++++++++
         M lib/account.py                      |      68 +++++++++++++++++--------------
         M lib/bitcoin.py                      |     142 +++++++++++++------------------
         M lib/commands.py                     |      44 ++++++++++++++++++++++---------
         M lib/deserialize.py                  |       4 ++--
         M lib/wallet.py                       |     297 +++++++++++++++++++------------
       
       11 files changed, 768 insertions(+), 554 deletions(-)
       ---
   DIR diff --git a/electrum b/electrum
       t@@ -22,6 +22,7 @@ import sys, os, time, json
        import optparse
        import platform
        from decimal import Decimal
       +import traceback
        
        try:
            import ecdsa  
       t@@ -106,7 +107,6 @@ if __name__ == '__main__':
                util.check_windows_wallet_migration()
        
            config = SimpleConfig(config_options)
       -    wallet = Wallet(config)
        
        
            if len(args)==0:
       t@@ -124,86 +124,22 @@ if __name__ == '__main__':
                try:
                    gui = __import__('electrum_gui.gui_' + gui_name, fromlist=['electrum_gui'])
                except ImportError:
       -            sys.exit("Error: Unknown GUI: " + gui_name )
       +            traceback.print_exc(file=sys.stdout)
       +            sys.exit()
       +            #sys.exit("Error: Unknown GUI: " + gui_name )
                
       -        interface = Interface(config, True)
       -        wallet.interface = interface
       -
       -        gui = gui.ElectrumGui(wallet, config)
       -
       -        found = config.wallet_file_exists
       -        if not found:
       -            a = gui.restore_or_create()
       -            if not a: exit()
       -
       -            if a =='create':
       -                wallet.init_seed(None)
       -                gui.show_seed()
       -                if gui.verify_seed():
       -                    wallet.save_seed()
       -                else:
       -                    exit()
       -
       -            else:
       -                # ask for seed and gap.
       -                sg = gui.seed_dialog()
       -                if not sg: exit()
       -                seed, gap = sg
       -                if not seed: exit()
       -                wallet.gap_limit = gap
       -                if len(seed) == 128:
       -                    wallet.seed = ''
       -                    wallet.init_sequence(str(seed))
       -                else:
       -                    wallet.init_seed(str(seed))
       -                    wallet.save_seed()
       -            
       -            # select a server.
       -            s = gui.network_dialog()
       -            if s is None:
       -                config.set_key("server", None, True)
       -                config.set_key('auto_cycle', False, True)
       -            
       -        interface.start(wait = False)
       -        interface.send([('server.peers.subscribe',[])])
       -
       -        # generate the first addresses, in case we are offline
       -        if not found and ( s is None or a == 'create'):
       -            wallet.synchronize()
       -
       -        verifier = WalletVerifier(interface, config)
       -        verifier.start()
       -        wallet.set_verifier(verifier)
       -        synchronizer = WalletSynchronizer(wallet, config)
       -        synchronizer.start()
       -
       -        if not found and a == 'restore' and s is not None:
       -            try:
       -                keep_it = gui.restore_wallet()
       -                wallet.fill_addressbook()
       -            except:
       -                import traceback
       -                traceback.print_exc(file=sys.stdout)
       -                exit()
       -
       -            if not keep_it: exit()
       -
       -        if not found:
       -            gui.password_dialog()
       -
       -        #wallet.save()
       +        gui = gui.ElectrumGui(config)
                gui.main(url)
       -        #wallet.save()
       -
       -        verifier.stop()
       -        synchronizer.stop()
       -        interface.stop()
        
                # we use daemon threads, their termination is enforced.
                # this sleep command gives them time to terminate cleanly. 
                time.sleep(0.1)
                sys.exit(0)
        
       +
       +    # instanciate wallet for command-line
       +    wallet = Wallet(config)
       +
            if cmd not in known_commands:
                cmd = 'help'
        
       t@@ -337,6 +273,16 @@ if __name__ == '__main__':
            elif cmd in ['payto', 'mktx']:
                domain = [options.from_addr] if options.from_addr else None
                args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ]
       +        
       +    elif cmd in ['paytomany', 'mksendmanytx']:
       +        domain = [options.from_addr] if options.from_addr else None
       +        outputs = []
       +        for i in range(1, len(args), 2):
       +            if len(args) < i+2:
       +                print_msg("Error: Mismatched arguments.")
       +                exit(1)
       +            outputs.append((args[i], Decimal(args[i+1])))
       +        args = [ 'mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ]        
        
            elif cmd == 'help':
                if len(args) < 2:
       t@@ -429,7 +375,8 @@ if __name__ == '__main__':
                try:
                    result = func(*args[1:])
                except BaseException, e:
       -            print_msg("Error: " + str(e))
       +            import traceback
       +            traceback.print_exc(file=sys.stdout)
                    sys.exit(1)
                    
                if type(result) == str:
   DIR diff --git a/gui/gui_classic.py b/gui/gui_classic.py
       t@@ -42,7 +42,7 @@ except:
        from electrum.wallet import format_satoshis
        from electrum.bitcoin import Transaction, is_valid
        from electrum import mnemonic
       -from electrum import util, bitcoin, commands
       +from electrum import util, bitcoin, commands, Interface, Wallet, WalletVerifier, WalletSynchronizer
        
        import bmp, pyqrnative
        import exchange_rate
       t@@ -265,6 +265,7 @@ class ElectrumWindow(QMainWindow):
                if reason == QSystemTrayIcon.DoubleClick:
                    self.showNormal()
        
       +
            def __init__(self, wallet, config):
                QMainWindow.__init__(self)
                self._close_electrum = False
       t@@ -352,10 +353,21 @@ class ElectrumWindow(QMainWindow):
                wallet_folder = self.wallet.config.path
                re.sub("(\/\w*.dat)$", "", wallet_folder)
                file_name = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat")
       -        if not file_name:
       -            return
       -        else:
       -          self.load_wallet(file_name)
       +        return file_name
       +
       +    def open_wallet(self):
       +        n = self.select_wallet_file()
       +        if n:
       +            self.load_wallet(n)
       +
       +    def new_wallet(self):
       +        n = self.getOpenFileName("Select wallet file")
       +
       +        wizard = installwizard.InstallWizard(self.config, self.interface)
       +        wallet = wizard.run()
       +        if wallet: 
       +            self.load_wallet(wallet)
       +        
        
        
            def init_menubar(self):
       t@@ -363,7 +375,10 @@ class ElectrumWindow(QMainWindow):
        
                electrum_menu = menubar.addMenu(_("&File"))
                open_wallet_action = electrum_menu.addAction(_("Open wallet"))
       -        open_wallet_action.triggered.connect(self.select_wallet_file)
       +        open_wallet_action.triggered.connect(self.open_wallet)
       +
       +        new_wallet_action = electrum_menu.addAction(_("New wallet"))
       +        new_wallet_action.triggered.connect(self.new_wallet)
        
                preferences_name = _("Preferences")
                if sys.platform == 'darwin':
       t@@ -430,6 +445,7 @@ class ElectrumWindow(QMainWindow):
        
                self.setMenuBar(menubar)
        
       +
            def load_wallet(self, filename):
                import electrum
        
       t@@ -1268,7 +1284,7 @@ class ElectrumWindow(QMainWindow):
                    account_items = []
        
                for k, account in account_items:
       -            name = account.get_name()
       +            name = self.wallet.labels.get(k, 'unnamed account')
                    c,u = self.wallet.get_account_balance(k)
                    account_item = QTreeWidgetItem( [ name, '', self.format_amount(c+u), ''] )
                    l.addTopLevelItem(account_item)
       t@@ -1395,7 +1411,7 @@ class ElectrumWindow(QMainWindow):
                    sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
                if self.wallet.seed:
                    self.lock_icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
       -            self.password_button = StatusBarButton( self.lock_icon, _("Password"), lambda: self.change_password_dialog(self.wallet, self) )
       +            self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
                    sb.addPermanentWidget( self.password_button )
                sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
                if self.wallet.seed:
       t@@ -1406,6 +1422,13 @@ class ElectrumWindow(QMainWindow):
                self.run_hook('create_status_bar', (sb,))
        
                self.setStatusBar(sb)
       +
       +
       +    def change_password_dialog(self):
       +        from password_dialog import PasswordDialog
       +        d = PasswordDialog(self.wallet, self)
       +        d.run()
       +
                
            def go_lite(self):
                import gui_lite
       t@@ -1490,63 +1513,12 @@ class ElectrumWindow(QMainWindow):
                except:
                    QMessageBox.warning(self, _('Error'), _('Incorrect Password'), _('OK'))
                    return
       -        self.show_seed(seed, self.wallet.imported_keys, self)
       -
       -
       -    @classmethod
       -    def show_seed(self, seed, imported_keys, parent=None):
       -        dialog = QDialog(parent)
       -        dialog.setModal(1)
       -        dialog.setWindowTitle('Electrum' + ' - ' + _('Seed'))
       -
       -        brainwallet = ' '.join(mnemonic.mn_encode(seed))
       -
       -        label1 = QLabel(_("Your wallet generation seed is")+ ":")
       -
       -        seed_text = QTextEdit(brainwallet)
       -        seed_text.setReadOnly(True)
       -        seed_text.setMaximumHeight(130)
       -        
       -        msg2 =  _("Please write down or memorize these 12 words (order is important).") + " " \
       -              + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
       -              + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
       -              + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
       -        if imported_keys:
       -            msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
       -        label2 = QLabel(msg2)
       -        label2.setWordWrap(True)
       -
       -        logo = QLabel()
       -        logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
       -        logo.setMaximumWidth(60)
       -
       -        qrw = QRCodeWidget(seed)
       -
       -        ok_button = QPushButton(_("OK"))
       -        ok_button.setDefault(True)
       -        ok_button.clicked.connect(dialog.accept)
       -
       -        grid = QGridLayout()
       -        #main_layout.addWidget(logo, 0, 0)
       -
       -        grid.addWidget(logo, 0, 0)
       -        grid.addWidget(label1, 0, 1)
       -
       -        grid.addWidget(seed_text, 1, 0, 1, 2)
       -
       -        grid.addWidget(qrw, 0, 2, 2, 1)
        
       -        vbox = QVBoxLayout()
       -        vbox.addLayout(grid)
       -        vbox.addWidget(label2)
       +        from seed_dialog import SeedDialog
       +        d = SeedDialog(self)
       +        d.show_seed(seed, self.wallet.imported_keys)
        
       -        hbox = QHBoxLayout()
       -        hbox.addStretch(1)
       -        hbox.addWidget(ok_button)
       -        vbox.addLayout(hbox)
        
       -        dialog.setLayout(vbox)
       -        dialog.exec_()
        
            def show_qrcode(self, data, title = "QR code"):
                if not data: return
       t@@ -1728,79 +1700,6 @@ class ElectrumWindow(QMainWindow):
        
        
        
       -    @staticmethod
       -    def change_password_dialog( wallet, parent=None ):
       -
       -        if not wallet.seed:
       -            QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
       -            return
       -
       -        d = QDialog(parent)
       -        d.setModal(1)
       -
       -        pw = QLineEdit()
       -        pw.setEchoMode(2)
       -        new_pw = QLineEdit()
       -        new_pw.setEchoMode(2)
       -        conf_pw = QLineEdit()
       -        conf_pw.setEchoMode(2)
       -
       -        vbox = QVBoxLayout()
       -        if parent:
       -            msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
       -                   +_('To disable wallet encryption, enter an empty new password.')) \
       -                   if wallet.use_encryption else _('Your wallet keys are not encrypted')
       -        else:
       -            msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
       -                  +_("Leave these fields empty if you want to disable encryption.")
       -        vbox.addWidget(QLabel(msg))
       -
       -        grid = QGridLayout()
       -        grid.setSpacing(8)
       -
       -        if wallet.use_encryption:
       -            grid.addWidget(QLabel(_('Password')), 1, 0)
       -            grid.addWidget(pw, 1, 1)
       -
       -        grid.addWidget(QLabel(_('New Password')), 2, 0)
       -        grid.addWidget(new_pw, 2, 1)
       -
       -        grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
       -        grid.addWidget(conf_pw, 3, 1)
       -        vbox.addLayout(grid)
       -
       -        vbox.addLayout(ok_cancel_buttons(d))
       -        d.setLayout(vbox) 
       -
       -        if not d.exec_(): return
       -
       -        password = unicode(pw.text()) if wallet.use_encryption else None
       -        new_password = unicode(new_pw.text())
       -        new_password2 = unicode(conf_pw.text())
       -
       -        try:
       -            seed = wallet.decode_seed(password)
       -        except:
       -            QMessageBox.warning(parent, _('Error'), _('Incorrect Password'), _('OK'))
       -            return
       -
       -        if new_password != new_password2:
       -            QMessageBox.warning(parent, _('Error'), _('Passwords do not match'), _('OK'))
       -            return ElectrumWindow.change_password_dialog(wallet, parent) # Retry
       -
       -        try:
       -            wallet.update_password(seed, password, new_password)
       -        except:
       -            QMessageBox.warning(parent, _('Error'), _('Failed to update password'), _('OK'))
       -            return
       -
       -        QMessageBox.information(parent, _('Success'), _('Password was updated successfully'), _('OK'))
       -
       -        if parent: 
       -            icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
       -            parent.password_button.setIcon( icon )
       -
       -
        
            def generate_transaction_information_widget(self, tx):
                tabs = QTabWidget(self)
       t@@ -2282,10 +2181,13 @@ class OpenFileEventFilter(QObject):
                        return True
                return False
        
       +
       +
       +
        class ElectrumGui:
        
       -    def __init__(self, wallet, config, app=None):
       -        self.wallet = wallet
       +    def __init__(self, config, app=None):
       +        self.interface = Interface(config, True)
                self.config = config
                self.windows = []
                self.efilter = OpenFileEventFilter(self.windows)
       t@@ -2293,116 +2195,32 @@ class ElectrumGui:
                    self.app = QApplication(sys.argv)
                self.app.installEventFilter(self.efilter)
        
       -    def restore_or_create(self):
       -        msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
       -        r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
       -        if r==2: return None
       -        return 'restore' if r==1 else 'create'
       -
       -
       -    def verify_seed(self):
       -        r = self.seed_dialog(False)
       -        if r != self.wallet.seed:
       -            QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
       -            return False
       -        else:
       -            return True
       -        
        
       -
       -    def seed_dialog(self, is_restore=True):
       -        d = QDialog()
       -        d.setModal(1)
       -
       -        vbox = QVBoxLayout()
       -        if is_restore:
       -            msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
       -        else:
       -            msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
       -
       -        msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
       -        
       -        label=QLabel(msg)
       -        label.setWordWrap(True)
       -        vbox.addWidget(label)
       -
       -        seed_e = QTextEdit()
       -        seed_e.setMaximumHeight(100)
       -        vbox.addWidget(seed_e)
       -
       -        if is_restore:
       -            grid = QGridLayout()
       -            grid.setSpacing(8)
       -            gap_e = AmountEdit(None, True)
       -            gap_e.setText("5")
       -            grid.addWidget(QLabel(_('Gap limit')), 2, 0)
       -            grid.addWidget(gap_e, 2, 1)
       -            grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
       -            vbox.addLayout(grid)
       -
       -        vbox.addLayout(ok_cancel_buttons(d))
       -        d.setLayout(vbox) 
       -
       -        if not d.exec_(): return
       -
       -        try:
       -            seed = str(seed_e.toPlainText())
       -            seed.decode('hex')
       -        except:
       -            try:
       -                seed = mnemonic.mn_decode( seed.split() )
       -            except:
       -                QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
       -                return
       -
       -        if not seed:
       -            QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
       -            return
       -
       -        if not is_restore:
       -            return seed
       +    def main(self, url):
       +            
       +        found = self.config.wallet_file_exists
       +        if not found:
       +            import installwizard
       +            wizard = installwizard.InstallWizard(self.config, self.interface)
       +            wallet = wizard.run()
       +            if not wallet: 
       +                exit()
                else:
       -            try:
       -                gap = int(unicode(gap_e.text()))
       -            except:
       -                QMessageBox.warning(None, _('Error'), 'error', 'OK')
       -                return
       -            return seed, gap
       -
       -
       -    def network_dialog(self):
       -        return NetworkDialog(self.wallet.interface, self.config, None).do_exec()
       -        
       -
       -    def show_seed(self):
       -        ElectrumWindow.show_seed(self.wallet.seed, self.wallet.imported_keys)
       +            wallet = Wallet(self.config)
        
       -    def password_dialog(self):
       -        if self.wallet.seed:
       -            ElectrumWindow.change_password_dialog(self.wallet)
       -
       -
       -    def restore_wallet(self):
       -        wallet = self.wallet
       -        # wait until we are connected, because the user might have selected another server
       -        if not wallet.interface.is_connected:
       -            waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
       -            waiting_dialog(waiting)
       +        self.wallet = wallet
        
       -        waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
       -            %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
       +        self.interface.start(wait = False)
       +        self.interface.send([('server.peers.subscribe',[])])
       +        wallet.interface = self.interface
        
       -        wallet.set_up_to_date(False)
       -        wallet.interface.poke('synchronizer')
       -        waiting_dialog(waiting)
       -        if wallet.is_found():
       -            print_error( "Recovery successful" )
       -        else:
       -            QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
       +        verifier = WalletVerifier(self.interface, self.config)
       +        verifier.start()
       +        wallet.set_verifier(verifier)
       +        synchronizer = WalletSynchronizer(wallet, self.config)
       +        synchronizer.start()
        
       -        return True
        
       -    def main(self,url):
                s = Timer()
                s.start()
                w = ElectrumWindow(self.wallet, self.config)
       t@@ -2415,4 +2233,8 @@ class ElectrumGui:
        
                self.app.exec_()
        
       +        verifier.stop()
       +        synchronizer.stop()
       +        self.interface.stop()
       +
        
   DIR diff --git a/gui/installwizard.py b/gui/installwizard.py
       t@@ -0,0 +1,183 @@
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +import PyQt4.QtCore as QtCore
       +from i18n import _
       +
       +from electrum import Wallet, mnemonic
       +from seed_dialog import SeedDialog
       +from network_dialog import NetworkDialog
       +from qt_util import *
       +
       +class InstallWizard(QDialog):
       +
       +    def __init__(self, config, interface):
       +        QDialog.__init__(self)
       +        self.config = config
       +        self.interface = interface
       +
       +
       +    def restore_or_create(self):
       +        msg = _("Wallet file not found.")+"\n"+_("Do you want to create a new wallet, or to restore an existing one?")
       +        r = QMessageBox.question(None, _('Message'), msg, _('Create'), _('Restore'), _('Cancel'), 0, 2)
       +        if r==2: return None
       +        return 'restore' if r==1 else 'create'
       +
       +
       +    def verify_seed(self, wallet):
       +        r = self.seed_dialog(False)
       +        if r != wallet.seed:
       +            QMessageBox.warning(None, _('Error'), 'incorrect seed', 'OK')
       +            return False
       +        else:
       +            return True
       +
       +
       +    def seed_dialog(self, is_restore=True):
       +        d = QDialog()
       +        d.setModal(1)
       +
       +        vbox = QVBoxLayout()
       +        if is_restore:
       +            msg = _("Please enter your wallet seed (or your master public key if you want to create a watching-only wallet)." + ' ')
       +        else:
       +            msg = _("Your seed is important! To make sure that you have properly saved your seed, please type it here." + ' ')
       +
       +        msg += _("Your seed can be entered as a sequence of words, or as a hexadecimal string."+ '\n')
       +        
       +        label=QLabel(msg)
       +        label.setWordWrap(True)
       +        vbox.addWidget(label)
       +
       +        seed_e = QTextEdit()
       +        seed_e.setMaximumHeight(100)
       +        vbox.addWidget(seed_e)
       +
       +        if is_restore:
       +            grid = QGridLayout()
       +            grid.setSpacing(8)
       +            gap_e = AmountEdit(None, True)
       +            gap_e.setText("5")
       +            grid.addWidget(QLabel(_('Gap limit')), 2, 0)
       +            grid.addWidget(gap_e, 2, 1)
       +            grid.addWidget(HelpButton(_('Keep the default value unless you modified this parameter in your wallet.')), 2, 3)
       +            vbox.addLayout(grid)
       +
       +        vbox.addLayout(ok_cancel_buttons(d))
       +        d.setLayout(vbox) 
       +
       +        if not d.exec_(): return
       +
       +        try:
       +            seed = str(seed_e.toPlainText())
       +            seed.decode('hex')
       +        except:
       +            try:
       +                seed = mnemonic.mn_decode( seed.split() )
       +            except:
       +                QMessageBox.warning(None, _('Error'), _('I cannot decode this'), _('OK'))
       +                return
       +
       +        if not seed:
       +            QMessageBox.warning(None, _('Error'), _('No seed'), _('OK'))
       +            return
       +
       +        if not is_restore:
       +            return seed
       +        else:
       +            try:
       +                gap = int(unicode(gap_e.text()))
       +            except:
       +                QMessageBox.warning(None, _('Error'), 'error', 'OK')
       +                return
       +            return seed, gap
       +
       +
       +    def network_dialog(self):
       +        return NetworkDialog(self.interface, self.config, None).do_exec()
       +        
       +
       +    def show_seed(self, wallet):
       +        d = SeedDialog()
       +        d.show_seed(wallet.seed, wallet.imported_keys)
       +
       +
       +    def password_dialog(self, wallet):
       +        from password_dialog import PasswordDialog
       +        d = PasswordDialog(wallet)
       +        d.run()
       +
       +
       +    def restore_wallet(self):
       +        wallet = self.wallet
       +        # wait until we are connected, because the user might have selected another server
       +        if not wallet.interface.is_connected:
       +            waiting = lambda: False if wallet.interface.is_connected else "%s \n" % (_("Connecting..."))
       +            waiting_dialog(waiting)
       +
       +        waiting = lambda: False if wallet.is_up_to_date() else "%s\n%s %d\n%s %.1f"\
       +            %(_("Please wait..."),_("Addresses generated:"),len(wallet.addresses(True)),_("Kilobytes received:"), wallet.interface.bytes_received/1024.)
       +
       +        wallet.set_up_to_date(False)
       +        wallet.interface.poke('synchronizer')
       +        waiting_dialog(waiting)
       +        if wallet.is_found():
       +            print_error( "Recovery successful" )
       +        else:
       +            QMessageBox.information(None, _('Error'), _("No transactions found for this seed"), _('OK'))
       +
       +        return True
       +
       +
       +    def run(self):
       +
       +        a = self.restore_or_create()
       +        if not a: exit()
       +
       +        wallet = Wallet(self.config)
       +        wallet.interface = self.interface
       +
       +        if a =='create':
       +            wallet.init_seed(None)
       +            self.show_seed(wallet)
       +            if self.verify_seed(wallet):
       +                wallet.save_seed()
       +            else:
       +                exit()
       +        else:
       +            # ask for seed and gap.
       +            sg = gui.seed_dialog()
       +            if not sg: exit()
       +            seed, gap = sg
       +            if not seed: exit()
       +            wallet.gap_limit = gap
       +            if len(seed) == 128:
       +                wallet.seed = ''
       +                wallet.init_sequence(str(seed))
       +            else:
       +                wallet.init_seed(str(seed))
       +                wallet.save_seed()
       +
       +        # select a server.
       +        s = self.network_dialog()
       +        if s is None:
       +            self.config.set_key("server", None, True)
       +            self.config.set_key('auto_cycle', False, True)
       +
       +        # generate the first addresses, in case we are offline
       +        if s is None or a == 'create':
       +            wallet.synchronize()
       +
       +
       +        if a == 'restore' and s is not None:
       +            try:
       +                keep_it = gui.restore_wallet()
       +                wallet.fill_addressbook()
       +            except:
       +                import traceback
       +                traceback.print_exc(file=sys.stdout)
       +                exit()
       +
       +            if not keep_it: exit()
       +
       +
       +        self.password_dialog(wallet)
   DIR diff --git a/gui/password_dialog.py b/gui/password_dialog.py
       t@@ -0,0 +1,104 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2013 ecdsa@github
       +#
       +# This program is free software: you can redistribute it and/or modify
       +# it under the terms of the GNU General Public License as published by
       +# the Free Software Foundation, either version 3 of the License, or
       +# (at your option) any later version.
       +#
       +# This program is distributed in the hope that it will be useful,
       +# but WITHOUT ANY WARRANTY; without even the implied warranty of
       +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
       +# GNU General Public License for more details.
       +#
       +# You should have received a copy of the GNU General Public License
       +# along with this program. If not, see <http://www.gnu.org/licenses/>.
       +
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +from i18n import _
       +from qt_util import *
       +
       +
       +class PasswordDialog(QDialog):
       +
       +    def __init__(self, wallet, parent=None):
       +        QDialog.__init__(self, parent)
       +        self.setModal(1)
       +        self.wallet = wallet
       +        self.parent = parent
       +
       +        self.pw = QLineEdit()
       +        self.pw.setEchoMode(2)
       +        self.new_pw = QLineEdit()
       +        self.new_pw.setEchoMode(2)
       +        self.conf_pw = QLineEdit()
       +        self.conf_pw.setEchoMode(2)
       +
       +        vbox = QVBoxLayout()
       +        if parent:
       +            msg = (_('Your wallet is encrypted. Use this dialog to change your password.')+'\n'\
       +                   +_('To disable wallet encryption, enter an empty new password.')) \
       +                   if wallet.use_encryption else _('Your wallet keys are not encrypted')
       +        else:
       +            msg = _("Please choose a password to encrypt your wallet keys.")+'\n'\
       +                  +_("Leave these fields empty if you want to disable encryption.")
       +        vbox.addWidget(QLabel(msg))
       +
       +        grid = QGridLayout()
       +        grid.setSpacing(8)
       +
       +        if wallet.use_encryption:
       +            grid.addWidget(QLabel(_('Password')), 1, 0)
       +            grid.addWidget(self.pw, 1, 1)
       +
       +        grid.addWidget(QLabel(_('New Password')), 2, 0)
       +        grid.addWidget(self.new_pw, 2, 1)
       +
       +        grid.addWidget(QLabel(_('Confirm Password')), 3, 0)
       +        grid.addWidget(self.conf_pw, 3, 1)
       +        vbox.addLayout(grid)
       +
       +        vbox.addLayout(ok_cancel_buttons(self))
       +        self.setLayout(vbox) 
       +
       +
       +    def run(self):
       +        wallet = self.wallet
       +
       +        if not wallet.seed:
       +            QMessageBox.information(parent, _('Error'), _('No seed'), _('OK'))
       +            return
       +
       +        if not self.exec_(): return
       +
       +        password = unicode(self.pw.text()) if wallet.use_encryption else None
       +        new_password = unicode(self.new_pw.text())
       +        new_password2 = unicode(self.conf_pw.text())
       +
       +        try:
       +            seed = wallet.decode_seed(password)
       +        except:
       +            QMessageBox.warning(self.parent, _('Error'), _('Incorrect Password'), _('OK'))
       +            return
       +
       +        if new_password != new_password2:
       +            QMessageBox.warning(self.parent, _('Error'), _('Passwords do not match'), _('OK'))
       +            self.run() # Retry
       +
       +        try:
       +            wallet.update_password(seed, password, new_password)
       +        except:
       +            QMessageBox.warning(self.parent, _('Error'), _('Failed to update password'), _('OK'))
       +            return
       +
       +        QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
       +
       +        if self.parent: 
       +            icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
       +            self.parent.password_button.setIcon( icon )
       +
       +
       +
   DIR diff --git a/gui/plugins.py b/gui/plugins.py
       t@@ -4,6 +4,7 @@ class BasePlugin:
        
            def __init__(self, gui, name):
                self.gui = gui
       +        self.wallet = self.gui.wallet
                self.name = name
                self.config = gui.config
        
   DIR diff --git a/gui/seed_dialog.py b/gui/seed_dialog.py
       t@@ -0,0 +1,82 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2013 ecdsa@github
       +#
       +# This program is free software: you can redistribute it and/or modify
       +# it under the terms of the GNU General Public License as published by
       +# the Free Software Foundation, either version 3 of the License, or
       +# (at your option) any later version.
       +#
       +# This program is distributed in the hope that it will be useful,
       +# but WITHOUT ANY WARRANTY; without even the implied warranty of
       +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
       +# GNU General Public License for more details.
       +#
       +# You should have received a copy of the GNU General Public License
       +# along with this program. If not, see <http://www.gnu.org/licenses/>.
       +
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +import PyQt4.QtCore as QtCore
       +from i18n import _
       +from electrum import mnemonic
       +from qrcodewidget import QRCodeWidget
       +
       +class SeedDialog(QDialog):
       +    def __init__(self, parent=None):
       +        QDialog.__init__(self, parent)
       +        self.setModal(1)
       +        self.setWindowTitle('Electrum' + ' - ' + _('Seed'))
       +
       +
       +    def show_seed(self, seed, imported_keys, parent=None):
       +
       +        brainwallet = ' '.join(mnemonic.mn_encode(seed))
       +
       +        label1 = QLabel(_("Your wallet generation seed is")+ ":")
       +
       +        seed_text = QTextEdit(brainwallet)
       +        seed_text.setReadOnly(True)
       +        seed_text.setMaximumHeight(130)
       +        
       +        msg2 =  _("Please write down or memorize these 12 words (order is important).") + " " \
       +              + _("This seed will allow you to recover your wallet in case of computer failure.") + " " \
       +              + _("Your seed is also displayed as QR code, in case you want to transfer it to a mobile phone.") + "<p>" \
       +              + "<b>"+_("WARNING")+":</b> " + _("Never disclose your seed. Never type it on a website.") + "</b><p>"
       +        if imported_keys:
       +            msg2 += "<b>"+_("WARNING")+":</b> " + _("Your wallet contains imported keys. These keys cannot be recovered from seed.") + "</b><p>"
       +        label2 = QLabel(msg2)
       +        label2.setWordWrap(True)
       +
       +        logo = QLabel()
       +        logo.setPixmap(QPixmap(":icons/seed.png").scaledToWidth(56))
       +        logo.setMaximumWidth(60)
       +
       +        qrw = QRCodeWidget(seed)
       +
       +        ok_button = QPushButton(_("OK"))
       +        ok_button.setDefault(True)
       +        ok_button.clicked.connect(self.accept)
       +
       +        grid = QGridLayout()
       +        #main_layout.addWidget(logo, 0, 0)
       +
       +        grid.addWidget(logo, 0, 0)
       +        grid.addWidget(label1, 0, 1)
       +
       +        grid.addWidget(seed_text, 1, 0, 1, 2)
       +
       +        grid.addWidget(qrw, 0, 2, 2, 1)
       +
       +        vbox = QVBoxLayout()
       +        vbox.addLayout(grid)
       +        vbox.addWidget(label2)
       +
       +        hbox = QHBoxLayout()
       +        hbox.addStretch(1)
       +        hbox.addWidget(ok_button)
       +        vbox.addLayout(hbox)
       +
       +        self.setLayout(vbox)
       +        self.exec_()
   DIR diff --git a/lib/account.py b/lib/account.py
       t@@ -24,13 +24,9 @@ class Account(object):
            def __init__(self, v):
                self.addresses = v.get('0', [])
                self.change = v.get('1', [])
       -        self.name = v.get('name', 'unnamed')
        
            def dump(self):
       -        return {'0':self.addresses, '1':self.change, 'name':self.name}
       -
       -    def get_name(self):
       -        return self.name
       +        return {'0':self.addresses, '1':self.change}
        
            def get_addresses(self, for_change):
                return self.change[:] if for_change else self.addresses[:]
       t@@ -171,25 +167,9 @@ class BIP32_Account(Account):
                    K, K_compressed, chain = CKD_prime(K, chain, i)
                return K_compressed.encode('hex')
        
       -    def get_private_key(self, sequence, master_k):
       -        chain = self.c
       -        k = master_k
       -        for i in sequence:
       -            k, chain = CKD(k, chain, i)
       -        return SecretToASecret(k, True)
       -
       -    def get_private_keys(self, sequence_list, seed):
       -        return [ self.get_private_key( sequence, seed) for sequence in sequence_list]
       +    def redeem_script(self, sequence):
       +        return None
        
       -    def check_seed(self, seed):
       -        master_secret, master_chain, master_public_key, master_public_key_compressed = bip32_init(seed)
       -        assert self.mpk == (master_public_key.encode('hex'), master_chain.encode('hex'))
       -
       -    def get_input_info(self, sequence):
       -        chain, i = sequence
       -        pk_addr = self.get_address(chain, i)
       -        redeemScript = None
       -        return pk_addr, redeemScript
        
        
        
       t@@ -215,18 +195,44 @@ class BIP32_Account_2of2(BIP32_Account):
                    K, K_compressed, chain = CKD_prime(K, chain, i)
                return K_compressed.encode('hex')
        
       +    def redeem_script(self, sequence):
       +        chain, i = sequence
       +        pubkey1 = self.get_pubkey(chain, i)
       +        pubkey2 = self.get_pubkey2(chain, i)
       +        return Transaction.multisig_script([pubkey1, pubkey2], 2)
       +
            def get_address(self, for_change, n):
       -        pubkey1 = self.get_pubkey(for_change, n)
       -        pubkey2 = self.get_pubkey2(for_change, n)
       -        address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
       +        address = hash_160_to_bc_address(hash_160(self.redeem_script((for_change, n)).decode('hex')), 5)
                return address
        
       -    def get_input_info(self, sequence):
       +
       +class BIP32_Account_2of3(BIP32_Account_2of2):
       +
       +    def __init__(self, v):
       +        BIP32_Account_2of2.__init__(self, v)
       +        self.c3 = v['c3'].decode('hex')
       +        self.K3 = v['K3'].decode('hex')
       +        self.cK3 = v['cK3'].decode('hex')
       +
       +    def dump(self):
       +        d = BIP32_Account_2of2.dump(self)
       +        d['c3'] = self.c3.encode('hex')
       +        d['K3'] = self.K3.encode('hex')
       +        d['cK3'] = self.cK3.encode('hex')
       +        return d
       +
       +    def get_pubkey3(self, for_change, n):
       +        K = self.K3
       +        chain = self.c3
       +        for i in [for_change, n]:
       +            K, K_compressed, chain = CKD_prime(K, chain, i)
       +        return K_compressed.encode('hex')
       +
       +    def get_redeem_script(self, sequence):
                chain, i = sequence
                pubkey1 = self.get_pubkey(chain, i)
                pubkey2 = self.get_pubkey2(chain, i)
       -        # fixme
       -        pk_addr = None # public_key_to_bc_address( pubkey1 ) # we need to return that address to get the right private key
       -        redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
       -        return pk_addr, redeemScript
       +        pubkey3 = self.get_pubkey3(chain, i)
       +        return Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 3)
       +
        
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -244,17 +244,17 @@ def is_compressed(sec):
            return len(b) == 33
        
        
       -def address_from_private_key(sec):
       +def public_key_from_private_key(sec):
            # rebuild public key from private key, compressed or uncompressed
            pkey = regenerate_key(sec)
            assert pkey
       -
       -    # figure out if private key is compressed
            compressed = is_compressed(sec)
       -        
       -    # rebuild private and public key from regenerated secret
       -    private_key = GetPrivKey(pkey, compressed)
            public_key = GetPubKey(pkey.pubkey, compressed)
       +    return public_key.encode('hex')
       +
       +
       +def address_from_private_key(sec):
       +    public_key = public_key_from_private_key(sec)
            address = public_key_to_bc_address(public_key)
            return address
        
       t@@ -448,6 +448,11 @@ def bip32_public_derivation(c, K, branch, sequence):
            return c.encode('hex'), K.encode('hex'), cK.encode('hex')
        
        
       +def bip32_private_key(sequence, k, chain):
       +    for i in sequence:
       +        k, chain = CKD(k, chain, i)
       +    return SecretToASecret(k, True)
       +
        
        
        
       t@@ -508,8 +513,7 @@ class Transaction:
                    raise
                s += 'ae'
        
       -        out = { "address": hash_160_to_bc_address(hash_160(s.decode('hex')), 5), "redeemScript":s }
       -        return out
       +        return s
        
            @classmethod
            def serialize( klass, inputs, outputs, for_sig = None ):
       t@@ -522,24 +526,24 @@ class Transaction:
                    s += int_to_hex(txin['index'],4)                         # prev index
        
                    if for_sig is None:
       -                pubkeysig = txin.get('pubkeysig')
       -                if pubkeysig:
       -                    pubkey, sig = pubkeysig[0]
       -                    sig = sig + chr(1)                               # hashtype
       -                    script  = op_push( len(sig))
       -                    script += sig.encode('hex')
       -                    script += op_push( len(pubkey))
       -                    script += pubkey.encode('hex')
       +                signatures = txin['signatures']
       +                pubkeys = txin['pubkeys']
       +                if not txin.get('redeemScript'):
       +                    pubkey = pubkeys[0]
       +                    sig = signatures[0]
       +                    sig = sig + '01'                                 # hashtype
       +                    script  = op_push(len(sig)/2)
       +                    script += sig
       +                    script += op_push(len(pubkey)/2)
       +                    script += pubkey
                        else:
       -                    signatures = txin['signatures']
       -                    pubkeys = txin['pubkeys']
                            script = '00'                                    # op_0
                            for sig in signatures:
                                sig = sig + '01'
                                script += op_push(len(sig)/2)
                                script += sig
        
       -                    redeem_script = klass.multisig_script(pubkeys,2).get('redeemScript')
       +                    redeem_script = klass.multisig_script(pubkeys,2)
                            script += op_push(len(redeem_script)/2)
                            script += redeem_script
        
       t@@ -587,79 +591,47 @@ class Transaction:
            def hash(self):
                return Hash(self.raw.decode('hex') )[::-1].encode('hex')
        
       -    def sign(self, private_keys):
       +
       +
       +    def sign(self, keypairs):
                import deserialize
       +        is_complete = True
       +        print_error("tx.sign(), keypairs:", keypairs)
        
       -        for i in range(len(self.inputs)):
       -            txin = self.inputs[i]
       -            tx_for_sig = self.serialize( self.inputs, self.outputs, for_sig = i )
       +        for i, txin in enumerate(self.inputs):
        
       +            # if the input is multisig, parse redeem script
                    redeem_script = txin.get('redeemScript')
       -            if redeem_script:
       -                # 1 parse the redeem script
       -                num, redeem_pubkeys = deserialize.parse_redeemScript(redeem_script)
       -                self.inputs[i]["pubkeys"] = redeem_pubkeys
       -
       -                # build list of public/private keys
       -                keypairs = {}
       -                for sec in private_keys.values():
       +            num, redeem_pubkeys = deserialize.parse_redeemScript(redeem_script) if redeem_script else (1, [txin.get('redeemPubkey')])
       +
       +            # add pubkeys
       +            txin["pubkeys"] = redeem_pubkeys
       +            # get list of already existing signatures
       +            signatures = txin.get("signatures",[])
       +            # continue if this txin is complete
       +            if len(signatures) == num:
       +                continue
       +
       +            tx_for_sig = self.serialize( self.inputs, self.outputs, for_sig = i )
       +            for pubkey in redeem_pubkeys:
       +                # check if we have the corresponding private key
       +                if pubkey in keypairs.keys():
       +                    # add signature
       +                    sec = keypairs[pubkey]
                            compressed = is_compressed(sec)
                            pkey = regenerate_key(sec)
       -                    pubkey = GetPubKey(pkey.pubkey, compressed)
       -                    keypairs[ pubkey.encode('hex') ] = sec
       -
       -                print "keypairs", keypairs
       -                print redeem_script, redeem_pubkeys
       -
       -                # list of already existing signatures
       -                signatures = txin.get("signatures",[])
       -                print_error("signatures",signatures)
       -
       -                for pubkey in redeem_pubkeys:
       -
       -                    # here we have compressed key.. it won't work
       -                    #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 1:
       -                        # check if we have a key corresponding to the redeem script
       -                        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') )
       -                        
       -                # for p2sh, pubkeysig is a tuple (may be incomplete)
       -                self.inputs[i]["signatures"] = signatures
       -                print_error("signatures",signatures)
       -                self.is_complete = len(signatures) == num
       -
       -            else:
       -                sec = private_keys[txin['address']]
       -                compressed = is_compressed(sec)
       -                pkey = regenerate_key(sec)
       -                secexp = pkey.secret
       -                private_key = ecdsa.SigningKey.from_secret_exponent( secexp, curve = SECP256k1 )
       -                public_key = private_key.get_verifying_key()
       -                pkey = EC_KEY(secexp)
       -                pubkey = GetPubKey(pkey.pubkey, compressed)
       -                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
       +                    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') )
       +                    print_error("adding signature for", pubkey)
       +            
       +            txin["signatures"] = signatures
       +            is_complete = is_complete and len(signatures) == num
        
       +        self.is_complete = is_complete
                self.raw = self.serialize( self.inputs, self.outputs )
        
        
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -60,7 +60,9 @@ register_command('importprivkey',        1, 1, True,  True,  'Import a private k
        register_command('listaddresses',        3, 3, False, True,  'Returns your list of addresses.', '', listaddr_options)
        register_command('listunspent',          0, 0, False, True,  'Returns a list of unspent inputs in your wallet.')
        register_command('mktx',                 5, 5, True,  True,  'Create a signed transaction', 'mktx <recipient> <amount> [label]', payto_options)
       +register_command('mksendmanytx',         4, 4, True,  True,  'Create a signed transaction', 'mksendmanytx <recipient> <amount> [<recipient> <amount> ...]', payto_options)
        register_command('payto',                5, 5, True,  False, 'Create and broadcast a transaction.', "payto <recipient> <amount> [label]\n<recipient> can be a bitcoin address or a label", payto_options)
       +register_command('paytomany',            4, 4, True,  False, 'Create and broadcast a transaction.', "paytomany <recipient> <amount> [<recipient> <amount> ...]\n<recipient> can be a bitcoin address or a label", payto_options)
        register_command('password',             0, 0, True,  True,  'Change your password')
        register_command('prioritize',           1, 1, False, True,  'Coins at prioritized addresses are spent first.', 'prioritize <address>')
        register_command('restore',              0, 0, False, False, 'Restore a wallet', '', restore_options)
       t@@ -131,7 +133,9 @@ class Commands:
        
            def createmultisig(self, num, pubkeys):
                assert isinstance(pubkeys, list)
       -        return Transaction.multisig_script(pubkeys, num)
       +        redeem_script = Transaction.multisig_script(pubkeys, num)
       +        address = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
       +        return {'address':address, 'redeemScript':redeem_script}
            
            def freeze(self,addr):
                return self.wallet.freeze(addr)
       t@@ -205,10 +209,11 @@ class Commands:
                return self.wallet.verify_message(address, signature, message)
        
        
       -    def _mktx(self, to_address, amount, fee = None, change_addr = None, domain = None):
       +    def _mktx(self, outputs, fee = None, change_addr = None, domain = None):
        
       -        if not is_valid(to_address):
       -            raise BaseException("Invalid Bitcoin address", to_address)
       +        for to_address, amount in outputs:
       +            if not is_valid(to_address):
       +                raise BaseException("Invalid Bitcoin address", to_address)
        
                if change_addr:
                    if not is_valid(change_addr):
       t@@ -223,25 +228,40 @@ class Commands:
                            raise BaseException("address not in wallet", addr)
        
                for k, v in self.wallet.labels.items():
       -            if v == to_address:
       -                to_address = k
       -                print_msg("alias", to_address)
       -                break
                    if change_addr and v == change_addr:
                        change_addr = k
        
       -        amount = int(100000000*amount)
       +        final_outputs = []
       +        for to_address, amount in outputs:
       +            for k, v in self.wallet.labels.items():
       +                if v == to_address:
       +                    to_address = k
       +                    print_msg("alias", to_address)
       +                    break
       +
       +            amount = int(100000000*amount)
       +            final_outputs.append((to_address, amount))
       +            
                if fee: fee = int(100000000*fee)
       -        return self.wallet.mktx( [(to_address, amount)], self.password, fee , change_addr, domain)
       +        return self.wallet.mktx(final_outputs, self.password, fee , change_addr, domain)
        
        
            def mktx(self, to_address, amount, fee = None, change_addr = None, domain = None):
       -        tx = self._mktx(to_address, amount, fee, change_addr, domain)
       +        tx = self._mktx([(to_address, amount)], fee, change_addr, domain)
       +        return tx.as_dict()
       +
       +    def mksendmanytx(self, outputs, fee = None, change_addr = None, domain = None):
       +        tx = self._mktx(outputs, fee, change_addr, domain)
                return tx.as_dict()
        
        
            def payto(self, to_address, amount, fee = None, change_addr = None, domain = None):
       -        tx = self._mktx(to_address, amount, fee, change_addr, domain)
       +        tx = self._mktx([(to_address, amount)], fee, change_addr, domain)
       +        r, h = self.wallet.sendtx( tx )
       +        return h
       +
       +    def paytomany(self, outputs, fee = None, change_addr = None, domain = None):
       +        tx = self._mktx(outputs, fee, change_addr, domain)
                r, h = self.wallet.sendtx( tx )
                return h
        
   DIR diff --git a/lib/deserialize.py b/lib/deserialize.py
       t@@ -346,8 +346,8 @@ def get_address_from_input_script(bytes):
        
                redeemScript = decoded[-1][1]
                num = len(match) - 2
       -        signatures = map(lambda x:x[1].encode('hex'), decoded[1:-1])
       -        
       +        signatures = map(lambda x:x[1][:-1].encode('hex'), decoded[1:-1])
       +
                dec2 = [ x for x in script_GetOp(redeemScript) ]
        
                # 2 of 2
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -74,7 +74,7 @@ class Wallet:
                self.seed_version          = config.get('seed_version', SEED_VERSION)
                self.gap_limit             = config.get('gap_limit', 5)
                self.use_change            = config.get('use_change',True)
       -        self.fee                   = int(config.get('fee_per_kb',50000))
       +        self.fee                   = int(config.get('fee_per_kb',20000))
                self.num_zeros             = int(config.get('num_zeros',0))
                self.use_encryption        = config.get('use_encryption', False)
                self.seed                  = config.get('seed', '')               # encrypted
       t@@ -172,62 +172,112 @@ class Wallet:
        
                master_k, master_c, master_K, master_cK = bip32_init(self.seed)
                
       +        # normal accounts
                k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
       +        # p2sh 2of2
                k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/")
                k2, c2, K2, cK2 = bip32_private_derivation(master_k, master_c, "m/", "m/2'/")
       +        # p2sh 2of3
       +        k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/")
       +        k4, c4, K4, cK4 = bip32_private_derivation(master_k, master_c, "m/", "m/4'/")
       +        k5, c5, K5, cK5 = bip32_private_derivation(master_k, master_c, "m/", "m/5'/")
        
                self.master_public_keys = {
                    "m/0'/": (c0, K0, cK0),
                    "m/1'/": (c1, K1, cK1),
       -            "m/2'/": (c2, K2, cK2)
       +            "m/2'/": (c2, K2, cK2),
       +            "m/3'/": (c3, K3, cK3),
       +            "m/4'/": (c4, K4, cK4),
       +            "m/5'/": (c5, K5, cK5)
                    }
                
                self.master_private_keys = {
                    "m/0'/": k0,
       -            "m/1'/": k1
       +            "m/1'/": k1,
       +            "m/2'/": k2,
       +            "m/3'/": k3,
       +            "m/4'/": k4,
       +            "m/5'/": k5
                    }
       -        # send k2 to service
                
                self.config.set_key('master_public_keys', self.master_public_keys, True)
                self.config.set_key('master_private_keys', self.master_private_keys, True)
        
                # create default account
       -        self.create_new_account('Main account', None)
       +        self.create_account('Main account')
        
        
       -    def create_new_account(self, name, password):
       -        keys = self.accounts.keys()
       -        i = 0
       +    def find_root_by_master_key(self, c, K):
       +        for key, v in self.master_public_keys.items():
       +            if key == "m/":continue
       +            cc, KK, _ = v
       +            if (c == cc) and (K == KK):
       +                return key
        
       -        while True:
       -            derivation = "m/0'/%d'"%i
       -            if derivation not in keys: break
       -            i += 1
       +    def deseed_root(self, seed, password):
       +        # for safety, we ask the user to enter their seed
       +        assert seed == self.decode_seed(password)
       +        self.seed = ''
       +        self.config.set_key('seed', '', True)
       +
       +
       +    def deseed_branch(self, k):
       +        # check that parent has no seed
       +        assert self.seed == ''
       +        self.master_private_keys.pop(k)
       +        self.config.set_key('master_private_keys', self.master_private_keys, True)
        
       -        start = "m/0'/"
       -        master_k = self.get_master_private_key(start, password )
       -        master_c, master_K, master_cK = self.master_public_keys[start]
       -        k, c, K, cK = bip32_private_derivation(master_k, master_c, start, derivation)
       -        
       -        self.accounts[derivation] = BIP32_Account({ 'name':name, 'c':c, 'K':K, 'cK':cK })
       -        self.save_accounts()
        
       -    def create_p2sh_account(self, name):
       +    def account_id(self, account_type, i):
       +        if account_type is None:
       +            return "m/0'/%d"%i
       +        elif account_type == '2of2':
       +            return "m/1'/%d & m/2'/%d"%(i,i)
       +        elif account_type == '2of3':
       +            return "m/3'/%d & m/4'/%d & m/5'/%d"%(i,i,i)
       +        else:
       +            raise BaseException('unknown account type')
       +
       +
       +    def num_accounts(self, account_type):
                keys = self.accounts.keys()
                i = 0
                while True:
       -            account_id = "m/1'/%d & m/2'/%d"%(i,i)
       +            account_id = self.account_id(account_type, i)
                    if account_id not in keys: break
                    i += 1
       -
       -        master_c1, master_K1, _ = self.master_public_keys["m/1'/"]
       -        c1, K1, cK1 = bip32_public_derivation(master_c1.decode('hex'), master_K1.decode('hex'), "m/1'/", "m/1'/%d"%i)
       -        
       -        master_c2, master_K2, _ = self.master_public_keys["m/2'/"]
       -        c2, K2, cK2 = bip32_public_derivation(master_c2.decode('hex'), master_K2.decode('hex'), "m/2'/", "m/2'/%d"%i)
       -        
       -        self.accounts[account_id] = BIP32_Account_2of2({ 'name':name, 'c':c1, 'K':K1, 'cK':cK1, 'c2':c2, 'K2':K2, 'cK2':cK2 })
       +        return i
       +
       +
       +    def create_account(self, name, account_type = None):
       +        i = self.num_accounts(account_type)
       +        account_id = self.account_id(account_type,i)
       +
       +        if account_type is None:
       +            master_c0, master_K0, _ = self.master_public_keys["m/0'/"]
       +            c0, K0, cK0 = bip32_public_derivation(master_c0.decode('hex'), master_K0.decode('hex'), "m/0'/", "m/0'/%d"%i)
       +            account = BIP32_Account({ 'c':c0, 'K':K0, 'cK':cK0 })
       +
       +        elif account_type == '2of2':
       +            master_c1, master_K1, _ = self.master_public_keys["m/1'/"]
       +            c1, K1, cK1 = bip32_public_derivation(master_c1.decode('hex'), master_K1.decode('hex'), "m/1'/", "m/1'/%d"%i)
       +            master_c2, master_K2, _ = self.master_public_keys["m/2'/"]
       +            c2, K2, cK2 = bip32_public_derivation(master_c2.decode('hex'), master_K2.decode('hex'), "m/2'/", "m/2'/%d"%i)
       +            account = BIP32_Account_2of2({ 'c':c1, 'K':K1, 'cK':cK1, 'c2':c2, 'K2':K2, 'cK2':cK2 })
       +
       +        elif account_type == '2of3':
       +            master_c3, master_K3, _ = self.master_public_keys["m/3'/"]
       +            c3, K3, cK3 = bip32_public_derivation(master_c3.decode('hex'), master_K3.decode('hex'), "m/3'/", "m/3'/%d"%i)
       +            master_c4, master_K4, _ = self.master_public_keys["m/4'/"]
       +            c4, K4, cK4 = bip32_public_derivation(master_c4.decode('hex'), master_K4.decode('hex'), "m/4'/", "m/4'/%d"%i)
       +            master_c5, master_K5, _ = self.master_public_keys["m/5'/"]
       +            c5, K5, cK5 = bip32_public_derivation(master_c5.decode('hex'), master_K5.decode('hex'), "m/5'/", "m/5'/%d"%i)
       +            account = BIP32_Account_2of3({ 'c':c3, 'K':K3, 'cK':cK3, 'c2':c4, 'K2':K4, 'cK2':cK4, 'c3':c5, 'K3':K5, 'cK3':cK5 })
       +
       +        self.accounts[account_id] = account
                self.save_accounts()
       +        self.labels[account_id] = name
       +        self.config.set_key('labels', self.labels, True)
        
        
            def save_accounts(self):
       t@@ -283,15 +333,39 @@ class Wallet:
            def get_address_index(self, address):
                if address in self.imported_keys.keys():
                    return -1, None
       +
                for account in self.accounts.keys():
                    for for_change in [0,1]:
                        addresses = self.accounts[account].get_addresses(for_change)
                        for addr in addresses:
                            if address == addr:
                                return account, (for_change, addresses.index(addr))
       +
                raise BaseException("not found")
       +
       +
       +    def rebase_sequence(self, account, sequence):
       +        c, i = sequence
       +        dd = []
       +        for a in account.split('&'):
       +            s = a.strip()
       +            m = re.match("(m/\d+'/)(\d+)", s)
       +            root = m.group(1)
       +            num = int(m.group(2))
       +            dd.append( (root, [num,c,i] ) )
       +        return dd
                
        
       +    def get_keyID(self, account, sequence):
       +        rs = self.rebase_sequence(account, sequence)
       +        dd = []
       +        for root, public_sequence in rs:
       +            c, K, _ = self.master_public_keys[root]
       +            s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
       +            dd.append( 'bip32(%s,%s,%s)'%(c,K, s) )
       +        return '&'.join(dd)
       +
       +
            def get_public_key(self, address):
                account, sequence = self.get_address_index(address)
                return self.accounts[account].get_pubkey( *sequence )
       t@@ -304,50 +378,37 @@ class Wallet:
                
        
            def get_private_key(self, address, password):
       +        out = []
                if address in self.imported_keys.keys():
       -            return pw_decode( self.imported_keys[address], password )
       +            out.append( pw_decode( self.imported_keys[address], password ) )
                else:
                    account, sequence = self.get_address_index(address)
       -            m = re.match("m/0'/(\d+)'", account)
       -            if m:
       -                num = int(m.group(1))
       -                master_k = self.get_master_private_key("m/0'/", password)
       -                master_c, _, _ = self.master_public_keys["m/0'/"]
       -                master_k, master_c = CKD(master_k, master_c, num + BIP32_PRIME)
       -                return self.accounts[account].get_private_key(sequence, master_k)
       -                
       -            m2 = re.match("m/1'/(\d+) & m/2'/(\d+)", account)
       -            if m2:
       -                num = int(m2.group(1))
       -                master_k = self.get_master_private_key("m/1'/", password)
       -                master_c, master_K, _ = self.master_public_keys["m/1'/"]
       -                master_k, master_c = CKD(master_k.decode('hex'), master_c.decode('hex'), num)
       -                return self.accounts[account].get_private_key(sequence, master_k)
       -        return
       -
       -
       -    def get_private_keys(self, addresses, password):
       -        if not self.seed: return {}
       -        # decode seed in any case, in order to test the password
       -        seed = self.decode_seed(password)
       -        out = {}
       -        for address in addresses:
       -            pk = self.get_private_key(address, password)
       -            if pk: out[address] = pk
       -
       +            # assert address == self.accounts[account].get_address(*sequence)
       +            rs = self.rebase_sequence( account, sequence)
       +            for root, public_sequence in rs:
       +
       +                if root not in self.master_private_keys.keys(): continue
       +                master_k = self.get_master_private_key(root, password)
       +                master_c, _, _ = self.master_public_keys[root]
       +                pk = bip32_private_key( public_sequence, master_k.decode('hex'), master_c.decode('hex'))
       +                out.append(pk)
       +                    
                return out
        
        
       +
       +
            def signrawtransaction(self, tx, input_info, private_keys, password):
       +        import deserialize
                unspent_coins = self.get_unspent_coins()
                seed = self.decode_seed(password)
        
       -        # convert private_keys to dict 
       -        pk = {}
       +        # build a list of public/private keys
       +        keypairs = {}
                for sec in private_keys:
       -            address = address_from_private_key(sec)
       -            pk[address] = sec
       -        private_keys = pk
       +            pubkey = public_key_from_private_key(sec)
       +            keypairs[ pubkey ] = sec
       +
        
                for txin in tx.inputs:
                    # convert to own format
       t@@ -363,33 +424,61 @@ class Wallet:
                    else:
                        for item in unspent_coins:
                            if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
       +                        print_error( "tx input is in unspent coins" )
                                txin['raw_output_script'] = item['raw_output_script']
       +                        account, sequence = self.get_address_index(item['address'])
       +                        if account != -1:
       +                            txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
                                break
                        else:
       -                    # if neither, we might want to get it from the server..
       -                    raise
       -
       -            # find the address:
       -            if txin.get('KeyID'):
       -                account, name, sequence = txin.get('KeyID')
       -                if name != 'Electrum': continue
       -                sec = self.accounts[account].get_private_key(sequence, seed)
       -                addr = self.accounts[account].get_address(sequence)
       +                    raise BaseException("Unknown transaction input. Please provide the 'input_info' parameter, or synchronize this wallet")
       +
       +            # if available, derive private_keys from KeyID
       +            keyid = txin.get('KeyID')
       +            if keyid:
       +                roots = []
       +                for s in keyid.split('&'):
       +                    m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
       +                    if not m: continue
       +                    c = m.group(1)
       +                    K = m.group(2)
       +                    sequence = m.group(3)
       +                    root = self.find_root_by_master_key(c,K)
       +                    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
       -                private_keys[addr] = sec
       +                pk = self.get_private_key(addr, password)
       +                for sec in pk:
       +                    pubkey = public_key_from_private_key(sec)
       +                    keypairs[pubkey] = sec
       +
       +            redeem_script = txin.get("redeemScript")
       +            print_error( "p2sh:", "yes" if redeem_script else "no")
       +            if redeem_script:
       +                addr = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
       +            else:
       +                addr = deserialize.get_address_from_output_script(txin["raw_output_script"].decode('hex'))
       +            txin['address'] = addr
        
       -            elif txin.get("redeemScript"):
       -                txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
       +            # add private keys that are in the wallet
       +            pk = self.get_private_key(addr, password)
       +            for sec in pk:
       +                pubkey = public_key_from_private_key(sec)
       +                keypairs[pubkey] = sec
       +                if not redeem_script:
       +                    txin['redeemPubkey'] = pubkey
        
       -            elif txin.get("raw_output_script"):
       -                import deserialize
       -                addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
       -                sec = self.get_private_key(addr, password)
       -                if sec: 
       -                    private_keys[addr] = sec
       -                    txin['address'] = addr
       +            print txin
        
       -        tx.sign( private_keys )
       +        tx.sign( keypairs )
        
            def sign_message(self, address, message, password):
                sec = self.get_private_key(address, password)
       t@@ -513,7 +602,7 @@ class Wallet:
                self.config.set_key('contacts', self.addressbook, True)
                if label:  
                    self.labels[address] = label
       -            self.config.set_key('labels', self.labels)
       +            self.config.set_key('labels', self.labels, True)
        
            def delete_contact(self, addr):
                if addr in self.addressbook:
       t@@ -606,7 +695,7 @@ class Wallet:
            def get_accounts(self):
                accounts = {}
                for k, account in self.accounts.items():
       -            accounts[k] = account.name
       +            accounts[k] = self.labels.get(k, 'unnamed')
                if self.imported_keys:
                    accounts[-1] = 'Imported keys'
                return accounts
       t@@ -873,13 +962,6 @@ class Wallet:
        
        
            def mktx(self, outputs, password, fee=None, change_addr=None, account=None ):
       -        """
       -        create a transaction
       -        account parameter:
       -           None means use all accounts
       -           -1 means imported keys
       -           0, 1, etc are seed accounts
       -        """
                
                for address, x in outputs:
                    assert is_valid(address)
       t@@ -891,33 +973,28 @@ class Wallet:
                    raise ValueError("Not enough funds")
        
                outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account)
       -
                tx = Transaction.from_io(inputs, outputs)
        
       -        pk_addresses = []
       -        for i in range(len(tx.inputs)):
       -            txin = tx.inputs[i]
       +        keypairs = {}
       +        for i, txin in enumerate(tx.inputs):
                    address = txin['address']
       -            if address in self.imported_keys.keys():
       -                pk_addresses.append(address)
       -                continue
       -            account, sequence = self.get_address_index(address)
        
       -            txin['KeyID'] = (account, 'BIP32', sequence) # used by the server to find the key
       +            account, sequence = self.get_address_index(address)
       +            txin['KeyID'] = self.get_keyID(account, sequence)
        
       -            _, redeemScript = self.accounts[account].get_input_info(sequence)
       -            
       -            if redeemScript: txin['redeemScript'] = redeemScript
       -            pk_addresses.append(address)
       +            redeemScript = self.accounts[account].redeem_script(sequence)
       +            if redeemScript: 
       +                txin['redeemScript'] = redeemScript
       +            else:
       +                txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
        
       -        print "pk_addresses", pk_addresses
       +            private_keys = self.get_private_key(address, password)
        
       -        # get all private keys at once.
       -        if self.seed:
       -            private_keys = self.get_private_keys(pk_addresses, password)
       -            print "private keys", private_keys
       -            tx.sign(private_keys)
       +            for sec in private_keys:
       +                pubkey = public_key_from_private_key(sec)
       +                keypairs[ pubkey ] = sec
        
       +        tx.sign(keypairs)
                for address, x in outputs:
                    if address not in self.addressbook and not self.is_mine(address):
                        self.addressbook.append(address)