signverifymessagedialog.cpp raw
1 // Copyright (c) 2011-2022 The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5 #include <qt/signverifymessagedialog.h>
6 #include <qt/forms/ui_signverifymessagedialog.h>
7
8 #include <qt/addressbookpage.h>
9 #include <qt/guiutil.h>
10 #include <qt/platformstyle.h>
11 #include <qt/walletmodel.h>
12
13 #include <common/signmessage.h> // For MessageSign(), MessageVerify()
14 #include <limenka-build-config.h> // IWYU pragma: keep
15 #include <key_io.h>
16 #include <wallet/wallet.h>
17
18 #include <cassert>
19 #include <vector>
20
21 #include <QClipboard>
22 #include <QColor>
23 #include <QEvent>
24 #include <QLabel>
25
26 static const SignVerifyMessageDialog::ThemeColors LIGHT_THEME_COLORS = {
27 .warning = QColor("#FF0000"),
28 .valid = QColor("#007D32")
29 };
30
31 static const SignVerifyMessageDialog::ThemeColors DARK_THEME_COLORS = {
32 .warning = QColor("#FF8080"),
33 .valid = QColor("#45DEB5")
34 };
35
36 SignVerifyMessageDialog::SignVerifyMessageDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
37 QDialog(parent, GUIUtil::dialog_flags),
38 ui(new Ui::SignVerifyMessageDialog),
39 platformStyle(_platformStyle)
40 {
41 ui->setupUi(this);
42
43 ui->addressBookButton_SM->setIcon(platformStyle->SingleColorIcon(":/icons/address-book"));
44 ui->pasteButton_SM->setIcon(platformStyle->SingleColorIcon(":/icons/editpaste"));
45 ui->copySignatureButton_SM->setIcon(platformStyle->SingleColorIcon(":/icons/editcopy"));
46 ui->signMessageButton_SM->setIcon(platformStyle->SingleColorIcon(":/icons/edit"));
47 ui->clearButton_SM->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
48 ui->addressBookButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/address-book"));
49 ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/transaction_0"));
50 ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
51
52 GUIUtil::setupAddressWidget(ui->addressIn_SM, this);
53 GUIUtil::setupAddressWidget(ui->addressIn_VM, this);
54
55 ui->addressIn_SM->installEventFilter(this);
56 ui->messageIn_SM->installEventFilter(this);
57 ui->signatureOut_SM->installEventFilter(this);
58 ui->addressIn_VM->installEventFilter(this);
59 ui->messageIn_VM->installEventFilter(this);
60 ui->signatureIn_VM->installEventFilter(this);
61
62 ui->signatureOut_SM->setFont(GUIUtil::fixedPitchFont());
63 ui->signatureIn_VM->setFont(GUIUtil::fixedPitchFont());
64
65 GUIUtil::handleCloseWindowShortcut(this);
66
67 updateThemeColors();
68 }
69
70 SignVerifyMessageDialog::~SignVerifyMessageDialog()
71 {
72 delete ui;
73 }
74
75 void SignVerifyMessageDialog::setModel(WalletModel *_model)
76 {
77 this->model = _model;
78 }
79
80 void SignVerifyMessageDialog::setAddress_SM(const QString &address)
81 {
82 ui->addressIn_SM->setText(address);
83 ui->messageIn_SM->setFocus();
84 }
85
86 void SignVerifyMessageDialog::setAddress_VM(const QString &address)
87 {
88 ui->addressIn_VM->setText(address);
89 ui->messageIn_VM->setFocus();
90 }
91
92 void SignVerifyMessageDialog::showTab_SM(bool fShow)
93 {
94 ui->tabWidget->setCurrentIndex(0);
95 if (fShow)
96 this->show();
97 }
98
99 void SignVerifyMessageDialog::showTab_VM(bool fShow)
100 {
101 ui->tabWidget->setCurrentIndex(1);
102 if (fShow)
103 this->show();
104 }
105
106 void SignVerifyMessageDialog::on_addressBookButton_SM_clicked()
107 {
108 if (model && model->getAddressTableModel())
109 {
110 model->refresh(/*pk_hash_only=*/true);
111 AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::ReceivingTab, this);
112 dlg.setModel(model->getAddressTableModel());
113 if (dlg.exec())
114 {
115 setAddress_SM(dlg.getReturnValue());
116 }
117 }
118 }
119
120 void SignVerifyMessageDialog::on_pasteButton_SM_clicked()
121 {
122 setAddress_SM(QApplication::clipboard()->text());
123 }
124
125 void SignVerifyMessageDialog::on_signMessageButton_SM_clicked()
126 {
127 if (!model)
128 return;
129
130 /* Clear old signature to ensure users don't get confused on error with an old signature displayed */
131 ui->signatureOut_SM->clear();
132
133 CTxDestination destination = DecodeDestination(ui->addressIn_SM->text().toStdString());
134 if (!IsValidDestination(destination)) {
135 ui->statusLabel_SM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->warning.name()));
136 ui->statusLabel_SM->setText(tr("The entered address is invalid.") + QString(" ") + tr("Please check the address and try again."));
137 return;
138 }
139 MessageSignatureFormat sig_format{MessageSignatureFormat::LEGACY};
140 const PKHash* pkhash = std::get_if<PKHash>(&destination);
141 if (!pkhash) {
142 sig_format = MessageSignatureFormat::SIMPLE;
143 }
144
145 WalletModel::UnlockContext ctx(model->requestUnlock());
146 if (!ctx.isValid())
147 {
148 ui->statusLabel_SM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->warning.name()));
149 ui->statusLabel_SM->setText(tr("Wallet unlock was cancelled."));
150 return;
151 }
152
153 const std::string& message = ui->messageIn_SM->document()->toPlainText().toStdString();
154 std::string signature;
155 SigningResult res = model->wallet().signMessage(sig_format, message, destination, signature);
156
157 QString error;
158 switch (res) {
159 case SigningResult::OK:
160 error = tr("No error");
161 break;
162 case SigningResult::PRIVATE_KEY_NOT_AVAILABLE:
163 error = tr("Private key for the entered address is not available.");
164 break;
165 case SigningResult::SIGNING_FAILED:
166 error = tr("Message signing failed.");
167 break;
168 // no default case, so the compiler can warn about missing cases
169 }
170
171 if (res != SigningResult::OK) {
172 ui->statusLabel_SM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->warning.name()));
173 ui->statusLabel_SM->setText(QString("<nobr>") + error + QString("</nobr>"));
174 return;
175 }
176
177 ui->statusLabel_SM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->valid.name()));
178 ui->statusLabel_SM->setText(QString("<nobr>") + tr("Message signed.") + QString("</nobr>"));
179
180 ui->signatureOut_SM->setText(QString::fromStdString(signature));
181 }
182
183 void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked()
184 {
185 GUIUtil::setClipboard(ui->signatureOut_SM->text());
186 }
187
188 void SignVerifyMessageDialog::on_clearButton_SM_clicked()
189 {
190 ui->addressIn_SM->clear();
191 ui->messageIn_SM->clear();
192 ui->signatureOut_SM->clear();
193 ui->statusLabel_SM->clear();
194
195 ui->addressIn_SM->setFocus();
196 }
197
198 void SignVerifyMessageDialog::on_addressBookButton_VM_clicked()
199 {
200 if (model && model->getAddressTableModel())
201 {
202 AddressBookPage dlg(platformStyle, AddressBookPage::ForSelection, AddressBookPage::SendingTab, this);
203 dlg.setModel(model->getAddressTableModel());
204 if (dlg.exec())
205 {
206 setAddress_VM(dlg.getReturnValue());
207 }
208 }
209 }
210
211 void SignVerifyMessageDialog::on_verifyMessageButton_VM_clicked()
212 {
213 const std::string& address = ui->addressIn_VM->text().toStdString();
214 const std::string& signature = ui->signatureIn_VM->text().toStdString();
215 const std::string& message = ui->messageIn_VM->document()->toPlainText().toStdString();
216
217 const auto result = MessageVerify(address, signature, message);
218
219 if (result == MessageVerificationResult::OK) {
220 ui->statusLabel_VM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->valid.name()));
221 } else {
222 ui->statusLabel_VM->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->warning.name()));
223 }
224
225 switch (result) {
226 case MessageVerificationResult::OK:
227 ui->statusLabel_VM->setText(
228 QString("<nobr>") + tr("Message verified.") + QString("</nobr>")
229 );
230 return;
231 case MessageVerificationResult::INCONCLUSIVE:
232 case MessageVerificationResult::ERR_POF:
233 ui->statusLabel_VM->setText(
234 QString("<nobr>") + tr("This version of %1 is unable to check this signature.").arg(CLIENT_NAME) + QString("</nobr>")
235 );
236 return;
237 case MessageVerificationResult::ERR_INVALID_ADDRESS:
238 ui->statusLabel_VM->setText(
239 tr("The entered address is invalid.") + QString(" ") +
240 tr("Please check the address and try again.")
241 );
242 return;
243 case MessageVerificationResult::ERR_ADDRESS_NO_KEY:
244 case MessageVerificationResult::ERR_MALFORMED_SIGNATURE:
245 ui->signatureIn_VM->setValid(false);
246 ui->statusLabel_VM->setText(
247 tr("The signature could not be decoded.") + QString(" ") +
248 tr("Please check the signature and try again.")
249 );
250 return;
251 case MessageVerificationResult::ERR_PUBKEY_NOT_RECOVERED:
252 ui->signatureIn_VM->setValid(false);
253 ui->statusLabel_VM->setText(
254 tr("The signature did not match the message digest.") + QString(" ") +
255 tr("Please check the signature and try again.")
256 );
257 return;
258 case MessageVerificationResult::ERR_INVALID:
259 case MessageVerificationResult::ERR_NOT_SIGNED:
260 ui->statusLabel_VM->setText(
261 QString("<nobr>") + tr("Message verification failed.") + QString("</nobr>")
262 );
263 return;
264 }
265 }
266
267 void SignVerifyMessageDialog::on_clearButton_VM_clicked()
268 {
269 ui->addressIn_VM->clear();
270 ui->signatureIn_VM->clear();
271 ui->messageIn_VM->clear();
272 ui->statusLabel_VM->clear();
273
274 ui->addressIn_VM->setFocus();
275 }
276
277 bool SignVerifyMessageDialog::eventFilter(QObject *object, QEvent *event)
278 {
279 if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::FocusIn)
280 {
281 if (ui->tabWidget->currentIndex() == 0)
282 {
283 /* Clear status message on focus change */
284 ui->statusLabel_SM->clear();
285
286 /* Select generated signature */
287 if (object == ui->signatureOut_SM)
288 {
289 ui->signatureOut_SM->selectAll();
290 return true;
291 }
292 }
293 else if (ui->tabWidget->currentIndex() == 1)
294 {
295 /* Clear status message on focus change */
296 ui->statusLabel_VM->clear();
297 }
298 }
299 return QDialog::eventFilter(object, event);
300 }
301
302 void SignVerifyMessageDialog::changeEvent(QEvent* e)
303 {
304 if (e->type() == QEvent::PaletteChange) {
305 ui->addressBookButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book")));
306 ui->pasteButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editpaste")));
307 ui->copySignatureButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/editcopy")));
308 ui->signMessageButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/edit")));
309 ui->clearButton_SM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
310 ui->addressBookButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/address-book")));
311 ui->verifyMessageButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/transaction_0")));
312 ui->clearButton_VM->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove")));
313 updateThemeColors();
314 }
315
316 QDialog::changeEvent(e);
317 }
318
319 void SignVerifyMessageDialog::updateThemeColors()
320 {
321 // Detect dark mode for color palette selection
322 const bool dark_mode = GUIUtil::isDarkMode(palette().color(backgroundRole()));
323
324 // Set theme colors pointer based on dark mode
325 m_theme_colors = dark_mode ? &DARK_THEME_COLORS : &LIGHT_THEME_COLORS;
326
327 // Update status labels
328 updateStatusLabelColor(ui->statusLabel_SM);
329 updateStatusLabelColor(ui->statusLabel_VM);
330
331 // Re-trigger validation on all input fields to update their styling
332 // including background and text color
333 // Use setText to trigger validation
334 if (ui->addressIn_SM) {
335 ui->addressIn_SM->setText(ui->addressIn_SM->text());
336 }
337 if (ui->addressIn_VM) {
338 ui->addressIn_VM->setText(ui->addressIn_VM->text());
339 }
340 if (ui->signatureIn_VM) {
341 ui->signatureIn_VM->setText(ui->signatureIn_VM->text());
342 }
343 }
344
345 void SignVerifyMessageDialog::updateStatusLabelColor(QLabel* label)
346 {
347 assert(m_theme_colors);
348
349 if (!label->text().isEmpty()) {
350 QString currentStyle = label->styleSheet();
351
352 // Check what color the label actually has and update if needed
353 if (currentStyle.contains(LIGHT_THEME_COLORS.valid.name()) || currentStyle.contains(DARK_THEME_COLORS.valid.name())) {
354 label->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->valid.name()));
355 } else if (currentStyle.contains(LIGHT_THEME_COLORS.warning.name()) || currentStyle.contains(DARK_THEME_COLORS.warning.name())) {
356 label->setStyleSheet(QStringLiteral("QLabel { color: %1; }").arg(m_theme_colors->warning.name()));
357 }
358 // If neither color is found, do nothing (shouldn't happen in practice)
359 }
360 }
361