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