]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
Seed Recovery Dialog
authortobtoht <tob@featherwallet.org>
Thu, 14 Sep 2023 09:44:10 +0000 (11:44 +0200)
committertobtoht <tob@featherwallet.org>
Tue, 3 Oct 2023 19:10:00 +0000 (21:10 +0200)
src/dialog/SeedRecoveryDialog.cpp [new file with mode: 0644]
src/dialog/SeedRecoveryDialog.h [new file with mode: 0644]
src/dialog/SeedRecoveryDialog.ui [new file with mode: 0644]
src/wizard/PageWalletRestoreSeed.cpp

diff --git a/src/dialog/SeedRecoveryDialog.cpp b/src/dialog/SeedRecoveryDialog.cpp
new file mode 100644 (file)
index 0000000..7ad2d4b
--- /dev/null
@@ -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 &regex) {
+    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;
\ No newline at end of file
diff --git a/src/dialog/SeedRecoveryDialog.h b/src/dialog/SeedRecoveryDialog.h
new file mode 100644 (file)
index 0000000..e073dcb
--- /dev/null
@@ -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 &regex);
+    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
diff --git a/src/dialog/SeedRecoveryDialog.ui b/src/dialog/SeedRecoveryDialog.ui
new file mode 100644 (file)
index 0000000..dcb679e
--- /dev/null
@@ -0,0 +1,436 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SeedRecoveryDialog</class>
+ <widget class="QDialog" name="SeedRecoveryDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>570</width>
+    <height>632</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Seed Recovery</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label_17">
+     <property name="text">
+      <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This tool allows you to recover partial Polyseeds.&lt;/p&gt;&lt;p&gt;Enter every seed word you know. If you know a word partially, fill in the part you know. If you don't know a word at all, leave it blank.&lt;/p&gt;&lt;p&gt;Regex is supported. Entries containing no special characters are assumed to be prefixes.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="group_seed">
+     <property name="title">
+      <string/>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="3" column="5">
+       <widget class="QLineEdit" name="word_15">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="2">
+       <widget class="QLabel" name="label_12">
+        <property name="text">
+         <string>14.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="6">
+       <widget class="QLabel" name="label_15">
+        <property name="text">
+         <string>12.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>1.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="6">
+       <widget class="QLabel" name="label_16">
+        <property name="text">
+         <string>16.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="5">
+       <widget class="QLineEdit" name="word_11">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QLabel" name="label_10">
+        <property name="text">
+         <string>13.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="6">
+       <widget class="QLabel" name="label_8">
+        <property name="text">
+         <string>8.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>5.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="7">
+       <widget class="QLineEdit" name="word_16">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="7">
+       <widget class="QLineEdit" name="word_08">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="3">
+       <widget class="QLineEdit" name="word_02">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QLineEdit" name="word_13">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>2.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="2">
+       <widget class="QLabel" name="label_11">
+        <property name="text">
+         <string>10.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="3">
+       <widget class="QLineEdit" name="word_14">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="4">
+       <widget class="QLabel" name="label_14">
+        <property name="text">
+         <string>15.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="5">
+       <widget class="QLineEdit" name="word_07">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QLineEdit" name="word_09">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="4">
+       <widget class="QLabel" name="label_13">
+        <property name="text">
+         <string>11.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="5">
+       <widget class="QLineEdit" name="word_03">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_9">
+        <property name="text">
+         <string>9.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="3">
+       <widget class="QLineEdit" name="word_06">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="2">
+       <widget class="QLabel" name="label_6">
+        <property name="text">
+         <string>6.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="7">
+       <widget class="QLineEdit" name="word_04">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="4">
+       <widget class="QLabel" name="label_7">
+        <property name="text">
+         <string>7.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="7">
+       <widget class="QLineEdit" name="word_12">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="6">
+       <widget class="QLabel" name="label_5">
+        <property name="text">
+         <string>4.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QLineEdit" name="word_01">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QLineEdit" name="word_05">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="4">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>3.</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="3">
+       <widget class="QLineEdit" name="word_10">
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_19">
+     <property name="text">
+      <string>Enter any deposit address associated with the wallet (optional)</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QFormLayout" name="formLayout_2">
+     <property name="fieldGrowthPolicy">
+      <enum>QFormLayout::ExpandingFieldsGrow</enum>
+     </property>
+     <item row="0" column="0">
+      <widget class="QLabel" name="label_18">
+       <property name="text">
+        <string>Address</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLineEdit" name="line_address"/>
+     </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_21">
+       <property name="text">
+        <string>Account lookahead</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QLineEdit" name="line_majorLookahead">
+       <property name="text">
+        <string>50</string>
+       </property>
+       <property name="placeholderText">
+        <string>50</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="0">
+      <widget class="QLabel" name="label_20">
+       <property name="text">
+        <string>Address lookahead</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLineEdit" name="line_minorLookahead">
+       <property name="text">
+        <string>200</string>
+       </property>
+       <property name="placeholderText">
+        <string>200</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QFrame" name="frameProgress">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_2">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QGroupBox" name="groupBox">
+        <property name="title">
+         <string>Progress</string>
+        </property>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item>
+          <widget class="QProgressBar" name="progressBar">
+           <property name="value">
+            <number>0</number>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPlainTextEdit" name="potentialSeeds">
+           <property name="minimumSize">
+            <size>
+             <width>0</width>
+             <height>100</height>
+            </size>
+           </property>
+           <property name="readOnly">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </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>
+ <tabstops>
+  <tabstop>word_01</tabstop>
+  <tabstop>word_02</tabstop>
+  <tabstop>word_03</tabstop>
+  <tabstop>word_04</tabstop>
+  <tabstop>word_05</tabstop>
+  <tabstop>word_06</tabstop>
+  <tabstop>word_07</tabstop>
+  <tabstop>word_08</tabstop>
+  <tabstop>word_09</tabstop>
+  <tabstop>word_10</tabstop>
+  <tabstop>word_11</tabstop>
+  <tabstop>word_12</tabstop>
+  <tabstop>word_13</tabstop>
+  <tabstop>word_14</tabstop>
+  <tabstop>word_15</tabstop>
+  <tabstop>word_16</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>SeedRecoveryDialog</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>SeedRecoveryDialog</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 d9d26fdc2be70413f19cf526e015457bc70b6f0f..2285edd3db33d7ac6f481ca3d22f63cc01f176f7 100644 (file)
@@ -9,7 +9,9 @@
 #include <QDialogButtonBox>
 #include <QPlainTextEdit>
 #include <QPushButton>
+#include <QShortcut>
 
+#include "dialog/SeedRecoveryDialog.h"
 #include <monero_seed/wordlist.hpp>  // tevador 14 word
 #include "utils/Seed.h"
 #include "constants.h"
@@ -57,6 +59,12 @@ PageWalletRestoreSeed::PageWalletRestoreSeed(WizardFields *fields, QWidget *pare
     ui->seedEdit->setAcceptRichText(false);
     ui->seedEdit->setMaximumHeight(150);
 
+    QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+K"), this);
+    QObject::connect(shortcut, &QShortcut::activated, [&](){
+        SeedRecoveryDialog dialog{this};
+        dialog.exec();
+    });
+
     connect(ui->seedBtnGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), this, &PageWalletRestoreSeed::onSeedTypeToggled);
     connect(ui->combo_seedLanguage, &QComboBox::currentTextChanged, this, &PageWalletRestoreSeed::onSeedLanguageChanged);
     connect(ui->btnOptions, &QPushButton::clicked, this, &PageWalletRestoreSeed::onOptionsClicked);