diff --git a/application.qrc b/application.qrc index 0332b5f..9ff3227 100644 --- a/application.qrc +++ b/application.qrc @@ -50,6 +50,7 @@ res/dark-01.png res/money-mouth.png res/money-outgoing.png + res/hush-money-white.png res/hushdlogo.gif diff --git a/res/hush-money-white.png b/res/hush-money-white.png new file mode 100644 index 0000000..7ee8d9a Binary files /dev/null and b/res/hush-money-white.png differ diff --git a/res/hush-money.png b/res/hush-money.png new file mode 100644 index 0000000..eb27d4e Binary files /dev/null and b/res/hush-money.png differ diff --git a/src/Model/ChatItem.cpp b/src/Model/ChatItem.cpp index 3a190f0..34d1992 100644 --- a/src/Model/ChatItem.cpp +++ b/src/Model/ChatItem.cpp @@ -158,6 +158,8 @@ QString ChatItem::toChatLine() { QDateTime myDateTime; QString lock; + QString money; + QString moneyText; myDateTime.setTime_t(_timestamp); if (_notarize == true) @@ -176,10 +178,42 @@ QString ChatItem::toChatLine() lock = " "; } + + if (_memo.startsWith("Money transaction of :")) + { + if (_outgoing == true) + { + + moneyText = QString("

Outgoing Money Transaction

") + QString(" "); + }else{ + + + moneyText = QString("

Incoming Money Transaction

") + QString(" "); + + } + }else{money = ""; + moneyText = ""; } + + if (_memo.startsWith("Request of :")) + { + if (_outgoing == true) + { + + moneyText = QString("

Outgoing Hush Request

") + QString(" "); + }else{ + + + moneyText = QString("

Incoming Hush Request

") + QString(" "); + + } + }else{money = ""; + moneyText = ""; } + + QString line = QString("") + myDateTime.toString("yyyy-MM-dd hh:mm"); - line += QString(lock) + QString(""); + line += QString(lock) + QString(moneyText) + QString(""); line +=QString("

") + _memo.toHtmlEscaped() + QString("

"); return line; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a2dda35..d5e979d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1488,9 +1488,8 @@ void MainWindow::setupchatTab() { QAction* requestHushAction; QAction* subatomicAction; contextMenu = new QMenu(ui->listContactWidget); - HushAction = new QAction("Send this contact Hush ",contextMenu); + HushAction = new QAction("Send or Request Hush ",contextMenu); editAction = new QAction("Delete this contact",contextMenu); - requestAction = new QAction("Send a contact request - coming soon",contextMenu); subatomicAction = new QAction("Make a subatomic swap with a friend- coming soon",contextMenu); @@ -1500,8 +1499,7 @@ void MainWindow::setupchatTab() { ui->listContactWidget->setContextMenuPolicy(Qt::ActionsContextMenu); ui->listContactWidget->addAction(HushAction); - ui->listContactWidget->addAction(editAction); - ui->listContactWidget->addAction(requestAction); + ui->listContactWidget->addAction(editAction); ui->listContactWidget->addAction(subatomicAction); ui->memoTxtChat->setEnabled(true); @@ -1525,9 +1523,12 @@ void MainWindow::setupchatTab() { QDialog transactionDialog(this); transaction.setupUi(&transactionDialog); Settings::saveRestore(&transactionDialog); - transaction.requestHush->setEnabled(false); - transaction.requestHush->setVisible(false); + // transaction.requestHush->setEnabled(false); + // transaction.requestHush->setVisible(false); transaction.amountChat->setValidator(this->getAmountValidator()); + QString icon = ":icons/res/hush-money-white.png"; + QPixmap hush(icon); + transaction.label_3->setPixmap(hush); @@ -1559,6 +1560,19 @@ void MainWindow::setupchatTab() { QObject::connect(transaction.sendHush, &QPushButton::clicked, this , &MainWindow::sendMoneyChat); + + + QObject::connect(transaction.requestHush, &QPushButton::clicked, [&] (){ + + QString amt = transaction.amountChat->text(); + QString memo = transaction.MemoMoney->text(); + this->setAmt(amt); + this->setMoneyMemo(memo); + transactionDialog.close(); + }); + + QObject::connect(transaction.requestHush, &QPushButton::clicked, this , &MainWindow::sendMoneyRequestChat); + transactionDialog.exec(); @@ -1869,6 +1883,284 @@ QString MainWindow::doSendChatMoneyTxValidations(Tx tx) { return ""; } +// Create a Tx from the current state of the Chat page. +Tx MainWindow::createTxFromSendRequestChatPage() { + Tx tx; + CAmount totalAmt; + // For each addr/amt in the Chat tab + { + + QString amtStr = this->getAmt(); + CAmount amt; + CAmount amtHm; + + amt = CAmount::fromDecimalString("0"); + amtHm = CAmount::fromDecimalString("0"); + totalAmt = totalAmt + amt; + + QModelIndex index = ui->listContactWidget->currentIndex(); + QString label_contact = index.data(Qt::DisplayRole).toString(); + + for(auto &c : AddressBook::getInstance()->getAllAddressLabels()) + + if (label_contact == c.getName()) { + + QString cid = c.getCid(); + QString myAddr = c.getMyAddress(); + QString type = "Money"; + QString addr = c.getPartnerAddress(); + QString moneymemo = this->getMoneyMemo(); + + /////////User input for chatmemos + QString memoplain = QString("Request of : ") + amtStr + QString(" HUSH ") + QString("\n") + QString("\n") + moneymemo; + + /////////We convert the user input from QString to unsigned char*, so we can encrypt it later + int lengthmemo = memoplain.length(); + + char *memoplainchar = NULL; + memoplainchar = new char[lengthmemo+2]; + strncpy(memoplainchar, memoplain.toUtf8(), lengthmemo +1); + + QString pubkey = this->getPubkeyByAddress(addr); + QString passphraseHash = DataStore::getChatDataStore()->getPassword(); + int length = passphraseHash.length(); + + ////////////////Generate the secretkey for our message encryption + + char *hashEncryptionKeyraw = NULL; + hashEncryptionKeyraw = new char[length+1]; + strncpy(hashEncryptionKeyraw, passphraseHash.toUtf8(), length+1); + + #define MESSAGEAS1 ((const unsigned char *) hashEncryptionKeyraw) + #define MESSAGEAS1_LEN length + + + unsigned char sk[crypto_kx_SECRETKEYBYTES]; + unsigned char pk[crypto_kx_PUBLICKEYBYTES]; + unsigned char server_rx[crypto_kx_SESSIONKEYBYTES], server_tx[crypto_kx_SESSIONKEYBYTES]; + + if (crypto_kx_seed_keypair(pk,sk, + MESSAGEAS1) !=0) { + + this->logger->write("Suspicious keypair, bail out "); + } + ////////////////Get the pubkey from Bob, so we can create the share key + + const QByteArray pubkeyBobArray = QByteArray::fromHex(pubkey.toLatin1()); + const unsigned char *pubkeyBob = reinterpret_cast(pubkeyBobArray.constData()); + /////Create the shared key for sending the message + + if (crypto_kx_server_session_keys(server_rx, server_tx, + pk, sk, pubkeyBob) != 0) { + this->logger->write("Suspicious client public send key, bail out "); + } + + + // Let's try to preserve Unicode characters + QByteArray ba_memo = memoplain.toUtf8(); + int ba_memo_length = ba_memo.size(); + + #define MESSAGEMoney (const unsigned char *) ba_memo.data() + #define MESSAGE_LENMoney ba_memo_length + + + ////////////Now lets encrypt the message Alice send to Bob////////////////////////////// + //#define MESSAGE (const unsigned char *) memoplainchar + //#define MESSAGE_LEN lengthmemo + #define CIPHERTEXT_LEN (crypto_secretstream_xchacha20poly1305_ABYTES + MESSAGE_LENMoney) + unsigned char ciphertext[CIPHERTEXT_LEN]; + unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; + + crypto_secretstream_xchacha20poly1305_state state; + + /* Set up a new stream: initialize the state and create the header */ + crypto_secretstream_xchacha20poly1305_init_push(&state, header, server_tx); + + + /* Now, encrypt the first chunk. `c1` will contain an encrypted, + * authenticated representation of `MESSAGE_PART1`. */ + crypto_secretstream_xchacha20poly1305_push + (&state, ciphertext, NULL, MESSAGEMoney, MESSAGE_LENMoney, NULL, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL); + + ////Create the HM for this message + QString headerbytes = QByteArray(reinterpret_cast(header), crypto_secretstream_xchacha20poly1305_HEADERBYTES).toHex(); + QString publickeyAlice = QByteArray(reinterpret_cast(pk), crypto_kx_PUBLICKEYBYTES).toHex(); + + + QString hmemo= createHeaderMemo(type,cid,myAddr,headerbytes,publickeyAlice,1,0); + + /////Ciphertext Memo + QString memo = QByteArray(reinterpret_cast(ciphertext), CIPHERTEXT_LEN).toHex(); + + + tx.toAddrs.push_back(ToFields{addr, amtHm, hmemo}); + tx.toAddrs.push_back(ToFields{addr, amt, memo}); + + } + } + + tx.fee = Settings::getMinerFee(); + + return tx; + +} + +void MainWindow::sendMoneyRequestChat() { + +////////////////////////////Todo: Check if a Contact is selected////////// + + // Create a Tx from the values on the send tab. Note that this Tx object + // might not be valid yet. + + /* QString Name = ui->contactNameMemo->text(); + + if ((ui->contactNameMemo->text().isEmpty()) || (ui->memoTxtChat->toPlainText().trimmed().isEmpty())) { + + QMessageBox msg(QMessageBox::Critical, tr("You have to select a contact and insert a Memo"), + tr("You have selected no Contact from Contactlist,\n") + tr("\nor your Memo is empty"), + QMessageBox::Ok, this); + + msg.exec(); + return; + }*/ + + + Tx tx = createTxFromSendRequestChatPage(); + + QString error = doSendChatMoneyRequestTxValidations(tx); + + if (!error.isEmpty()) { + // Something went wrong, so show an error and exit + QMessageBox msg(QMessageBox::Critical, tr("Message Error"), error, + QMessageBox::Ok, this); + + msg.exec(); + + // abort the Tx + return; + } + + auto movie = new QMovie(this); + auto movie1 = new QMovie(this); + movie->setFileName(":/img/res/loaderblack.gif"); + movie1->setFileName(":/img/res/loaderwhite.gif"); + + auto theme = Settings::getInstance()->get_theme_name(); + if (theme == "Dark" || theme == "Midnight") { + + connect(movie, &QMovie::frameChanged, [=]{ + ui->sendChatButton->setIcon(movie->currentPixmap()); + }); + movie->start(); + ui->sendChatButton->show(); + ui->sendChatButton->setEnabled(false); + + } else { + + connect(movie1, &QMovie::frameChanged, [=]{ + ui->sendChatButton->setIcon(movie1->currentPixmap()); + }); + movie1->start(); + ui->sendChatButton->show(); + ui->sendChatButton->setEnabled(false); + } + + ui->memoTxtChat->clear(); + + // And send the Tx + rpc->executeTransaction(tx, + [=] (QString txid) { + ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid); + + + QTimer::singleShot(1000, [=]() { + + if (theme == "Dark" || theme == "Midnight") { + QPixmap send(":/icons/res/send-white.png"); + QIcon sendIcon(send); + ui->sendChatButton->setIcon(sendIcon); + movie->stop(); + ui->sendChatButton->setEnabled(true); + }else{ + + QPixmap send(":/icons/res/sendBlack.png"); + QIcon sendIcon(send); + ui->sendChatButton->setIcon(sendIcon); + movie1->stop(); + ui->sendChatButton->setEnabled(true); + } + + }); + + // Force a UI update so we get the unconfirmed Tx + rpc->refresh(true); + ui->memoTxtChat->clear(); + + }, + // Errored out + [=] (QString opid, QString errStr) { + ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000); + + if (!opid.isEmpty()) + errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr; + + QMessageBox::critical(this, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok); + movie->stop(); + + + if (theme == "Dark" || theme == "Midnight") { + QPixmap send(":/icons/res/send-white.png"); + QIcon sendIcon(send); + ui->sendChatButton->setIcon(sendIcon); + movie->stop(); + ui->sendChatButton->setEnabled(true); + }else{ + + QPixmap send(":/icons/res/sendBlack.png"); + QIcon sendIcon(send); + ui->sendChatButton->setIcon(sendIcon); + movie1->stop(); + ui->sendChatButton->setEnabled(true); + } + + + + } + ); + + } + +QString MainWindow::doSendChatMoneyRequestTxValidations(Tx tx) { + // Check to see if we have enough verified funds to send the Tx. + + CAmount total; + for (auto toAddr : tx.toAddrs) { + if (!Settings::isValidAddress(toAddr.addr)) { + QString addr = (toAddr.addr.length() > 100 ? toAddr.addr.left(100) + "..." : toAddr.addr); + return QString(tr("Recipient Address ")) % addr % tr(" is Invalid"); + } + + // This technically shouldn't be possible, but issue #62 seems to have discovered a bug + // somewhere, so just add a check to make sure. + if (toAddr.amount.toqint64() < 0) { + return QString(tr("Amount for address '%1' is invalid!").arg(toAddr.addr)); + } + + total = total + toAddr.amount; + } + total = total + tx.fee; + + auto available = rpc->getModel()->getAvailableBalance(); + + if (available < total) { + return tr("Not enough available funds to send this transaction\n\nHave: %1\nNeed: %2\n\nNote: Funds need 1 confirmations before they can be spent") + .arg(available.toDecimalhushString(), total.toDecimalhushString()); + } + + return ""; +} + + void MainWindow::updateChat() { rpc->refreshChat(ui->listChat,ui->memoSizeChat); diff --git a/src/mainwindow.h b/src/mainwindow.h index 3867de6..ca4d8cc 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -51,6 +51,7 @@ public: QString doSendChatTxValidations(Tx tx); QString doSendChatMoneyTxValidations(Tx tx); QString doSendRequestTxValidations(Tx tx); + QString doSendChatMoneyRequestTxValidations(Tx tx); QString getCid(); QString getAmt(); QString getMoneyMemo(); @@ -143,6 +144,7 @@ private: Tx createTxFromChatPage(); Tx createTxForSafeContactRequest(); Tx createTxFromSendChatPage(); + Tx createTxFromSendRequestChatPage(); void encryptWallet(); void removeWalletEncryption(); @@ -152,6 +154,7 @@ private: void sendButton(); void sendChat(); void sendMoneyChat(); + void sendMoneyRequestChat(); void addContact(); void ContactRequest();