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
BankOfHighFidelity. 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. \
-
It is very important to back up your hifikey file! \
-Tap here to open the folder where your HifiKeys are stored on your main display."
- }
- 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. \
-Tap here to open the folder where your HifiKeys are stored on your main display.";
- }
- 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:
\
-- If you have backed up your hifikey file before, search your backup location
\
-- Search your AppData directory in the last machine you used to set up the Wallet
\
-- If you are a developer and have installed multiple builds of High Fidelity, your hifikey file might be in another folder
\
-
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! \
-
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. \
-
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.\
-
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. \
+
Tap here to change your Security Pic.";
}
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();
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();
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();
+ 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 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()->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();
- 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();
+ // 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 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();
};