]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
wizard: legacy seed recovery
authortobtoht <tob@featherwallet.org>
Wed, 6 Mar 2024 12:16:34 +0000 (13:16 +0100)
committertobtoht <tob@featherwallet.org>
Fri, 8 Mar 2024 19:58:40 +0000 (20:58 +0100)
src/dialog/LegacySeedRecovery.cpp [new file with mode: 0644]
src/dialog/LegacySeedRecovery.h [new file with mode: 0644]
src/dialog/LegacySeedRecovery.ui [new file with mode: 0644]
src/wizard/PageWalletRestoreSeed.cpp

diff --git a/src/dialog/LegacySeedRecovery.cpp b/src/dialog/LegacySeedRecovery.cpp
new file mode 100644 (file)
index 0000000..94a9d22
--- /dev/null
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2024 The Monero Project
+
+#include "LegacySeedRecovery.h"
+#include "ui_LegacySeedRecovery.h"
+
+#include <mnemonics/electrum-words.h>
+#include "ColorScheme.h"
+#include "utils/Utils.h"
+#include "polyseed/polyseed.h"
+#include "utils/AsyncTask.h"
+#include "device/device_default.hpp"
+#include "cryptonote_basic/account.h"
+#include "cryptonote_basic/cryptonote_basic_impl.h"
+#include "cryptonote_basic/blobdatatype.h"
+#include "common/base58.h"
+#include "serialization/binary_utils.h"
+
+LegacySeedRecovery::LegacySeedRecovery(QWidget *parent)
+        : WindowModalDialog(parent)
+        , m_scheduler(this)
+        , m_watcher(this)
+        , ui(new Ui::LegacySeedRecovery)
+{
+    ui->setupUi(this);
+
+    std::vector<const Language::Base*> wordlists = crypto::ElectrumWords::get_language_list();
+    for (const auto& wordlist: wordlists) {
+        QStringList words_qt;
+        std::vector<std::string> words_std = wordlist->get_word_list();
+        for (const auto& word: words_std) {
+            words_qt += QString::fromStdString(word);
+        }
+
+        QString language = QString::fromStdString(wordlist->get_english_language_name());
+        ui->combo_seedLanguage->addItem(language);
+        m_wordLists[language] = words_qt;
+    }
+
+    ui->combo_seedLanguage->setCurrentIndex(1);
+
+    ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+    ui->buttonBox->button(QDialogButtonBox::Apply)->setText("Check");
+
+    disconnect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+    connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &LegacySeedRecovery::checkSeed);
+    connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this]{
+        m_cancelled = true;
+    });
+    connect(ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this]{
+        m_cancelled = true;
+        m_watcher.waitForFinished();
+        this->close();
+    });
+
+    connect(this, &LegacySeedRecovery::progressUpdated, this, &LegacySeedRecovery::onProgressUpdated);
+
+    connect(this, &LegacySeedRecovery::searchFinished, this, &LegacySeedRecovery::onFinished);
+    connect(this, &LegacySeedRecovery::matchFound, this, &LegacySeedRecovery::onMatchFound);
+    connect(this, &LegacySeedRecovery::addressMatchFound, this, &LegacySeedRecovery::onAddressMatchFound);
+    connect(this, &LegacySeedRecovery::addResultText, this, &LegacySeedRecovery::onAddResultText);
+
+    this->adjustSize();
+}
+
+void LegacySeedRecovery::onMatchFound(const QString &match) {
+    ui->results->appendPlainText(match);
+}
+
+void LegacySeedRecovery::onAddressMatchFound(const QString &match) {
+    ui->results->appendPlainText(QString("Found seed containing address:\n%1").arg(match));
+}
+
+void LegacySeedRecovery::onFinished(bool cancelled) {
+    if (!cancelled) {
+        ui->progressBar->setMaximum(100);
+        ui->progressBar->setValue(100);
+    }
+
+    ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+    ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
+}
+
+void LegacySeedRecovery::onProgressUpdated(int value) {
+    ui->progressBar->setValue(value);
+}
+
+void LegacySeedRecovery::onAddResultText(const QString &text) {
+    ui->results->appendPlainText(text);
+}
+
+bool LegacySeedRecovery::testSeed(const QString &seed, const crypto::public_key &spkey) {
+    std::string mnemonic = seed.toStdString();
+
+    crypto::secret_key k;
+    std::string lang;
+    bool r = crypto::ElectrumWords::words_to_bytes(mnemonic, k, lang);
+
+    if (!r) {
+        return false;
+    }
+
+    if (spkey == crypto::null_pkey) {
+        emit matchFound(seed);
+        return false;
+    }
+
+
+    cryptonote::account_base base;
+    base.generate(k, true, false);
+
+    hw::device &hwdev = base.get_device();
+
+    for (int x = 0; x < m_major; x++) {
+        const std::vector<crypto::public_key> pkeys = hwdev.get_subaddress_spend_public_keys(base.get_keys(), x, 0, m_minor);
+        for (const auto &k : pkeys) {
+            if (k == spkey) {
+                emit addressMatchFound(seed);
+                emit searchFinished(false);
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+void LegacySeedRecovery::checkSeed() {
+    m_cancelled = false;
+
+    ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
+    ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true);
+
+    ui->results->clear();
+    ui->progressBar->setMaximum(39024);
+    ui->progressBar->setValue(0);
+
+    QStringList words = ui->seed->toPlainText().replace("\n", " ").replace("\r", "").trimmed().split(" ", Qt::SkipEmptyParts);
+    if (words.length() < 24) {
+        Utils::showError(this, "Invalid seed", "Less than 24 words were entered", {"Remember to use a single space between each word."});
+        return;
+    }
+    if (words.length() > 25) {
+        Utils::showError(this, "Invalid seed", "More than 25 words were entered", {"Remember to use a single space between each word."});
+        return;
+    }
+
+    Mode mode = words.length() == 25 ? Mode::WORD_25 : Mode::WORD_24;
+
+    QString address = ui->line_depositAddress->text();
+    crypto::public_key spkey = crypto::null_pkey;
+
+    if (!address.isEmpty()) {
+        cryptonote::blobdata data;
+        uint64_t prefix;
+        if (!tools::base58::decode_addr(address.toStdString(), prefix, data))
+        {
+            Utils::showError(this, "Unable to decode address");
+            this->onFinished(false);
+            return;
+        }
+
+        cryptonote::account_public_address a;
+        if (!::serialization::parse_binary(data, a))
+        {
+            Utils::showError(this, "Account public address keys can't be parsed");
+            this->onFinished(false);
+            return;
+        }
+
+        if (!crypto::check_key(a.m_spend_public_key) || !crypto::check_key(a.m_view_public_key))
+        {
+            Utils::showError(this, "Failed to validate address keys");
+            this->onFinished(false);
+            return;
+        }
+
+        spkey = a.m_spend_public_key;
+    }
+
+    if (spkey == crypto::null_pkey) {
+        ui->results->appendPlainText("\nPossible seeds:");
+    }
+
+    m_major = ui->line_majorLookahead->text().toInt();
+    m_minor = ui->line_minorLookahead->text().toInt();
+
+    QString language = ui->combo_seedLanguage->currentText();
+    if (!m_wordLists.contains(language)) {
+        Utils::showError(this, "Unable to start recovery tool", QString("No wordlist for language: %1").arg(language));
+        return;
+    }
+
+    ui->results->appendPlainText(QString("%1 words entered\n").arg(QString::number(words.length())));
+
+    // Single threaded for now
+    const auto future = m_scheduler.run([this, words, spkey, mode, language]{
+
+        if (mode == Mode::WORD_25) {
+            emit addResultText("Strategy [1/2]: swap adjacent words\n");
+
+            ui->progressBar->setValue(0);
+            ui->progressBar->setMaximum(24);
+
+            for (int i = 0; i < 24; i++) {
+                QStringList seed = words;
+                seed.swapItemsAt(i, i+1);
+
+                QString m = seed.join(" ");
+                bool done = this->testSeed(m, spkey);
+                if (done) {
+                    return;
+                }
+
+                // Swap back
+                seed.swapItemsAt(i, i+1);
+            }
+
+            if (m_cancelled) {
+                emit searchFinished(true);
+                return;
+            }
+
+            emit addResultText("Strategy [2/2]: one word is incorrect\n");
+
+            ui->progressBar->setValue(0);
+            ui->progressBar->setMaximum(39024);
+
+            int tries = 0;
+            for (int i = 0; i < 24; i++) {
+                QStringList seed = words;
+
+                for (const auto &word : m_wordLists[language]) {
+                    if (m_cancelled) {
+                        emit searchFinished(true);
+                        return;
+                    }
+                    emit progressUpdated(++tries);
+
+                    seed[i] = word;
+
+                    QString m = seed.join(" ");
+                    bool done = this->testSeed(m, spkey);
+                    if (done) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (mode == Mode::WORD_24) {
+            emit addResultText("Strategy [1/1]: one word is missing\n");
+
+            ui->progressBar->setValue(0);
+            ui->progressBar->setMaximum(39024);
+
+            int tries = 0;
+            for (int i = 0; i < 24; i++) {
+                QStringList seed = words;
+                seed.insert(i, "placeholder");
+
+                for (const auto &word : m_wordLists[language]) {
+                    if (m_cancelled) {
+                        emit searchFinished(true);
+                        return;
+                    }
+                    emit progressUpdated(++tries);
+
+                    seed[i] = word;
+                    QString m = seed.join(" ");
+
+                    bool done = this->testSeed(m, spkey);
+                    if (done) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        emit searchFinished(false);
+    });
+
+    m_watcher.setFuture(future.second);
+}
+
+LegacySeedRecovery::~LegacySeedRecovery() = default;
\ No newline at end of file
diff --git a/src/dialog/LegacySeedRecovery.h b/src/dialog/LegacySeedRecovery.h
new file mode 100644 (file)
index 0000000..e133157
--- /dev/null
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020-2024 The Monero Project
+
+#ifndef FEATHER_LEGACYSEEDRECOVERY_H
+#define FEATHER_LEGACYSEEDRECOVERY_H
+
+
+#include <QDialog>
+
+#include "components.h"
+#include "utils/scheduler.h"
+
+#include "cryptonote_basic/account.h"
+
+namespace Ui {
+    class LegacySeedRecovery;
+}
+
+class LegacySeedRecovery : public WindowModalDialog
+{
+Q_OBJECT
+
+public:
+    explicit LegacySeedRecovery(QWidget *parent = nullptr);
+    ~LegacySeedRecovery() override;
+
+    enum Mode {
+        WORD_24 = 0,
+        WORD_25 = 1
+    };
+
+signals:
+    void progressUpdated(int value);
+    void searchFinished(bool cancelled);
+    void matchFound(QString match);
+    void addressMatchFound(QString match);
+    void addResultText(QString text);
+
+private slots:
+    void onFinished(bool cancelled);
+    void onMatchFound(const QString &match);
+    void onAddressMatchFound(const QString &match);
+    void onProgressUpdated(int value);
+    void onAddResultText(const QString &text);
+
+private:
+    void checkSeed();
+    QString mnemonic(const QList<QStringList> &words, const QList<int> &index);
+
+    bool testSeed(const QString &seed, const crypto::public_key &spkey);
+
+    std::atomic<bool> m_cancelled = false;
+
+    int m_major = 50;
+    int m_minor = 200;
+
+    QHash<QString, QStringList> m_wordLists;
+    QFutureWatcher<void> m_watcher;
+    FutureScheduler m_scheduler;
+    QScopedPointer<Ui::LegacySeedRecovery> ui;
+};
+
+
+#endif //FEATHER_LEGACYSEEDRECOVERY_H
diff --git a/src/dialog/LegacySeedRecovery.ui b/src/dialog/LegacySeedRecovery.ui
new file mode 100644 (file)
index 0000000..090551b
--- /dev/null
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>LegacySeedRecovery</class>
+ <widget class="QDialog" name="LegacySeedRecovery">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>612</width>
+    <height>530</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Legacy Seed Recovery</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Enter seed here (use a space between each word):</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QPlainTextEdit" name="seed"/>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_3">
+     <property name="text">
+      <string>Enter any deposit address associated with the wallet (optional)</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <item row="1" column="0">
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Address</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label_4">
+       <property name="text">
+        <string>Account lookahead</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="0">
+      <widget class="QLabel" name="label_5">
+       <property name="text">
+        <string>Address lookahead</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="line_majorLookahead">
+       <property name="text">
+        <string>5</string>
+       </property>
+      </widget>
+     </item>
+     <item row="3" column="1">
+      <widget class="QLineEdit" name="line_minorLookahead">
+       <property name="text">
+        <string>50</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="line_depositAddress"/>
+     </item>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_6">
+       <property name="text">
+        <string>Language</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QComboBox" name="combo_seedLanguage"/>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Results</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <item>
+       <widget class="QProgressBar" name="progressBar">
+        <property name="value">
+         <number>0</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPlainTextEdit" name="results">
+        <property name="readOnly">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>LegacySeedRecovery</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>LegacySeedRecovery</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 823f2ca6bb4ab02fbdbb99210ceb824b8a769e21..7080b7411452d13d708333241b8286eb9b4f6be5 100644 (file)
@@ -12,6 +12,7 @@
 #include <QShortcut>
 
 #include "dialog/SeedRecoveryDialog.h"
+#include "dialog/LegacySeedRecovery.h"
 #include <monero_seed/wordlist.hpp>  // tevador 14 word
 #include "utils/Seed.h"
 #include "constants.h"
@@ -61,8 +62,14 @@ PageWalletRestoreSeed::PageWalletRestoreSeed(WizardFields *fields, QWidget *pare
 
     QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+K"), this);
     QObject::connect(shortcut, &QShortcut::activated, [&](){
-        SeedRecoveryDialog dialog{this};
-        dialog.exec();
+        if (ui->radio16->isChecked()) {
+            SeedRecoveryDialog dialog{this};
+            dialog.exec();
+        }
+        if (ui->radio25->isChecked()) {
+            LegacySeedRecovery dialog{this};
+            dialog.exec();
+        }
     });
 
     ui->seedObscured->hide();