Wallet encryption part 2: ask passphrase when needed, add menu options
authorWladimir J. van der Laan <laanwj@gmail.com>
Wed, 24 Aug 2011 20:07:26 +0000 (22:07 +0200)
committerWladimir J. van der Laan <laanwj@gmail.com>
Wed, 31 Aug 2011 12:19:43 +0000 (14:19 +0200)
16 files changed:
bitcoin-qt.pro
doc/assets-attribution.txt
src/qt/addresstablemodel.cpp
src/qt/addresstablemodel.h
src/qt/askpassphrasedialog.cpp [new file with mode: 0644]
src/qt/askpassphrasedialog.h [new file with mode: 0644]
src/qt/bitcoin.qrc
src/qt/bitcoingui.cpp
src/qt/bitcoingui.h
src/qt/editaddressdialog.cpp
src/qt/forms/askpassphrasedialog.ui [new file with mode: 0644]
src/qt/guiconstants.h
src/qt/res/icons/key.png [new file with mode: 0644]
src/qt/sendcoinsdialog.cpp
src/qt/walletmodel.cpp
src/qt/walletmodel.h

index e3dea66..28c5a33 100644 (file)
@@ -90,7 +90,8 @@ HEADERS += src/qt/bitcoingui.h \
     src/qt/sendcoinsentry.h \
     src/qt/qvalidatedlineedit.h \
     src/qt/bitcoinunits.h \
-    src/qt/qvaluecombobox.h
+    src/qt/qvaluecombobox.h \
+    src/qt/askpassphrasedialog.h
 SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
     src/qt/transactiontablemodel.cpp \
     src/qt/addresstablemodel.cpp \
@@ -134,7 +135,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \
     src/qt/sendcoinsentry.cpp \
     src/qt/qvalidatedlineedit.cpp \
     src/qt/bitcoinunits.cpp \
-    src/qt/qvaluecombobox.cpp
+    src/qt/qvaluecombobox.cpp \
+    src/qt/askpassphrasedialog.cpp
 
 RESOURCES += \
     src/qt/bitcoin.qrc
@@ -146,7 +148,8 @@ FORMS += \
     src/qt/forms/editaddressdialog.ui \
     src/qt/forms/transactiondescdialog.ui \
     src/qt/forms/overviewpage.ui \
-    src/qt/forms/sendcoinsentry.ui
+    src/qt/forms/sendcoinsentry.ui \
+    src/qt/forms/askpassphrasedialog.ui
 
 CODECFORTR = UTF-8
 # for lrelease/lupdate
index d498e8b..91d2e65 100644 (file)
@@ -64,5 +64,10 @@ Designer: Crobbo (forum)
 Site: https://bitcointalk.org/index.php?topic=32273.0
 License: Public domain
 
+Icon: src/qt/res/icons/key.png
+Designer: VisualPharm (Ivan Boyko)
+Icon Pack: Must Have
+Site: http://findicons.com/icon/51009/key?id=51009
+License: Creative Commons Attribution (by)
 
 
index bd314ba..6bda1e7 100644 (file)
@@ -267,6 +267,14 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
     else if(type == Receive)
     {
         // Generate a new address to associate with given label
+        WalletModel::UnlockContext ctx(walletModel->requestUnlock());
+        if(!ctx.isValid())
+        {
+            // Unlock wallet failed or was cancelled
+            editStatus = WALLET_UNLOCK_FAILURE;
+            return QString();
+        }
+
         strAddress = CBitcoinAddress(wallet->GetOrReuseKeyFromPool()).ToString();
     }
     else
index 296fa58..bc505c4 100644 (file)
@@ -26,9 +26,10 @@ public:
 
     // Return status of last edit/insert operation
     enum EditStatus {
-        OK = 0,
-        INVALID_ADDRESS = 1,
-        DUPLICATE_ADDRESS = 2
+        OK,
+        INVALID_ADDRESS,
+        DUPLICATE_ADDRESS,
+        WALLET_UNLOCK_FAILURE
     };
 
     static const QString Send; /* Send addres */
diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp
new file mode 100644 (file)
index 0000000..a297513
--- /dev/null
@@ -0,0 +1,186 @@
+#include "askpassphrasedialog.h"
+#include "ui_askpassphrasedialog.h"
+
+#include "guiconstants.h"
+#include "walletmodel.h"
+
+#include <QMessageBox>
+#include <QPushButton>
+
+AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget *parent) :
+    QDialog(parent),
+    ui(new Ui::AskPassphraseDialog),
+    mode(mode),
+    model(0)
+{
+    ui->setupUi(this);
+    ui->passEdit1->setMaxLength(MAX_PASSPHRASE_SIZE);
+    ui->passEdit2->setMaxLength(MAX_PASSPHRASE_SIZE);
+    ui->passEdit3->setMaxLength(MAX_PASSPHRASE_SIZE);
+
+    switch(mode)
+    {
+        case Encrypt: // Ask passphrase x2
+            ui->passLabel1->hide();
+            ui->passEdit1->hide();
+            ui->warningLabel->setText(tr("Enter the new passphrase to the wallet.<br/>Please use a passphrase of <b>10 or more random characters</b>, or <b>eight or more words</b>."));
+            setWindowTitle(tr("Encrypt wallet"));
+            break;
+        case Unlock: // Ask passphrase
+            ui->warningLabel->setText(tr("This operation needs your wallet passphrase to unlock the wallet."));
+            ui->passLabel2->hide();
+            ui->passEdit2->hide();
+            ui->passLabel3->hide();
+            ui->passEdit3->hide();
+            setWindowTitle(tr("Unlock wallet"));
+            break;
+        case Decrypt:   // Ask passphrase
+            ui->warningLabel->setText(tr("This operation needs your wallet passphrase to decrypt the wallet."));
+            ui->passLabel2->hide();
+            ui->passEdit2->hide();
+            ui->passLabel3->hide();
+            ui->passEdit3->hide();
+            setWindowTitle(tr("Decrypt wallet"));
+            break;
+        case ChangePass: // Ask old passphrase + new passphrase x2
+            setWindowTitle(tr("Change passphrase"));
+            ui->warningLabel->setText(tr("Enter the old and new passphrase to the wallet."));
+            break;
+    }
+    resize(minimumSize()); // Get rid of extra space in dialog
+
+    textChanged();
+    connect(ui->passEdit1, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
+    connect(ui->passEdit2, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
+    connect(ui->passEdit3, SIGNAL(textChanged(QString)), this, SLOT(textChanged()));
+}
+
+AskPassphraseDialog::~AskPassphraseDialog()
+{
+    // Attempt to overwrite text so that they do not linger around in memory
+    ui->passEdit1->setText(QString(" ").repeated(ui->passEdit1->text().size()));
+    ui->passEdit2->setText(QString(" ").repeated(ui->passEdit2->text().size()));
+    ui->passEdit3->setText(QString(" ").repeated(ui->passEdit3->text().size()));
+    delete ui;
+}
+
+void AskPassphraseDialog::setModel(WalletModel *model)
+{
+    this->model = model;
+}
+
+void AskPassphraseDialog::accept()
+{
+    std::string oldpass, newpass1, newpass2;
+    // TODO: mlock memory / munlock on return so they will not be swapped out, really need "mlockedstring" wrapper class to do this safely
+    oldpass.reserve(MAX_PASSPHRASE_SIZE);
+    newpass1.reserve(MAX_PASSPHRASE_SIZE);
+    newpass2.reserve(MAX_PASSPHRASE_SIZE);
+    oldpass.assign(ui->passEdit1->text().toStdString());
+    newpass1.assign(ui->passEdit2->text().toStdString());
+    newpass2.assign(ui->passEdit3->text().toStdString());
+
+    switch(mode)
+    {
+    case Encrypt: {
+        if(newpass1.empty() || newpass2.empty())
+        {
+            // Cannot encrypt with empty passphrase
+            break;
+        }
+        QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm wallet encryption"),
+                 tr("WARNING: If you encrypt your wallet and lose your passphrase, you will <b>LOSE ALL OF YOUR BITCOINS</b>!\nAre you sure you wish to encrypt your wallet?"),
+                 QMessageBox::Yes|QMessageBox::Cancel,
+                 QMessageBox::Cancel);
+        if(retval == QMessageBox::Yes)
+        {
+            if(newpass1 == newpass2)
+            {
+                if(model->setWalletEncrypted(true, newpass1))
+                {
+                    QMessageBox::warning(this, tr("Wallet encrypted"),
+                                         tr("Remember that encrypting your wallet cannot fully protect your bitcoins from being stolen by malware infecting your computer."));
+                }
+                else
+                {
+                    QMessageBox::critical(this, tr("Wallet encryption failed"),
+                                         tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted."));
+                }
+                QDialog::accept(); // Success
+            }
+            else
+            {
+                QMessageBox::critical(this, tr("Wallet encryption failed"),
+                                     tr("The supplied passphrases do not match."));
+            }
+        }
+        else
+        {
+            QDialog::reject(); // Cancelled
+        }
+        } break;
+    case Unlock:
+        if(!model->setWalletLocked(false, oldpass))
+        {
+            QMessageBox::critical(this, tr("Wallet unlock failed"),
+                                  tr("The passphrase entered for the wallet decryption was incorrect."));
+        }
+        else
+        {
+            QDialog::accept(); // Success
+        }
+        break;
+    case Decrypt:
+        if(!model->setWalletEncrypted(false, oldpass))
+        {
+            QMessageBox::critical(this, tr("Wallet decryption failed"),
+                                  tr("The passphrase entered for the wallet decryption was incorrect."));
+        }
+        else
+        {
+            QDialog::accept(); // Success
+        }
+        break;
+    case ChangePass:
+        if(newpass1 == newpass2)
+        {
+            if(model->changePassphrase(oldpass, newpass1))
+            {
+                QMessageBox::information(this, tr("Wallet encrypted"),
+                                     tr("Wallet passphrase was succesfully changed."));
+                QDialog::accept(); // Success
+            }
+            else
+            {
+                QMessageBox::critical(this, tr("Wallet encryption failed"),
+                                     tr("The passphrase entered for the wallet decryption was incorrect."));
+            }
+        }
+        else
+        {
+            QMessageBox::critical(this, tr("Wallet encryption failed"),
+                                 tr("The supplied passphrases do not match."));
+        }
+        break;
+    }
+}
+
+void AskPassphraseDialog::textChanged()
+{
+    // Validate input, set Ok button to enabled when accepable
+    bool acceptable = false;
+    switch(mode)
+    {
+    case Encrypt: // New passphrase x2
+        acceptable = !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty();
+        break;
+    case Unlock: // Old passphrase x1
+    case Decrypt:
+        acceptable = !ui->passEdit1->text().isEmpty();
+        break;
+    case ChangePass: // Old passphrase x1, new passphrase x2
+        acceptable = !ui->passEdit1->text().isEmpty() && !ui->passEdit2->text().isEmpty() && !ui->passEdit3->text().isEmpty();
+        break;
+    }
+    ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(acceptable);
+}
diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h
new file mode 100644 (file)
index 0000000..761612c
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef ASKPASSPHRASEDIALOG_H
+#define ASKPASSPHRASEDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+    class AskPassphraseDialog;
+}
+
+class WalletModel;
+
+class AskPassphraseDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    enum Mode {
+        Encrypt,    // Ask passphrase x2
+        Unlock,     // Ask passphrase
+        ChangePass, // Ask old passphrase + new passphrase x2
+        Decrypt   // Ask passphrase
+    };
+
+    explicit AskPassphraseDialog(Mode mode, QWidget *parent = 0);
+    ~AskPassphraseDialog();
+
+    void accept();
+
+    void setModel(WalletModel *model);
+
+private:
+    Ui::AskPassphraseDialog *ui;
+    Mode mode;
+    WalletModel *model;
+
+private slots:
+    void textChanged();
+};
+
+#endif // ASKPASSPHRASEDIALOG_H
index 1d5a58a..be0e4dc 100644 (file)
@@ -36,6 +36,7 @@
         <file alias="tx_inout">res/icons/tx_inout.png</file>
         <file alias="lock_closed">res/icons/lock_closed.png</file>
         <file alias="lock_open">res/icons/lock_open.png</file>
+        <file alias="key">res/icons/key.png</file>
     </qresource>
     <qresource prefix="/images">
         <file alias="about">res/images/about.png</file>
index 6aa14dc..0c2eaab 100644 (file)
@@ -19,6 +19,7 @@
 #include "overviewpage.h"
 #include "bitcoinunits.h"
 #include "guiconstants.h"
+#include "askpassphrasedialog.h"
 
 #include <QApplication>
 #include <QMainWindow>
@@ -48,6 +49,8 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
     QMainWindow(parent),
     clientModel(0),
     walletModel(0),
+    encryptWalletAction(0),
+    changePassphraseAction(0),
     trayIcon(0)
 {
     resize(850, 550);
@@ -66,6 +69,9 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
     file->addAction(quitAction);
     
     QMenu *settings = menuBar()->addMenu(tr("&Settings"));
+    settings->addAction(encryptWalletAction);
+    settings->addAction(changePassphraseAction);
+    settings->addSeparator();
     settings->addAction(optionsAction);
 
     QMenu *help = menuBar()->addMenu(tr("&Help"));
@@ -199,11 +205,18 @@ void BitcoinGUI::createActions()
     openBitcoinAction->setToolTip(tr("Show the Bitcoin window"));
     exportAction = new QAction(QIcon(":/icons/export"), tr("&Export..."), this);
     exportAction->setToolTip(tr("Export the current view to a file"));
+    encryptWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Encrypt Wallet"), this);
+    encryptWalletAction->setToolTip(tr("Encrypt or decrypt wallet"));
+    encryptWalletAction->setCheckable(true);
+    changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase"), this);
+    changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption"));
 
     connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
     connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
     connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
     connect(openBitcoinAction, SIGNAL(triggered()), this, SLOT(show()));
+    connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
+    connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
 }
 
 void BitcoinGUI::setClientModel(ClientModel *clientModel)
@@ -254,6 +267,9 @@ void BitcoinGUI::setWalletModel(WalletModel *walletModel)
     // Balloon popup for new transaction
     connect(walletModel->getTransactionTableModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
             this, SLOT(incomingTransaction(QModelIndex,int,int)));
+
+    // Ask for passphrase if needed
+    connect(walletModel, SIGNAL(requireUnlock()), this, SLOT(unlockWallet()));
 }
 
 void BitcoinGUI::createTrayIcon()
@@ -544,16 +560,53 @@ void BitcoinGUI::setEncryptionStatus(int status)
     {
     case WalletModel::Unencrypted:
         labelEncryptionIcon->hide();
+        encryptWalletAction->setChecked(false);
+        changePassphraseAction->setEnabled(false);
+        encryptWalletAction->setEnabled(true);
         break;
     case WalletModel::Unlocked:
         labelEncryptionIcon->show();
         labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_open").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
         labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
+        encryptWalletAction->setChecked(true);
+        changePassphraseAction->setEnabled(true);
+        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
         break;
     case WalletModel::Locked:
         labelEncryptionIcon->show();
         labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_closed").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
         labelEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
+        encryptWalletAction->setChecked(true);
+        changePassphraseAction->setEnabled(true);
+        encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported
         break;
     }
 }
+
+void BitcoinGUI::encryptWallet(bool status)
+{
+    AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt:
+                                     AskPassphraseDialog::Decrypt, this);
+    dlg.setModel(walletModel);
+    dlg.exec();
+
+    setEncryptionStatus(walletModel->getEncryptionStatus());
+}
+
+void BitcoinGUI::changePassphrase()
+{
+    AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this);
+    dlg.setModel(walletModel);
+    dlg.exec();
+}
+
+void BitcoinGUI::unlockWallet()
+{
+    // Unlock wallet if needed
+    if(walletModel->getEncryptionStatus() == WalletModel::Locked)
+    {
+        AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this);
+        dlg.setModel(walletModel);
+        dlg.exec();
+    }
+}
index 4b71317..484987c 100644 (file)
@@ -73,6 +73,8 @@ private:
     QAction *optionsAction;
     QAction *openBitcoinAction;
     QAction *exportAction;
+    QAction *encryptWalletAction;
+    QAction *changePassphraseAction;
 
     QSystemTrayIcon *trayIcon;
     TransactionView *transactionView;
@@ -108,6 +110,9 @@ private slots:
     void aboutClicked();
     void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
     void incomingTransaction(const QModelIndex & parent, int start, int end);
+    void encryptWallet(bool status);
+    void changePassphrase();
+    void unlockWallet();
 };
 
 #endif
index 2b3d9bf..06e74db 100644 (file)
@@ -92,6 +92,11 @@ void EditAddressDialog::accept()
                 tr("The entered address \"%1\" is not a valid bitcoin address.").arg(ui->addressEdit->text()),
                 QMessageBox::Ok, QMessageBox::Ok);
             return;
+        case AddressTableModel::WALLET_UNLOCK_FAILURE:
+            QMessageBox::critical(this, windowTitle(),
+                tr("Could not unlock wallet."),
+                QMessageBox::Ok, QMessageBox::Ok);
+            return;
         }
 
         return;
diff --git a/src/qt/forms/askpassphrasedialog.ui b/src/qt/forms/askpassphrasedialog.ui
new file mode 100644 (file)
index 0000000..70d9180
--- /dev/null
@@ -0,0 +1,148 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AskPassphraseDialog</class>
+ <widget class="QDialog" name="AskPassphraseDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>589</width>
+    <height>228</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>550</width>
+    <height>0</height>
+   </size>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="warningLabel">
+     <property name="text">
+      <string>TextLabel</string>
+     </property>
+     <property name="textFormat">
+      <enum>Qt::RichText</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <property name="fieldGrowthPolicy">
+      <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+     </property>
+     <item row="1" column="0">
+      <widget class="QLabel" name="passLabel1">
+       <property name="text">
+        <string>Enter passphrase</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="passEdit1">
+       <property name="echoMode">
+        <enum>QLineEdit::Password</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="passLabel2">
+       <property name="text">
+        <string>New passphrase</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="passEdit2">
+       <property name="echoMode">
+        <enum>QLineEdit::Password</enum>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="passLabel3">
+       <property name="text">
+        <string>Repeat new passphrase</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="passEdit3">
+       <property name="echoMode">
+        <enum>QLineEdit::Password</enum>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>40</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>AskPassphraseDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>AskPassphraseDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index b787019..0cb5075 100644 (file)
@@ -4,6 +4,9 @@
 /* Milliseconds between model updates */
 static const int MODEL_UPDATE_DELAY = 500;
 
+/* Maximum  passphrase length */
+static const int MAX_PASSPHRASE_SIZE = 1024;
+
 /* Size of icons in status bar */
 static const int STATUSBAR_ICONSIZE = 16;
 
diff --git a/src/qt/res/icons/key.png b/src/qt/res/icons/key.png
new file mode 100644 (file)
index 0000000..757cad4
Binary files /dev/null and b/src/qt/res/icons/key.png differ
index a9a89c2..852d789 100644 (file)
@@ -6,6 +6,7 @@
 #include "optionsmodel.h"
 #include "sendcoinsentry.h"
 #include "guiutil.h"
+#include "askpassphrasedialog.h"
 
 #include <QMessageBox>
 #include <QLocale>
@@ -84,6 +85,13 @@ void SendCoinsDialog::on_sendButton_clicked()
         return;
     }
 
+    WalletModel::UnlockContext ctx(model->requestUnlock());
+    if(!ctx.isValid())
+    {
+        // Unlock wallet was cancelled
+        return;
+    }
+
     WalletModel::SendCoinsReturn sendstatus = model->sendCoins(recipients);
     switch(sendstatus.status)
     {
@@ -118,7 +126,6 @@ void SendCoinsDialog::on_sendButton_clicked()
             tr("Error: Transaction creation failed  "),
             QMessageBox::Ok, QMessageBox::Ok);
         break;
-        break;
     case WalletModel::TransactionCommitFailed:
         QMessageBox::warning(this, tr("Send Coins"),
             tr("Error: The transaction was rejected.  This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."),
index 9a7b56d..dfededc 100644 (file)
@@ -199,3 +199,79 @@ WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
         return Unlocked;
     }
 }
+
+bool WalletModel::setWalletEncrypted(bool encrypted, const std::string &passphrase)
+{
+    if(encrypted)
+    {
+        // Encrypt
+        return wallet->EncryptWallet(passphrase);
+    }
+    else
+    {
+        // Decrypt -- TODO; not supported yet
+        return false;
+    }
+}
+
+bool WalletModel::setWalletLocked(bool locked, const std::string &passPhrase)
+{
+    if(locked)
+    {
+        // Lock
+        return wallet->Lock();
+    }
+    else
+    {
+        // Unlock
+        return wallet->Unlock(passPhrase);
+    }
+}
+
+bool WalletModel::changePassphrase(const std::string &oldPass, const std::string &newPass)
+{
+    bool retval;
+    CRITICAL_BLOCK(wallet->cs_vMasterKey)
+    {
+        wallet->Lock(); // Make sure wallet is locked before attempting pass change
+        retval = wallet->ChangeWalletPassphrase(oldPass, newPass);
+    }
+    return retval;
+}
+
+// WalletModel::UnlockContext implementation
+WalletModel::UnlockContext WalletModel::requestUnlock()
+{
+    bool was_locked = getEncryptionStatus() == Locked;
+    if(was_locked)
+    {
+        // Request UI to unlock wallet
+        emit requireUnlock();
+    }
+    // If wallet is still locked, unlock was failed or cancelled, mark context as invalid
+    bool valid = getEncryptionStatus() != Locked;
+
+    return UnlockContext(this, valid, was_locked);
+}
+
+WalletModel::UnlockContext::UnlockContext(WalletModel *wallet, bool valid, bool relock):
+        wallet(wallet),
+        valid(valid),
+        relock(relock)
+{
+}
+
+WalletModel::UnlockContext::~UnlockContext()
+{
+    if(valid && relock)
+    {
+        wallet->setWalletLocked(true);
+    }
+}
+
+void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs)
+{
+    // Transfer context; old object no longer relocks wallet
+    *this = rhs;
+    rhs.relock = false;
+}
index a585f8d..b141c07 100644 (file)
@@ -2,6 +2,7 @@
 #define WALLETMODEL_H
 
 #include <QObject>
+#include <string>
 
 class OptionsModel;
 class AddressTableModel;
@@ -52,8 +53,6 @@ public:
     int getNumTransactions() const;
     EncryptionStatus getEncryptionStatus() const;
 
-    bool isEncrypted() const;
-
     // Check address for validity
     bool validateAddress(const QString &address);
 
@@ -71,6 +70,38 @@ public:
 
     // Send coins to a list of recipients
     SendCoinsReturn sendCoins(const QList<SendCoinsRecipient> &recipients);
+
+    // Wallet encryption
+    bool setWalletEncrypted(bool encrypted, const std::string &passphrase);
+    // Passphrase only needed when unlocking
+    bool setWalletLocked(bool locked, const std::string &passPhrase=std::string());
+    bool changePassphrase(const std::string &oldPass, const std::string &newPass);
+
+    // RAI object for unlocking wallet, returned by requestUnlock()
+    class UnlockContext
+    {
+    public:
+        UnlockContext(WalletModel *wallet, bool valid, bool relock);
+        ~UnlockContext();
+
+        bool isValid() const { return valid; }
+
+        UnlockContext(const UnlockContext& obj)
+        { CopyFrom(obj); }
+    private:
+        UnlockContext& operator=(const UnlockContext& rhs)
+        { CopyFrom(rhs); return *this; }
+
+    private:
+        WalletModel *wallet;
+        bool valid;
+        mutable bool relock; // mutable, as it can be set to false by copying
+
+        void CopyFrom(const UnlockContext& rhs);
+    };
+
+    UnlockContext requestUnlock();
+
 private:
     CWallet *wallet;
 
@@ -90,6 +121,7 @@ signals:
     void balanceChanged(qint64 balance, qint64 unconfirmedBalance);
     void numTransactionsChanged(int count);
     void encryptionStatusChanged(int status);
+    void requireUnlock();
 
     // Asynchronous error notification
     void error(const QString &title, const QString &message);