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 ;
0 commit comments