-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
764 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
// SPDX-FileCopyrightText: 2020-2023 The Monero Project | ||
|
||
#include "SeedRecoveryDialog.h" | ||
#include "ui_SeedRecoveryDialog.h" | ||
|
||
#include <monero_seed/wordlist.hpp> | ||
#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" | ||
|
||
SeedRecoveryDialog::SeedRecoveryDialog(QWidget *parent) | ||
: WindowModalDialog(parent) | ||
, m_scheduler(this) | ||
, m_watcher(this) | ||
, ui(new Ui::SeedRecoveryDialog) | ||
{ | ||
ui->setupUi(this); | ||
|
||
for (int i = 0; i != 2048; i++) { | ||
m_wordList << QString::fromStdString(wordlist::english.get_word(i)); | ||
} | ||
|
||
ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false); | ||
ui->buttonBox->button(QDialogButtonBox::Apply)->setText("Check"); | ||
|
||
connect(this, &SeedRecoveryDialog::progressUpdated, this, &SeedRecoveryDialog::onProgressUpdated); | ||
|
||
disconnect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); | ||
connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &SeedRecoveryDialog::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, &SeedRecoveryDialog::searchFinished, this, &SeedRecoveryDialog::onFinished); | ||
connect(this, &SeedRecoveryDialog::matchFound, this, &SeedRecoveryDialog::onMatchFound); | ||
connect(this, &SeedRecoveryDialog::addressMatchFound, this, &SeedRecoveryDialog::onAddressMatchFound); | ||
|
||
this->adjustSize(); | ||
} | ||
|
||
void SeedRecoveryDialog::onMatchFound(const QString &match) { | ||
ui->potentialSeeds->appendPlainText(match); | ||
} | ||
|
||
void SeedRecoveryDialog::onAddressMatchFound(const QString &match) { | ||
ui->potentialSeeds->appendPlainText(QString("\nFound seed containing address:\n%1").arg(match)); | ||
} | ||
|
||
void SeedRecoveryDialog::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); | ||
} | ||
|
||
QStringList SeedRecoveryDialog::wordsWithRegex(const QRegularExpression ®ex) { | ||
return m_wordList.filter(regex); | ||
} | ||
|
||
bool SeedRecoveryDialog::findNext(const QList<QStringList> &words, QList<int> &index) { | ||
if (words.length() != index.length()) { | ||
return false; | ||
} | ||
|
||
if (words.empty()) { | ||
return false; | ||
} | ||
|
||
for (int i = words.length() - 1; i >= 0; i--) { | ||
if ((words[i].length() - 1) > index[i]) { | ||
index[i] += 1; | ||
for (int j = i + 1; j < words.length(); j++) { | ||
index[j] = 0; | ||
} | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
QString SeedRecoveryDialog::mnemonic(const QList<QStringList> &words, const QList<int> &index) { | ||
if (words.length() != index.length()) { | ||
return QString(); | ||
} | ||
|
||
QStringList mnemonic; | ||
for (int i = 0; i < words.length(); i++) { | ||
mnemonic.push_back(words[i][index[i]]); | ||
} | ||
|
||
return mnemonic.join(" "); | ||
} | ||
|
||
bool SeedRecoveryDialog::isAlpha(const QString &word) { | ||
for (const QChar &ch : word) { | ||
if (!ch.isLetter()) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
void SeedRecoveryDialog::onProgressUpdated(int value) { | ||
ui->progressBar->setValue(value); | ||
} | ||
|
||
void SeedRecoveryDialog::checkSeed() { | ||
m_cancelled = false; | ||
|
||
ui->progressBar->setValue(0); | ||
ui->potentialSeeds->clear(); | ||
|
||
ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); | ||
ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(true); | ||
|
||
// Check address | ||
QString address = ui->line_address->text(); | ||
crypto::public_key spkey = crypto::null_pkey; | ||
|
||
if (!address.isEmpty()) { | ||
cryptonote::address_parse_info info; | ||
bool addressValid = cryptonote::get_account_address_from_str(info, cryptonote::network_type::MAINNET, address.toStdString()); | ||
if (!addressValid) { | ||
Utils::showError(this, "Invalid address entered"); | ||
this->onFinished(false); | ||
return; | ||
} | ||
spkey = info.address.m_spend_public_key; | ||
} | ||
|
||
QList<QLineEdit*> lineEdits = ui->group_seed->findChildren<QLineEdit*>(); | ||
std::sort(lineEdits.begin(), lineEdits.end(), [](QLineEdit* a, QLineEdit* b) { | ||
return a->objectName() < b->objectName(); | ||
}); | ||
|
||
QList<QStringList> words; | ||
uint64_t combinations = 1; | ||
|
||
for (QLineEdit *lineEdit : lineEdits) { | ||
lineEdit->setStyleSheet(""); | ||
} | ||
|
||
for (QLineEdit *lineEdit : lineEdits) { | ||
ColorScheme::updateFromWidget(this); | ||
QString word = lineEdit->text(); | ||
|
||
QString wordRe = word; | ||
if (this->isAlpha(word)) { | ||
wordRe = QString("^%1").arg(wordRe); | ||
} | ||
QRegularExpression regex{wordRe}; | ||
|
||
if (!regex.isValid()) { | ||
lineEdit->setStyleSheet(ColorScheme::RED.asStylesheet(true)); | ||
Utils::showError(this, "Invalid regex entered", QString("'%1' is not a valid regular expression").arg(wordRe)); | ||
this->onFinished(false); | ||
return; | ||
} | ||
|
||
QStringList possibleWords = wordsWithRegex(regex); | ||
int numWords = possibleWords.length(); | ||
|
||
if (numWords == 1) { | ||
lineEdit->setStyleSheet(ColorScheme::GREEN.asStylesheet(true)); | ||
} | ||
else if (numWords == 0) { | ||
lineEdit->setStyleSheet(ColorScheme::RED.asStylesheet(true)); | ||
Utils::showError(this, "Word is not in wordlist", QString("No words found for: '%1'").arg(word)); | ||
this->onFinished(false); | ||
return; | ||
} else { | ||
lineEdit->setStyleSheet(ColorScheme::YELLOW.asStylesheet(true)); | ||
ui->potentialSeeds->appendPlainText(QString("Possible words for '%1': %2").arg(word, possibleWords.join(", "))); | ||
|
||
if (combinations < std::numeric_limits<uint64_t>::max() / numWords) { | ||
combinations *= possibleWords.length(); | ||
} else { | ||
Utils::showError(this, "Too many possible seeds", "Recovery infeasible"); | ||
this->onFinished(false); | ||
return; | ||
} | ||
} | ||
|
||
words << possibleWords; | ||
} | ||
|
||
if (spkey == crypto::null_pkey) { | ||
ui->potentialSeeds->appendPlainText("\nPossible seeds:"); | ||
} | ||
|
||
qDebug() << "Number of possible combinations: " << combinations; | ||
|
||
ui->progressBar->setMaximum(combinations / 1000); | ||
|
||
uint32_t major = ui->line_majorLookahead->text().toInt(); | ||
uint32_t minor = ui->line_minorLookahead->text().toInt(); | ||
|
||
// Single threaded for now | ||
const auto future = m_scheduler.run([this, words, spkey, major, minor]{ | ||
QList<int> index(16, 0); | ||
|
||
qint64 i = 0; | ||
|
||
do { | ||
if (m_cancelled) { | ||
emit searchFinished(true); | ||
return; | ||
} | ||
|
||
if (++i % 1000 == 0) { | ||
emit progressUpdated(i / 1000); | ||
} | ||
|
||
QString seedString = mnemonic(words, index); | ||
|
||
crypto::secret_key key; | ||
try { | ||
polyseed::data seed(POLYSEED_MONERO); | ||
seed.decode(seedString.toStdString().c_str()); | ||
seed.keygen(&key.data, sizeof(key.data)); | ||
} | ||
catch (const polyseed::error& ex) { | ||
continue; | ||
} | ||
|
||
// Handle case where we don't know an address | ||
if (spkey == crypto::null_pkey) { | ||
emit matchFound(seedString); | ||
continue; | ||
} | ||
|
||
cryptonote::account_base base; | ||
base.generate(key, true, false); | ||
|
||
hw::device &hwdev = base.get_device(); | ||
|
||
for (int x = 0; x < major; x++) { | ||
const std::vector<crypto::public_key> pkeys = hwdev.get_subaddress_spend_public_keys(base.get_keys(), x, 0, minor); | ||
for (const auto &k : pkeys) { | ||
if (k == spkey) { | ||
emit addressMatchFound(seedString); | ||
emit searchFinished(false); | ||
return; | ||
} | ||
} | ||
} | ||
} while (findNext(words, index)); | ||
|
||
emit searchFinished(false); | ||
}); | ||
|
||
m_watcher.setFuture(future.second); | ||
} | ||
|
||
SeedRecoveryDialog::~SeedRecoveryDialog() = default; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
// SPDX-FileCopyrightText: 2020-2023 The Monero Project | ||
|
||
#ifndef FEATHER_SEEDRECOVERYDIALOG_H | ||
#define FEATHER_SEEDRECOVERYDIALOG_H | ||
|
||
#include <QDialog> | ||
|
||
#include "components.h" | ||
#include "utils/scheduler.h" | ||
|
||
namespace Ui { | ||
class SeedRecoveryDialog; | ||
} | ||
|
||
class SeedRecoveryDialog : public WindowModalDialog | ||
{ | ||
Q_OBJECT | ||
|
||
public: | ||
explicit SeedRecoveryDialog(QWidget *parent = nullptr); | ||
~SeedRecoveryDialog() override; | ||
|
||
signals: | ||
void progressUpdated(int value); | ||
void searchFinished(bool cancelled); | ||
void matchFound(QString match); | ||
void addressMatchFound(QString match); | ||
|
||
private slots: | ||
void onFinished(bool cancelled); | ||
void onMatchFound(const QString &match); | ||
void onAddressMatchFound(const QString &match); | ||
void onProgressUpdated(int value); | ||
|
||
private: | ||
void checkSeed(); | ||
QStringList wordsWithRegex(const QRegularExpression ®ex); | ||
bool isAlpha(const QString &word); | ||
bool findNext(const QList<QStringList> &words, QList<int> &index); | ||
QString mnemonic(const QList<QStringList> &words, const QList<int> &index); | ||
|
||
std::atomic<bool> m_cancelled = false; | ||
|
||
QStringList m_wordList; | ||
QFutureWatcher<void> m_watcher; | ||
FutureScheduler m_scheduler; | ||
QScopedPointer<Ui::SeedRecoveryDialog> ui; | ||
}; | ||
|
||
#endif //FEATHER_SEEDRECOVERYDIALOG_H |
Oops, something went wrong.