mirror of
https://github.com/feather-wallet/feather.git
synced 2024-11-16 09:17:36 +00:00
wizard: legacy seed recovery
This commit is contained in:
parent
83042f3d0f
commit
e20186f428
4 changed files with 515 additions and 2 deletions
286
src/dialog/LegacySeedRecovery.cpp
Normal file
286
src/dialog/LegacySeedRecovery.cpp
Normal file
|
@ -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;
|
64
src/dialog/LegacySeedRecovery.h
Normal file
64
src/dialog/LegacySeedRecovery.h
Normal file
|
@ -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
|
156
src/dialog/LegacySeedRecovery.ui
Normal file
156
src/dialog/LegacySeedRecovery.ui
Normal file
|
@ -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>
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue