From a1d88a8588184de8983ee30fa347aad294f660eb Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Thu, 23 Aug 2018 14:45:16 -0700
Subject: [PATCH 1/2] Merge howard/wallet-locker2 into my branch

---
 .../qml/hifi/commerce/wallet/Help.qml         |  51 +----
 .../qml/hifi/commerce/wallet/Security.qml     | 206 +-----------------
 .../qml/hifi/commerce/wallet/Wallet.qml       |  15 ++
 interface/src/commerce/Ledger.cpp             |  36 ++-
 interface/src/commerce/Ledger.h               |   3 +-
 interface/src/commerce/Wallet.cpp             |  61 +++++-
 interface/src/commerce/Wallet.h               |   4 +
 7 files changed, 113 insertions(+), 263 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml
index b453509712..6d8fc3c33f 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Help.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml
@@ -63,47 +63,6 @@ Item {
             question: "How can I get HFC?";
             answer: "High Fidelity commerce is in open beta right now. Want more HFC? \
 Get it by going to <br><br><b><font color='#0093C5'><a href='#bank'>BankOfHighFidelity.</a></font></b> and meeting with the banker!";
-        }
-        ListElement {
-            isExpanded: false;
-            question: "What are private keys and where are they stored?";
-            answer: 
-                "A private key is a secret piece of text that is used to prove ownership, unlock confidential information, and sign transactions. \
-In High Fidelity, your private key is used to securely access the contents of your Wallet and Purchases. \
-After wallet setup, a hifikey file is stored on your computer in High Fidelity Interface's AppData directory. \
-Your hifikey file contains your private key and is protected by your wallet passphrase. \
-<br><br>It is very important to back up your hifikey file! \
-<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>"
-        }
-        ListElement {
-            isExpanded: false;
-            question: "How do I back up my private keys?";
-            answer: "You can back up your hifikey file (which contains your private key and is encrypted using your wallet passphrase) by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. \
-Restore your hifikey file by replacing the file in Interface's AppData directory with your backup copy. \
-Others with access to your back up should not be able to spend your HFC without your passphrase. \
-<b><font color='#0093C5'><a href='#privateKeyPath'>Tap here to open the folder where your HifiKeys are stored on your main display.</a></font></b>";
-        }
-        ListElement {
-            isExpanded: false;
-            question: "What happens if I lose my private keys?";
-            answer: "We cannot stress enough that you should keep a backup! For security reasons, High Fidelity does not keep a copy, and cannot restore it for you. \
-If you lose your private key, you will no longer have access to the contents of your Wallet or My Purchases. \
-Here are some things to try:<ul>\
-<li>If you have backed up your hifikey file before, search your backup location</li>\
-<li>Search your AppData directory in the last machine you used to set up the Wallet</li>\
-<li>If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder</li>\
-</ul><br><br>As a last resort, you can set up your Wallet again and generate a new hifikey file. \
-Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
-        }
-        ListElement {
-            isExpanded: false;
-            question: "What if I forget my wallet passphrase?";
-            answer: "Your wallet passphrase is used to encrypt your private keys. Please write it down and store it securely! \
-<br><br>If you forget your passphrase, you will no longer be able to decrypt the hifikey file that the passphrase protects. \
-You will also no longer have access to the contents of your Wallet or My Purchases. \
-For security reasons, High Fidelity does not keep a copy of your passphrase, and can't restore it for you. \
-<br><br>If you still cannot remember your wallet passphrase, you can set up your Wallet again and generate a new hifikey file. \
-Unfortunately, this means you will start with 0 HFC and your purchased items will not be transferred over.";
         }
         ListElement {
             isExpanded: false;
@@ -114,11 +73,9 @@ In your Wallet's Send Money tab, choose from your list of connections, or choose
         ListElement {
             isExpanded: false;
             question: "What is a Security Pic?"
-            answer: "Your Security Pic is an encrypted image that you select during Wallet Setup. \
-It acts as an extra layer of Wallet security. \
-When you see your Security Pic, you know that your actions and data are securely making use of your private keys.\
-<br><br>Don't enter your passphrase anywhere that doesn't display your Security Pic! \
-If you don't see your Security Pic on a page that requests your Wallet passphrase, someone untrustworthy may be trying to access your Wallet.";
+            answer: "Your Security Pic acts as an extra layer of Wallet security. \
+When you see your Security Pic, you know that your actions and data are securely making use of your account. \
+<br><br><b><font color='#0093C5'><a href='#securitypic'>Tap here to change your Security Pic.</a></font></b>";
         }
         ListElement {
             isExpanded: false;
@@ -260,6 +217,8 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta
                             }
                         } else if (link === "#support") {
                             Qt.openUrlExternally("mailto:support@highfidelity.com");
+                        } else if (link === "#securitypic") {
+                            sendSignalToWallet({method: 'walletSecurity_changeSecurityImage'});
                         }
                     }
                 }
diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml
index 216d621bf8..e021328ebe 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Security.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml
@@ -88,76 +88,9 @@ Item {
             color: hifi.colors.faintGray;
         }
 
-        Item {
-            id: changePassphraseContainer;
-            anchors.top: securityTextSeparator.bottom;
-            anchors.topMargin: 8;
-            anchors.left: parent.left;
-            anchors.leftMargin: 40;
-            anchors.right: parent.right;
-            anchors.rightMargin: 55;
-            height: 75;
-
-            HiFiGlyphs {
-                id: changePassphraseImage;
-                text: hifi.glyphs.passphrase;
-                // Size
-                size: 80;
-                // Anchors
-                anchors.top: parent.top;
-                anchors.bottom: parent.bottom;
-                anchors.left: parent.left;
-                // Style
-                color: hifi.colors.white;
-            }
-
-            RalewaySemiBold {
-                text: "Passphrase";
-                // Anchors
-                anchors.top: parent.top;
-                anchors.bottom: parent.bottom;
-                anchors.left: changePassphraseImage.right;
-                anchors.leftMargin: 30;
-                width: 50;
-                // Text size
-                size: 18;
-                // Style
-                color: hifi.colors.white;
-            }
-
-            // "Change Passphrase" button
-            HifiControlsUit.Button {
-                id: changePassphraseButton;
-                color: hifi.buttons.blue;
-                colorScheme: hifi.colorSchemes.dark;
-                anchors.right: parent.right;
-                anchors.verticalCenter: parent.verticalCenter;
-                width: 140;
-                height: 40;
-                text: "Change";
-                onClicked: {
-                    sendSignalToWallet({method: 'walletSecurity_changePassphrase'});
-                }
-            }
-        }
-
-        Rectangle {
-            id: changePassphraseSeparator;
-            // Size
-            width: parent.width;
-            height: 1;
-            // Anchors
-            anchors.left: parent.left;
-            anchors.right: parent.right;
-            anchors.top: changePassphraseContainer.bottom;
-            anchors.topMargin: 8;
-            // Style
-            color: hifi.colors.faintGray;
-        }
-
         Item {
             id: changeSecurityImageContainer;
-            anchors.top: changePassphraseSeparator.bottom;
+            anchors.top: securityTextSeparator.bottom;
             anchors.topMargin: 8;
             anchors.left: parent.left;
             anchors.leftMargin: 40;
@@ -207,143 +140,6 @@ Item {
                 }
             }
         }
-
-        Rectangle {
-            id: privateKeysSeparator;
-            // Size
-            width: parent.width;
-            height: 1;
-            // Anchors
-            anchors.left: parent.left;
-            anchors.right: parent.right;
-            anchors.top: changeSecurityImageContainer.bottom;
-            anchors.topMargin: 8;
-            // Style
-            color: hifi.colors.faintGray;
-        }
-
-        Item {
-            id: yourPrivateKeysContainer;
-            anchors.top: privateKeysSeparator.bottom;
-            anchors.left: parent.left;
-            anchors.leftMargin: 40;
-            anchors.right: parent.right;
-            anchors.rightMargin: 55;
-            anchors.bottom: parent.bottom;
-
-            onVisibleChanged: {
-                if (visible) {
-                    Commerce.getKeyFilePathIfExists();
-                }
-            }
-
-            HiFiGlyphs {
-                id: yourPrivateKeysImage;
-                text: hifi.glyphs.walletKey;
-                // Size
-                size: 80;
-                // Anchors
-                anchors.top: parent.top;
-                anchors.topMargin: 20;
-                anchors.left: parent.left;
-                // Style
-                color: hifi.colors.white;
-            }
-
-            RalewaySemiBold {
-                id: yourPrivateKeysText;
-                text: "Private Keys";
-                size: 18;
-                // Anchors
-                anchors.top: parent.top;
-                anchors.topMargin: 32;
-                anchors.left: yourPrivateKeysImage.right;
-                anchors.leftMargin: 30;
-                anchors.right: parent.right;
-                height: 30;
-                // Style
-                color: hifi.colors.white;
-            }
-
-            // Text below "private keys"
-            RalewayRegular {
-                id: explanitoryText;
-                text: "Your money and purchases are secured with private keys that only you have access to.";
-                // Text size
-                size: 18;
-                // Anchors
-                anchors.top: yourPrivateKeysText.bottom;
-                anchors.topMargin: 10;
-                anchors.left: yourPrivateKeysText.left;
-                anchors.right: yourPrivateKeysText.right;
-                height: paintedHeight;
-                // Style
-                color: hifi.colors.white;
-                wrapMode: Text.WordWrap;
-                // Alignment
-                horizontalAlignment: Text.AlignLeft;
-                verticalAlignment: Text.AlignVCenter;
-            }
-
-            Rectangle {
-                id: removeHmdContainer;
-                z: 998;
-                visible: false;
-
-                gradient: Gradient {
-                    GradientStop {
-                        position: 0.2;
-                        color: hifi.colors.baseGrayHighlight;
-                    }
-                    GradientStop {
-                        position: 1.0;
-                        color: hifi.colors.baseGrayShadow;
-                    }
-                }
-                anchors.fill: backupInstructionsButton;
-                radius: 5;
-                MouseArea {
-                    anchors.fill: parent;
-                    propagateComposedEvents: false;
-                    hoverEnabled: true;
-                }
-
-                RalewayBold {
-                    anchors.fill: parent;
-                    text: "INSTRUCTIONS OPEN ON DESKTOP";
-                    size: 15;
-                    color: hifi.colors.white;
-                    verticalAlignment: Text.AlignVCenter;
-                    horizontalAlignment: Text.AlignHCenter;
-                }
-
-                    Timer {
-                        id: removeHmdContainerTimer;
-                        interval: 5000;
-                        onTriggered: removeHmdContainer.visible = false
-                    }
-            }
-
-            HifiControlsUit.Button {
-                id: backupInstructionsButton;
-                text: "View Backup Instructions";
-                color: hifi.buttons.blue;
-                colorScheme: hifi.colorSchemes.dark;
-                anchors.left: explanitoryText.left;
-                anchors.right: explanitoryText.right;
-                anchors.top: explanitoryText.bottom;
-                anchors.topMargin: 16;
-                height: 40;
-
-                onClicked: {
-                    var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'));
-                    Qt.openUrlExternally(keyPath + "/backup_instructions.html");
-                    Qt.openUrlExternally(keyPath);
-                    removeHmdContainer.visible = true;
-                    removeHmdContainerTimer.start();
-                }
-            }
-        }
     }
 
     //
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index 603d7fb676..ffd06cb4a8 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -399,6 +399,9 @@ Rectangle {
             onSendSignalToWallet: {
                 if (msg.method === 'walletReset' || msg.method === 'passphraseReset') {
                     sendToScript(msg);
+                } else if (msg.method === 'walletSecurity_changeSecurityImage') {
+                    securityImageChange.initModel();
+                    root.activeView = "securityImageChange";
                 }
             }
         }
@@ -803,12 +806,24 @@ Rectangle {
     }
 
     function walletResetSetup() {
+        /* Bypass all this and do it automatically
         root.activeView = "walletSetup";
         var timestamp = new Date();
         walletSetup.startingTimestamp = timestamp;
         walletSetup.setupAttemptID = generateUUID();
         UserActivityLogger.commerceWalletSetupStarted(timestamp, walletSetup.setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app",
             (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''));
+            */
+
+        var randomNumber = Math.floor(Math.random() * 34) + 1;
+        var securityImagePath = "images/" + addLeadingZero(randomNumber) + ".jpg";
+        Commerce.getWalletAuthenticatedStatus(); // before writing security image, ensures that salt/account password is set.
+        Commerce.chooseSecurityImage(securityImagePath);
+        Commerce.generateKeyPair();
+    }
+
+    function addLeadingZero(n) {
+        return n < 10 ? '0' + n : '' + n;
     }
 
     function followReferrer(msg) {
diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp
index 702251f867..67303f2a9b 100644
--- a/interface/src/commerce/Ledger.cpp
+++ b/interface/src/commerce/Ledger.cpp
@@ -31,7 +31,9 @@
 QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) {
     QByteArray response = reply->readAll();
     QJsonObject data = QJsonDocument::fromJson(response).object();
+#if defined(DEV_BUILD)  // Don't expose user's personal data in the wild. But during development this can be handy.
     qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact);
+#endif
     return data;
 }
 // Non-200 responses are not json:
@@ -69,7 +71,9 @@ void Ledger::send(const QString& endpoint, const QString& success, const QString
     auto accountManager = DependencyManager::get<AccountManager>();
     const QString URL = "/api/v1/commerce/";
     JSONCallbackParameters callbackParams(this, success, fail);
+#if defined(DEV_BUILD)  // Don't expose user's personal data in the wild. But during development this can be handy.
     qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact);
+#endif
     accountManager->sendRequest(URL + endpoint,
         authType,
         method,
@@ -117,7 +121,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
     signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
 }
 
-bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) {
+bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker) {
     auto accountManager = DependencyManager::get<AccountManager>();
     if (!accountManager->isLoggedIn()) {
         qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
@@ -125,11 +129,25 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& signing_key) {
         emit receiveAtResult(result);
         return false; // We know right away that we will fail, so tell the caller.
     }
-
-    signedSend("public_key", hfc_key.toUtf8(), signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
+    QJsonObject transaction;
+    transaction["public_key"] = hfc_key;
+    transaction["locker"] = QString::fromUtf8(locker);
+    QJsonDocument transactionDoc{ transaction };
+    auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
+    signedSend("text", transactionString, signing_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
     return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
 }
 
+bool Ledger::receiveAt() {
+    auto wallet = DependencyManager::get<Wallet>();
+    auto keys = wallet->listPublicKeys();
+    if (keys.isEmpty()) {
+        return false;
+    }
+    auto key = keys.first();
+    return receiveAt(key, key, wallet->getWallet());
+}
+
 void Ledger::balance(const QStringList& keys) {
     keysQuery("balance", "balanceSuccess", "balanceFailure");
 }
@@ -283,24 +301,30 @@ void Ledger::accountSuccess(QNetworkReply* reply) {
     auto iv = QByteArray::fromBase64(data["iv"].toString().toUtf8());
     auto ckey = QByteArray::fromBase64(data["ckey"].toString().toUtf8());
     QString remotePublicKey = data["public_key"].toString();
+    const QByteArray locker = data["locker"].toString().toUtf8();
     bool isOverride = wallet->wasSoftReset();
 
     wallet->setSalt(salt);
     wallet->setIv(iv);
     wallet->setCKey(ckey);
+    if (!locker.isEmpty()) {
+        wallet->setWallet(locker);
+        wallet->setPassphrase("ACCOUNT"); // We only locker wallets that have been converted to account-based auth.
+    }
 
     QString keyStatus = "ok";
     QStringList localPublicKeys = wallet->listPublicKeys();
     if (remotePublicKey.isEmpty() || isOverride) {
-        if (!localPublicKeys.isEmpty()) {
-            QString key = localPublicKeys.first();
-            receiveAt(key, key);
+        if (!localPublicKeys.isEmpty()) { // Let the metaverse know about a local wallet.
+            receiveAt();
         }
     } else {
         if (localPublicKeys.isEmpty()) {
             keyStatus = "preexisting";
         } else if (localPublicKeys.first() != remotePublicKey) {
             keyStatus = "conflicting";
+        } else if (locker.isEmpty()) { // Matches metaverse data, but we haven't lockered it yet.
+            receiveAt();
         }
     }
 
diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h
index ba2f167f4b..427395ee11 100644
--- a/interface/src/commerce/Ledger.h
+++ b/interface/src/commerce/Ledger.h
@@ -26,7 +26,8 @@ class Ledger : public QObject, public Dependency {
 
 public:
     void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
-    bool receiveAt(const QString& hfc_key, const QString& signing_key);
+    bool receiveAt(const QString& hfc_key, const QString& signing_key, const QByteArray& locker);
+    bool receiveAt();
     void balance(const QStringList& keys);
     void inventory(const QString& editionFilter, const QString& typeFilter, const QString& titleFilter, const int& page, const int& perPage);
     void history(const QStringList& keys, const int& pageNumber, const int& itemsPerPage);
diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp
index ef6b4654f5..5b8417be7c 100644
--- a/interface/src/commerce/Wallet.cpp
+++ b/interface/src/commerce/Wallet.cpp
@@ -131,7 +131,7 @@ bool Wallet::writeBackupInstructions() {
     QFile outputFile(outputFilename);
     bool retval = false;
 
-    if (getKeyFilePath() == "")
+    if (getKeyFilePath().isEmpty())
     {
         return false;
     }
@@ -190,6 +190,30 @@ bool writeKeys(const char* filename, EC_KEY* keys) {
     return retval;
 }
 
+bool Wallet::setWallet(const QByteArray& wallet) {
+    QFile file(keyFilePath());
+    if (!file.open(QIODevice::WriteOnly)) {
+        qCCritical(commerce) << "Unable to open wallet for write in" << keyFilePath();
+        return false;
+    }
+    if (file.write(wallet) != wallet.count()) {
+        qCCritical(commerce) << "Unable to write wallet in" << keyFilePath();
+        return false;
+    }
+    file.close();
+    return true;
+}
+QByteArray Wallet::getWallet() {
+    QFile file(keyFilePath());
+    if (!file.open(QIODevice::ReadOnly)) {
+        qCInfo(commerce) << "No existing wallet in" << keyFilePath();
+        return QByteArray();
+    }
+    QByteArray wallet = file.readAll();
+    file.close();
+    return wallet;
+}
+
 QPair<QByteArray*, QByteArray*> generateECKeypair() {
 
     EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
@@ -334,7 +358,7 @@ Wallet::Wallet() {
         uint status;
         QString keyStatus = result.contains("data") ? result["data"].toObject()["keyStatus"].toString() : "";
 
-        if (wallet->getKeyFilePath() == "" || !wallet->getSecurityImage()) {
+        if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) {
             if (keyStatus == "preexisting") {
                 status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING;
             } else{
@@ -524,15 +548,23 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
 
     // FIXME: initialize OpenSSL elsewhere soon
     initialize();
+    qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: checking" << (!_passphrase || !_passphrase->isEmpty());
 
     // this should always be false if we don't have a passphrase
     // cached yet
     if (!_passphrase || _passphrase->isEmpty()) {
-        return false;
+        if (!getKeyFilePath().isEmpty()) { // If file exists, then it is an old school file that has not been lockered. Must get user's passphrase.
+            qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: No passphrase, but there is an existing wallet.";
+            return false;
+        } else {
+            qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: New setup.";
+            setPassphrase("ACCOUNT"); // Going forward, consider this an account-based client.
+        }
     }
     if (_publicKeys.count() > 0) {
         // we _must_ be authenticated if the publicKeys are there
         DependencyManager::get<WalletScriptingInterface>()->setWalletStatus((uint)WalletStatus::WALLET_STATUS_READY);
+        qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet was ready";
         return true;
     }
 
@@ -545,10 +577,15 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
 
             // be sure to add the public key so we don't do this over and over
             _publicKeys.push_back(publicKey.toBase64());
+
+            if (*_passphrase != "ACCOUNT") {
+                changePassphrase("ACCOUNT"); // Rewrites with salt and constant, and will be lockered that way.
+            }
+            qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet now ready";
             return true;
         }
     }
-
+    qCDebug(commerce) << "walletIsAuthenticatedWithPassphrase: wallet not ready";
     return false;
 }
 
@@ -559,6 +596,7 @@ bool Wallet::generateKeyPair() {
     qCInfo(commerce) << "Generating keypair.";
     auto keyPair = generateECKeypair();
     if (!keyPair.first) {
+        qCWarning(commerce) << "Empty keypair";
         return false;
     }
 
@@ -576,7 +614,7 @@ bool Wallet::generateKeyPair() {
     // 2. It is maximally private, and we can step back from that later if desired.
     // 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
     auto ledger = DependencyManager::get<Ledger>();
-    return ledger->receiveAt(key, key);
+    return ledger->receiveAt(key, key, getWallet());
 }
 
 QStringList Wallet::listPublicKeys() {
@@ -666,11 +704,13 @@ void Wallet::chooseSecurityImage(const QString& filename) {
     // there _is_ a keyfile, we need to update it (similar to changing the
     // passphrase, we need to do so into a temp file and move it).
     if (!QFile(keyFilePath()).exists()) {
+        qCDebug(commerce) << "initial security pic set for empty wallet";
         emit securityImageResult(true);
         return;
     }
 
     bool success = writeWallet();
+    qCDebug(commerce) << "updated security pic" << success;
     emit securityImageResult(success);
 }
 
@@ -715,6 +755,11 @@ QString Wallet::getKeyFilePath() {
 
 bool Wallet::writeWallet(const QString& newPassphrase) {
     EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
+    auto ledger = DependencyManager::get<Ledger>();
+    // Remove any existing locker, because it will be out of date.
+    if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) {
+        return false;  // FIXME: receiveAt could fail asynchronously.
+    }
     if (keys) {
         // we read successfully, so now write to a new temp file
         QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
@@ -722,6 +767,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) {
         if (!newPassphrase.isEmpty()) {
             setPassphrase(newPassphrase);
         }
+
         if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
             if (writeSecurityImage(_securityImage, tempFileName)) {
                 // ok, now move the temp file to the correct spot
@@ -729,6 +775,11 @@ bool Wallet::writeWallet(const QString& newPassphrase) {
                 QFile(tempFileName).rename(QString(keyFilePath()));
                 qCDebug(commerce) << "wallet written successfully";
                 emit keyFilePathIfExistsResult(getKeyFilePath());
+                if (!walletIsAuthenticatedWithPassphrase() || !ledger->receiveAt()) {
+                    // FIXME: Should we fail the whole operation?
+                    // Tricky, because we'll need the the key and file from the TEMP location...
+                    qCWarning(commerce) << "Failed to update locker";
+                }
                 return true;
             } else {
                 qCDebug(commerce) << "couldn't write security image to temp wallet";
diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h
index 665afd9a23..c096713058 100644
--- a/interface/src/commerce/Wallet.h
+++ b/interface/src/commerce/Wallet.h
@@ -73,6 +73,7 @@ private slots:
     void handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
 
 private:
+    friend class Ledger;
     QStringList _publicKeys{};
     QPixmap* _securityImage { nullptr };
     QByteArray _salt;
@@ -87,6 +88,9 @@ private:
     bool readSecurityImage(const QString& inputFilePath, unsigned char** outputBufferPtr, int* outputBufferLen);
     bool writeBackupInstructions();
 
+    bool setWallet(const QByteArray& wallet);
+    QByteArray getWallet();
+
     void account();
 };
 

From dddb2141f041422c0b9365be84480003f28037c1 Mon Sep 17 00:00:00 2001
From: Zach Fox <fox@highfidelity.io>
Date: Thu, 23 Aug 2018 15:45:29 -0700
Subject: [PATCH 2/2] Implement Wallet Security feature - Auto Logout checkbox

---
 .../qml/LoginDialog/LinkAccountBody.qml       |  1 +
 .../qml/hifi/commerce/wallet/Security.qml     | 75 +++++++++++++++++++
 .../qml/hifi/commerce/wallet/Wallet.qml       | 11 +++
 interface/src/Application.cpp                 |  6 ++
 scripts/system/commerce/wallet.js             |  7 ++
 5 files changed, 100 insertions(+)

diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
index 814778a4b1..4c6e5f6fce 100644
--- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -120,6 +120,7 @@ Item {
 
         TextField {
             id: usernameField
+            text: Settings.getValue("wallet/savedUsername", "");
             width: parent.width
             focus: true
             label: "Username or Email"
diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml
index e021328ebe..14ac696ef7 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Security.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml
@@ -140,6 +140,81 @@ Item {
                 }
             }
         }
+
+        Item {
+            id: autoLogoutContainer;
+            anchors.top: changeSecurityImageContainer.bottom;
+            anchors.topMargin: 8;
+            anchors.left: parent.left;
+            anchors.leftMargin: 40;
+            anchors.right: parent.right;
+            anchors.rightMargin: 55;
+            height: 75;
+
+            HiFiGlyphs {	
+                id: autoLogoutImage;	
+                text: hifi.glyphs.walletKey;	
+                // Size	
+                size: 80;	
+                // Anchors	
+                anchors.top: parent.top;	
+                anchors.topMargin: 20;	
+                anchors.left: parent.left;	
+                // Style	
+                color: hifi.colors.white;	
+            }
+
+            HifiControlsUit.CheckBox {
+                id: autoLogoutCheckbox;
+                checked: Settings.getValue("wallet/autoLogout", false);
+                text: "Automatically Log Out when Exiting Interface"
+                // Anchors
+                anchors.verticalCenter: autoLogoutImage.verticalCenter;
+                anchors.left: autoLogoutImage.right;
+                anchors.leftMargin: 20;
+                anchors.right: autoLogoutHelp.left;
+                anchors.rightMargin: 12;
+                boxSize: 28;
+                labelFontSize: 18;
+                color: hifi.colors.white;
+                onCheckedChanged: {
+                    Settings.setValue("wallet/autoLogout", checked);
+                    if (checked) {
+                        Settings.setValue("wallet/savedUsername", Account.username);
+                    } else {
+                        Settings.setValue("wallet/savedUsername", "");
+                    }
+                }
+            }
+
+            RalewaySemiBold {
+                id: autoLogoutHelp;
+                text: '[?]';
+                // Anchors
+                anchors.verticalCenter: autoLogoutImage.verticalCenter;
+                anchors.right: parent.right;
+                width: 30;
+                height: 30;
+                // Text size
+                size: 18;
+                // Style
+                color: hifi.colors.blueHighlight;
+
+                MouseArea {
+                    anchors.fill: parent;
+                    hoverEnabled: true;
+                    onEntered: {
+                        parent.color = hifi.colors.blueAccent;
+                    }
+                    onExited: {
+                        parent.color = hifi.colors.blueHighlight;
+                    }
+                    onClicked: {
+                        sendSignalToWallet({method: 'walletSecurity_autoLogoutHelp'});
+                    }
+                }
+            }
+        }
     }
 
     //
diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
index ffd06cb4a8..65d98af234 100644
--- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
+++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml
@@ -382,6 +382,17 @@ Rectangle {
                 } else if (msg.method === 'walletSecurity_changeSecurityImage') {
                     securityImageChange.initModel();
                     root.activeView = "securityImageChange";
+                } else if (msg.method === 'walletSecurity_autoLogoutHelp') {
+                    lightboxPopup.titleText = "Automatically Log Out";
+                    lightboxPopup.bodyText = "By default, after you log in to High Fidelity, you will stay logged in to your High Fidelity " +
+                        "account even after you close and re-open Interface. This means anyone who opens Interface on your computer " +
+                        "could make purchases with your Wallet.\n\n" +
+                        "If you do not want to stay logged in across Interface sessions, check this box.";
+                    lightboxPopup.button1text = "CLOSE";
+                    lightboxPopup.button1method = function() {
+                        lightboxPopup.visible = false;
+                    }
+                    lightboxPopup.visible = true;
                 }
             }
         }
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 1d515392b0..60d23c94dd 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -375,6 +375,7 @@ static const int INTERVAL_TO_CHECK_HMD_WORN_STATUS = 500; // milliseconds
 static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
 static const QString ACTIVE_DISPLAY_PLUGIN_SETTING_NAME = "activeDisplayPlugin";
 static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
+static const QString AUTO_LOGOUT_SETTING_NAME = "wallet/autoLogout";
 
 const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
     { SVO_EXTENSION, &Application::importSVOFromURL },
@@ -1730,6 +1731,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
     QTimer* settingsTimer = new QTimer();
     moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
         connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
+            bool autoLogout = Setting::Handle<bool>(AUTO_LOGOUT_SETTING_NAME, false).get();
+            if (autoLogout) {
+                auto accountManager = DependencyManager::get<AccountManager>();
+                accountManager->logout();
+            }
             // Disconnect the signal from the save settings
             QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
             // Stop the settings timer
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index ac25269e41..8730273e7c 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -406,6 +406,11 @@
         sendMoneyRecipient = null;
     }
 
+    function onUsernameChanged() {
+        Settings.setValue("wallet/autoLogout", false);
+        Settings.setValue("wallet/savedUsername", "");
+    }
+
     // Function Name: fromQml()
     //
     // Description:
@@ -581,6 +586,7 @@
     var tablet = null;
     var walletEnabled = Settings.getValue("commerce", true);
     function startup() {
+        GlobalServices.myUsernameChanged.connect(onUsernameChanged);
         if (walletEnabled) {
             tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
             button = tablet.addButton({
@@ -612,6 +618,7 @@
         removeOverlays();
     }
     function shutdown() {
+        GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
         button.clicked.disconnect(onButtonClicked);
         tablet.removeButton(button);
         deleteSendMoneyParticleEffect();