Skip to content

Commit 6225e0e

Browse files
committed
wizard: add diceware support
1 parent c23869c commit 6225e0e

File tree

9 files changed

+583
-12
lines changed

9 files changed

+583
-12
lines changed

src/dialog/SeedDiceDialog.cpp

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
3+
4+
#include "SeedDiceDialog.h"
5+
#include "ui_SeedDiceDialog.h"
6+
7+
#include <cmath>
8+
#include <algorithm>
9+
10+
#include <QPasswordDigestor>
11+
12+
#include "utils/Seed.h"
13+
14+
SeedDiceDialog::SeedDiceDialog(QWidget *parent)
15+
: WindowModalDialog(parent)
16+
, ui(new Ui::SeedDiceDialog)
17+
{
18+
ui->setupUi(this);
19+
20+
ui->frame_dice->hide();
21+
ui->frame_coinflip->hide();
22+
23+
connect(ui->radio_dice, &QRadioButton::toggled, [this](bool toggled){
24+
ui->frame_dice->setVisible(toggled);
25+
this->updateRollsLeft();
26+
ui->label_rollsLeft2->setText("Rolls left:");
27+
ui->label_rolls->setText("Rolls:");
28+
});
29+
30+
connect(ui->radio_coinflip, &QRadioButton::toggled, [this](bool toggled){
31+
ui->frame_coinflip->setVisible(toggled);
32+
this->updateRollsLeft();
33+
ui->label_rollsLeft2->setText("Flips left:");
34+
ui->label_rolls->setText("Flips:");
35+
});
36+
37+
connect(ui->spin_sides, &QSpinBox::valueChanged, [this](int value){
38+
if (!ui->radio_dice->isChecked()) {
39+
return;
40+
}
41+
this->updateRollsLeft();
42+
});
43+
44+
connect(ui->line_roll, &QLineEdit::textChanged, this, &SeedDiceDialog::validateRollEntry);
45+
46+
connect(ui->btn_next, &QPushButton::clicked, [this]{
47+
this->setEnableMethodSelection(false);
48+
49+
if (!this->validateRollEntry()) {
50+
return;
51+
}
52+
53+
QStringList rolls = ui->line_roll->text().simplified().split(" ");
54+
for (const auto &roll : rolls) {
55+
this->addRoll(roll);
56+
}
57+
58+
ui->line_roll->clear();
59+
ui->line_roll->setFocus();
60+
});
61+
62+
connect(ui->btn_heads, &QPushButton::clicked, [this]{
63+
this->setEnableMethodSelection(false);
64+
this->addFlip(true);
65+
});
66+
67+
connect(ui->btn_tails, &QPushButton::clicked, [this]{
68+
this->setEnableMethodSelection(false);
69+
this->addFlip(false);
70+
});
71+
72+
connect(ui->btn_reset, &QPushButton::clicked, [this]{
73+
m_rolls.clear();
74+
this->update();
75+
this->setEnableMethodSelection(true);
76+
ui->btn_createPolyseed->setEnabled(false);
77+
});
78+
79+
connect(ui->btn_createPolyseed, &QPushButton::clicked, [this]{
80+
QByteArray salt = "POLYSEED";
81+
QByteArray data = m_rolls.join(" ").toUtf8();
82+
83+
// We already have enough entropy assuming unbiased throws, but a few extra rounds can't hurt
84+
// Polyseed requests 19 bytes of random data and discards two bits (for a total of 150 bits)
85+
m_key = QPasswordDigestor::deriveKeyPbkdf2(QCryptographicHash::Sha256, data, salt, 2048, 19);
86+
87+
this->accept();
88+
});
89+
90+
connect(ui->btn_cancel, &QPushButton::clicked, [this]{
91+
this->reject();
92+
});
93+
94+
ui->radio_dice->setChecked(true);
95+
96+
this->update();
97+
this->adjustSize();
98+
}
99+
100+
void SeedDiceDialog::addFlip(bool heads) {
101+
m_rolls << (heads ? "H" : "T");
102+
this->update();
103+
}
104+
105+
void SeedDiceDialog::addRoll(const QString &roll) {
106+
if (roll.isEmpty()) {
107+
return;
108+
}
109+
110+
m_rolls << roll;
111+
this->update();
112+
}
113+
114+
bool SeedDiceDialog::validateRollEntry() {
115+
ui->line_roll->setStyleSheet("");
116+
117+
QString errStyle = "QLineEdit{border: 1px solid red;}";
118+
QStringList rolls = ui->line_roll->text().simplified().split(" ");
119+
120+
for (const auto &rollstr : rolls) {
121+
if (rollstr.isEmpty()) {
122+
continue;
123+
}
124+
125+
bool ok;
126+
int roll = rollstr.toInt(&ok);
127+
if (!ok || roll < 1 || roll > ui->spin_sides->value()) {
128+
ui->line_roll->setStyleSheet(errStyle);
129+
return false;
130+
}
131+
}
132+
133+
return true;
134+
}
135+
136+
void SeedDiceDialog::update() {
137+
this->updateRollsLeft();
138+
this->updateRolls();
139+
140+
if (this->updateEntropy()) {
141+
ui->btn_createPolyseed->setEnabled(true);
142+
}
143+
}
144+
145+
bool SeedDiceDialog::updateEntropy() {
146+
double entropy = entropyPerRoll() * m_rolls.length();
147+
ui->label_entropy->setText(QString("%1 / %2 bits").arg(QString::number(entropy, 'f', 2), QString::number(entropyNeeded)));
148+
149+
return entropy > entropyNeeded;
150+
}
151+
152+
void SeedDiceDialog::updateRolls() {
153+
ui->rolls->setPlainText(m_rolls.join(" "));
154+
}
155+
156+
double SeedDiceDialog::entropyPerRoll() {
157+
if (ui->radio_dice->isChecked()) {
158+
return log(ui->spin_sides->value()) / log(2);
159+
} else {
160+
return 1;
161+
}
162+
}
163+
164+
void SeedDiceDialog::updateRollsLeft() {
165+
int rollsLeft = std::max((int)(ceil((entropyNeeded - (this->entropyPerRoll() * m_rolls.length())) / this->entropyPerRoll())), 0);
166+
ui->label_rollsLeft->setText(QString::number(rollsLeft));
167+
}
168+
169+
void SeedDiceDialog::setEnableMethodSelection(bool enabled) {
170+
ui->radio_dice->setEnabled(enabled);
171+
ui->radio_coinflip->setEnabled(enabled);
172+
ui->spin_sides->setEnabled(enabled);
173+
}
174+
175+
const char* SeedDiceDialog::getSecret() {
176+
return m_key.data();
177+
}
178+
179+
const QString& SeedDiceDialog::getMnemonic() {
180+
return m_mnemonic;
181+
}
182+
183+
SeedDiceDialog::~SeedDiceDialog() = default;

src/dialog/SeedDiceDialog.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
3+
4+
#include <QDialog>
5+
6+
#include "components.h"
7+
8+
#ifndef FEATHER_SEEDDICEDIALOG_H
9+
#define FEATHER_SEEDDICEDIALOG_H
10+
11+
namespace Ui {
12+
class SeedDiceDialog;
13+
}
14+
15+
class SeedDiceDialog : public WindowModalDialog
16+
{
17+
Q_OBJECT
18+
19+
public:
20+
explicit SeedDiceDialog(QWidget *parent);
21+
~SeedDiceDialog() override;
22+
23+
const char* getSecret();
24+
25+
const QString& getMnemonic();
26+
27+
private:
28+
void addFlip(bool heads);
29+
void addRoll(const QString &roll);
30+
double entropyPerRoll();
31+
bool validateRollEntry();
32+
33+
void update();
34+
bool updateEntropy();
35+
void updateRolls();
36+
void updateRollsLeft();
37+
void setEnableMethodSelection(bool enabled);
38+
39+
QScopedPointer<Ui::SeedDiceDialog> ui;
40+
QStringList m_rolls;
41+
QByteArray m_key;
42+
int entropyNeeded = 152; // Polyseed requests 19 bytes of random data
43+
QString m_mnemonic;
44+
};
45+
46+
47+
#endif //FEATHER_SEEDDICEDIALOG_H

0 commit comments

Comments
 (0)