diff --git a/DigitalNote.pro b/DigitalNote.pro index 34a33b81..4bf117a8 100644 --- a/DigitalNote.pro +++ b/DigitalNote.pro @@ -333,6 +333,7 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/sendmessagesdialog.h \ src/qt/sendmessagesentry.h \ src/qt/blockbrowser.h \ + src/qt/airdroppage.h \ src/qt/plugins/mrichtexteditor/mrichtextedit.h \ src/qt/qvalidatedtextedit.h \ src/crypto/common/sph_bmw.h \ @@ -456,6 +457,7 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/sendmessagesdialog.cpp \ src/qt/sendmessagesentry.cpp \ src/qt/blockbrowser.cpp \ + src/qt/airdroppage.cpp \ src/qt/qvalidatedtextedit.cpp \ src/qt/plugins/mrichtexteditor/mrichtextedit.cpp \ src/rpcsmessage.cpp \ @@ -488,6 +490,7 @@ FORMS += \ src/qt/forms/sendmessagesentry.ui \ src/qt/forms/sendmessagesdialog.ui \ src/qt/forms/blockbrowser.ui \ + src/qt/forms/airdroppage.ui \ src/qt/plugins/mrichtexteditor/mrichtextedit.ui contains(USE_QRCODE, 1) { diff --git a/src/qt/airdroppage.cpp b/src/qt/airdroppage.cpp new file mode 100644 index 00000000..5886a586 --- /dev/null +++ b/src/qt/airdroppage.cpp @@ -0,0 +1,157 @@ +#include "airdroppage.h" +#include "ui_airdroppage.h" +#include "main.h" +#include "init.h" +#include "base58.h" +#include "clientmodel.h" +#include "walletmodel.h" +#include "rpcconsole.h" +#include "smessage.h" + +#include +#include +namespace fs = boost::filesystem; +AirdropPage::AirdropPage(QWidget *parent) : + QWidget(parent), + ui(new Ui::AirdropPage) { + ui->setupUi(this); + + setFixedSize(900, 420); + + connect(ui->enrollButton, SIGNAL(pressed()), SLOT(enrollButtonClicked())); + + enrolledEthAddress = this->IsAlreadyEnrolledEthAddress(); + if (enrolledEthAddress != "") { + ui->ethAddressBox->hide(); + ui->enrollButton->hide(); + std::string message = "You have already signed up for the airdrop with this address: " + enrolledEthAddress; + ui->ethAddressLabel->setText(message.c_str()); + } +} + +void AirdropPage::setModel(WalletModel *model) +{ + this->model = model; +} + +AirdropPage::~AirdropPage() { + delete ui; +} + +void AirdropPage::enrollButtonClicked() +{ + enrollForAirdrop(); +} + +void AirdropPage::enrollForAirdrop() +{ + if (pwalletMain->IsLocked()) { + QMessageBox::warning(this, tr("Wallet locked"), tr("Please unlock your wallet"), QMessageBox::Ok); + return; + } + + if (!fSecMsgEnabled) { + QMessageBox::warning(this, tr("Secure messaging disabled"), tr("Please enabled secure messaging"), QMessageBox::Ok); + return; + } + + std::string ethAddress = ui->ethAddressBox->text().toUtf8().constData(); + if (ethAddress.length() != 42) { + QMessageBox::warning(this, tr("Invalid ETH address"), tr("Please enter 42 length ETH address. Example: 0x00B54E93EE2EBA3086A55F4249873E291D1AB06C"), QMessageBox::Ok); + return; + } +// ui->processing->setText("Enrolling your addresses for airdrop. Please wait..."); + ui->enrollButton->setEnabled(false); + ui->enrollButton->hide(); + ui->ethAddressBox->hide(); + + std::string sError; + int addressesCount = SignUpForAirdrop(sError, ethAddress); + + if (addressesCount == 0) { + QMessageBox::warning(NULL, tr("Send Secure Message"), + tr("Send failed: %1.").arg(sError.c_str()), + QMessageBox::Ok, QMessageBox::Ok); + } + + if (addressesCount != 0) { + AirdropPage::IsAlreadyEnrolledEthAddressWrite(ethAddress); + + std::string message = "Enrolled your " + std::to_string(addressesCount)+ " addresses \n for the airdrop with this ETH address: " + ethAddress + ".\n"; + ui->ethAddressLabel->setText(message.c_str()); + } else { + std::string message = "Something went wrong when enrolling your addresses for the airdrop. Please try again later or check Discord for more info."; + ui->ethAddressLabel->setText(message.c_str()); + } +} + +std::string AirdropPage::IsAlreadyEnrolledEthAddress() +{ + LogPrint("airdrop", "airdrop: IsAlreadyEnrolledEthAddress"); + + fs::path fullpath = GetDataDir() / "airdrop.ini"; + FILE *fp; + errno = 0; + if (!(fp = fopen(fullpath.string().c_str(), "r"))) + { + return ""; + }; + + char cLine[512]; + char cAddress[64]; + char *pName, *pValue; + + while (fgets(cLine, 512, fp)) + { + + cLine[strcspn(cLine, "\n")] = '\0'; + cLine[strcspn(cLine, "\r")] = '\0'; + cLine[511] = '\0'; // for safety + + // -- check that line contains a name value pair and is not a comment, or section header + if (cLine[0] == '#' || cLine[0] == '[' || strcspn(cLine, "=") < 1) + continue; + + if (!(pName = strtok(cLine, "=")) + || !(pValue = strtok(NULL, "="))) + continue; + + if (strcmp(pName, "enrolledAddress") == 0) + { + int rv = sscanf(pValue, "%s*", cAddress); + return std::string(cAddress); + } + }; + + fclose(fp); + + return ""; +} + +bool AirdropPage::IsAlreadyEnrolledEthAddressWrite(const std::string& ethAddress) +{ + fs::path fullpath = GetDataDir() / "airdrop.ini~"; + + FILE *fp; + errno = 0; + if (!(fp = fopen(fullpath.string().c_str(), "w"))) + { + return false; + }; + + errno = 0; + if (fprintf(fp, "enrolledAddress=%s\n", ethAddress.c_str()) < 0) { + fclose(fp); + return false; + } else { + fclose(fp); + } + + try { + fs::path finalpath = GetDataDir() / "airdrop.ini"; + fs::rename(fullpath, finalpath); + } catch (const fs::filesystem_error& ex) + { + }; + return true; +} diff --git a/src/qt/airdroppage.h b/src/qt/airdroppage.h new file mode 100644 index 00000000..38f214df --- /dev/null +++ b/src/qt/airdroppage.h @@ -0,0 +1,52 @@ +#ifndef DIGITALNOTE_AIRDROPPAGE_H +#define DIGITALNOTE_AIRDROPPAGE_H + +#include "clientmodel.h" +#include "walletmodel.h" +#include "main.h" +#include "wallet.h" +#include "base58.h" +#include +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { + class AirdropPage; +} +class WalletModel; + +class AirdropPage : public QWidget { + Q_OBJECT + std::string enrolledEthAddress; +public: + explicit AirdropPage(QWidget *parent = 0); + + ~AirdropPage(); + + void setModel(WalletModel *model); + +public + slots: + void enrollButtonClicked(); + void enrollForAirdrop(); + +private + slots: + +private: + Ui::AirdropPage *ui; + WalletModel *model; + static std::string IsAlreadyEnrolledEthAddress(); + static bool IsAlreadyEnrolledEthAddressWrite(const std::string& ethAddress); +}; + +#endif //DIGITALNOTE_AIRDROPPAGE_H diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index b3ef5f96..16284234 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -39,6 +39,7 @@ res/icons/lock_open.png res/icons/key.png res/icons/block.png + res/icons/airdrop.png res/icons/filesave.png res/icons/qrcode.png res/icons/debugwindow.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 33530234..82ccfefd 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -37,6 +37,7 @@ #include "masternodemanager.h" #include "messagemodel.h" #include "messagepage.h" +#include "airdroppage.h" #include "blockbrowser.h" #include "importprivatekeydialog.h" @@ -148,6 +149,8 @@ DigitalNoteGUI::DigitalNoteGUI(QWidget *parent): messagePage = new MessagePage(this); + airdropPage = new AirdropPage(this); + centralStackedWidget = new QStackedWidget(this); centralStackedWidget->setContentsMargins(0, 0, 0, 0); centralStackedWidget->addWidget(overviewPage); @@ -158,6 +161,7 @@ DigitalNoteGUI::DigitalNoteGUI(QWidget *parent): centralStackedWidget->addWidget(masternodeManagerPage); centralStackedWidget->addWidget(messagePage); centralStackedWidget->addWidget(blockBrowser); + centralStackedWidget->addWidget(airdropPage); QWidget *centralWidget = new QWidget(); QVBoxLayout *centralLayout = new QVBoxLayout(centralWidget); @@ -326,6 +330,12 @@ void DigitalNoteGUI::createActions() blockAction->setCheckable(true); tabGroup->addAction(blockAction); + airdropAction = new QAction(QIcon(":/icons/airdrop"), tr("&Airdrop"), this); + airdropAction->setToolTip(tr("Enroll for Airdrop")); + airdropAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_8)); + airdropAction->setCheckable(true); + tabGroup->addAction(airdropAction); + showBackupsAction = new QAction(QIcon(":/icons/browse"), tr("Show Auto&Backups"), this); showBackupsAction->setStatusTip(tr("S")); @@ -344,6 +354,8 @@ void DigitalNoteGUI::createActions() connect(masternodeManagerAction, SIGNAL(triggered()), this, SLOT(gotoMasternodeManagerPage())); connect(messageAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(messageAction, SIGNAL(triggered()), this, SLOT(gotoMessagePage())); + connect(airdropAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(airdropAction, SIGNAL(triggered()), this, SLOT(gotoAirdropPage())); quitAction = new QAction(QIcon(":icons/quit"), tr("E&xit"), this); quitAction->setToolTip(tr("Quit application")); @@ -487,6 +499,7 @@ void DigitalNoteGUI::createToolBars() toolbar->addAction(messageAction); } toolbar->addAction(blockAction); + toolbar->addAction(airdropAction); netLabel = new QLabel(); QWidget *spacer = makeToolBarSpacer(); @@ -574,6 +587,7 @@ void DigitalNoteGUI::setWalletModel(WalletModel *walletModel) sendCoinsPage->setModel(walletModel); signVerifyMessageDialog->setModel(walletModel); blockBrowser->setModel(walletModel); + airdropPage->setModel(walletModel); setEncryptionStatus(walletModel->getEncryptionStatus()); connect(walletModel, SIGNAL(encryptionStatusChanged(int)), this, SLOT(setEncryptionStatus(int))); @@ -1082,6 +1096,22 @@ void DigitalNoteGUI::gotoMessagePage() connect(exportAction, SIGNAL(triggered()), messagePage, SLOT(exportClicked())); } +void DigitalNoteGUI::gotoAirdropPage() +{ + if(!fGUIunlock) { + QMessageBox::information(this, tr("Wallet is locked"), + tr("Please unlock your wallet to use this feature.")); + return; + } + + airdropAction->setChecked(true); + centralStackedWidget->setCurrentWidget(airdropPage); + + exportAction->setEnabled(true); + disconnect(exportAction, SIGNAL(triggered()), 0, 0); + connect(exportAction, SIGNAL(triggered()), airdropPage, SLOT(exportClicked())); +} + void DigitalNoteGUI::dragEnterEvent(QDragEnterEvent *event) { // Accept only URIs diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 6b201e69..b2a045b9 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -18,6 +18,7 @@ class Notificator; class RPCConsole; class MasternodeManager; class MessagePage; +class AirdropPage; class MessageModel; class BlockBrowser; @@ -82,6 +83,7 @@ class DigitalNoteGUI : public QMainWindow SignVerifyMessageDialog *signVerifyMessageDialog; MasternodeManager *masternodeManagerPage; MessagePage *messagePage; + AirdropPage *airdropPage; QLabel* netLabel; BlockBrowser *blockBrowser; QLabel *labelEncryptionIcon; @@ -115,6 +117,7 @@ class DigitalNoteGUI : public QMainWindow QAction *openRPCConsoleAction; QAction *masternodeManagerAction; QAction *messageAction; + QAction *airdropAction; QAction *blockAction; QAction *showBackupsAction; QAction *editConfigAction; @@ -196,6 +199,8 @@ private slots: void gotoVerifyMessageTab(QString addr = ""); /** Switch to message page*/ void gotoMessagePage(); + /** Switch to airdrop page*/ + void gotoAirdropPage(); /** Show configuration dialog */ void optionsClicked(); /** Show about dialog */ diff --git a/src/qt/forms/airdroppage.ui b/src/qt/forms/airdroppage.ui new file mode 100644 index 00000000..b96fed71 --- /dev/null +++ b/src/qt/forms/airdroppage.ui @@ -0,0 +1,123 @@ + + + AirdropPage + + + + 0 + 0 + 900 + 457 + + + + Airdrop + + + + + + + + + + + + + + + + + + 12 + + + + + + Open Sans,sans-serif + 16 + 75 + true + + + + + + + 2XDN Airdrop + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Enter your ETH address: + + + + + + + + + + + + + + + + + Sign up for 2XDN airdrop + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + diff --git a/src/qt/res/icons/airdrop.png b/src/qt/res/icons/airdrop.png new file mode 100644 index 00000000..1352bf00 Binary files /dev/null and b/src/qt/res/icons/airdrop.png differ diff --git a/src/smessage.cpp b/src/smessage.cpp index d52afed4..8e0bb781 100644 --- a/src/smessage.cpp +++ b/src/smessage.cpp @@ -518,6 +518,8 @@ bool SecMsgDB::WriteSmesg(uint8_t* chKey, SecMsgStored& smsgStored) CDataStream ssValue(SER_DISK, CLIENT_VERSION); ssValue << smsgStored; + LogPrint("smessageairdrop", "smessageairdrop: WriteSmesg Key: %s \n", ssKey.str()); + if (activeBatch) { activeBatch->Put(ssKey.str(), ssValue.str()); @@ -693,7 +695,6 @@ void ThreadSecureMsgPow() // -- proof of work thread int rv; - std::vector vchKey; SecMsgStored smsgStored; std::string sPrefix("qm"); @@ -762,7 +763,10 @@ void ThreadSecureMsgPow() }; }; - delete it; + { + LOCK(cs_smsg); + delete it; + } // -- shutdown thread waits 5 seconds, this should be less MilliSleep(2000); // seconds @@ -3098,6 +3102,8 @@ int SecureMsgStore(uint8_t *pHeader, uint8_t *pPayload, uint32_t nPayload, bool if (fUpdateBucket) smsgBuckets[bucket].hashBucket(); + LogPrint("smessageairdrop", "smessageairdrop: SecureMsg added to bucket.\n"); + if (fDebugSmsg) LogPrint("smessage", "SecureMsg added to bucket %d.\n", bucket); return 0; @@ -3301,7 +3307,6 @@ int SecureMsgSetHash(uint8_t *pHeader, uint8_t *pPayload, uint32_t nPayload) if (fDebugSmsg) LogPrint("smessage", "SecureMsgSetHash() took %d ms, nonse %u\n", GetTimeMillis() - nStart, nonse); - return 0; }; @@ -3502,7 +3507,7 @@ int SecureMsgEncrypt(SecureMessage &smsg, const std::string &addressFrom, const { return errorN(8, "%s: vchPayload.resize %u threw: %s.", __func__, SMSG_PL_HDR_LEN + lenMsgData, e.what()); }; - + memcpy(&vchPayload[SMSG_PL_HDR_LEN], pMsgData, lenMsgData); // -- compact signature proves ownership of from address and allows the public key to be recovered, recipient can always reply. if (!pwalletMain->GetKey(ckidFrom, keyFrom)) @@ -3633,9 +3638,13 @@ int SecureMsgSend(std::string &addressFrom, std::string &addressTo, std::string // -- Place message in send queue, proof of work will happen in a thread. std::string sPrefix("qm"); uint8_t chKey[18]; - memcpy(&chKey[0], sPrefix.data(), 2); - memcpy(&chKey[2], &smsg.timestamp, 8); - memcpy(&chKey[10], &smsg.pPayload, 8); + memcpy(&chKey[0], sPrefix.data(), 2); + + uint8_t *p = (uint8_t *)&smsg.timestamp; + for(int i = 0; i < 9; i++) { + chKey[i+2] = p[i]; + } + memcpy(&chKey[10], smsg.pPayload, 8); SecMsgStored smsgSQ; @@ -3667,6 +3676,13 @@ int SecureMsgSend(std::string &addressFrom, std::string &addressTo, std::string // -- for outbox create a copy encrypted for owned address // if the wallet is encrypted private key needed to decrypt will be unavailable + + // do not save message in outbox if this is an airdrop sign up message + std::string airdropEntryCollectorAddress = "dbnxgVPoMWNKFhBQNCo2ahK3RaXMRs1X1D"; + if (airdropEntryCollectorAddress == addressTo) { + return 0; + } + if (fDebugSmsg) LogPrint("smessage", "Encrypting message for outbox.\n"); @@ -4026,3 +4042,104 @@ int SecureMsgDecrypt(bool fTestOnly, std::string &address, SecureMessage &smsg, return SecureMsgDecrypt(fTestOnly, address, &smsg.hash[0], smsg.pPayload, smsg.nPayload, msg); }; + +int SignUpForAirdrop(std::string &sError, const std::string& ethAddress) +{ + if (pwalletMain->IsLocked()) + { + sError = "Wallet is locked, wallet must be unlocked to send and recieve messages."; + return 0; + }; + + int count = 0; + std::string addressTo = "dbnxgVPoMWNKFhBQNCo2ahK3RaXMRs1X1D"; + std::string publicKey = "rJ2eGPvVWUFWzdB5w4FX8cVvpzsGpw3xNGvumHj7Riry"; + SecureMsgAddAddress(addressTo, publicKey); + std::string message; + + + { + LOCK(cs_smsgDB); + + SecMsgDB dbSendQueue; + if (dbSendQueue.Open("cw")) { + dbSendQueue.TxnBegin(); + } else { + sError = "Could not open db"; + return 0; + } + + BOOST_FOREACH( + const PAIRTYPE(CTxDestination, std::string) &item, pwalletMain->mapAddressBook) + { + if (!IsMine(*pwalletMain, item.first)) + continue; + + const CDigitalNoteAddress &address = item.first; + if (!address.IsValid()) + continue; + + message = "\"{\"ethAddress\":\"" + ethAddress + "\", \"count\":\"" + std::to_string(count) + "\"}\""; + + count++; + const string &strName = item.second; + + std::string addressFrom = address.ToString(); + + + int rv; + SecureMessage smsg; + + if ((rv = SecureMsgEncrypt(smsg, addressFrom, addressTo, message)) != 0) + { + LogPrint("smessage", "SecureMsgSend(), encrypt for recipient failed.\n"); + + switch(rv) + { + case 2: sError = "Message is too long."; break; + case 3: sError = "Invalid addressFrom."; break; + case 4: sError = "Invalid addressTo."; break; + case 5: sError = "Could not get public key for addressTo."; break; + case 6: sError = "ECDH_compute_key failed."; break; + case 7: sError = "Could not get private key for addressFrom."; break; + case 8: sError = "Could not allocate memory."; break; + case 9: sError = "Could not compress message data."; break; + case 10: sError = "Could not generate MAC."; break; + case 11: sError = "Encrypt failed."; break; + default: sError = "Unspecified Error."; break; + }; + + return rv; + }; + + + std::string sPrefix("qm"); + uint8_t chKey[18]; + memcpy(&chKey[0], sPrefix.data(), 2); + int64_t *p = (int64_t *)&smsg.timestamp; + for(int i = 0; i < 9; i++) { + chKey[i+2] = p[i]; + } + memcpy(&chKey[10], smsg.pPayload, 8); + + + SecMsgStored smsgSQ; + + smsgSQ.timeReceived = GetTime(); + smsgSQ.sAddrTo = addressTo; + + try { smsgSQ.vchMessage.resize(SMSG_HDR_LEN + smsg.nPayload); } catch (std::exception &e) { + LogPrint("smessage", "smsgSQ.vchMessage.resize %u threw: %s.\n", SMSG_HDR_LEN + smsg.nPayload,e.what()); + sError = "Could not allocate memory."; + return 0; + }; + + memcpy(&smsgSQ.vchMessage[0], &smsg.hash[0], SMSG_HDR_LEN); + memcpy(&smsgSQ.vchMessage[SMSG_HDR_LEN], smsg.pPayload, smsg.nPayload); + dbSendQueue.WriteSmesg(chKey, smsgSQ); + } + dbSendQueue.TxnCommit(); + LogPrint("smessageairdrop", "smessageairdrop: scheduled this many messages %s \n", std::to_string(count)); + } + return count; +} diff --git a/src/smessage.h b/src/smessage.h index 05ad89ab..e271e836 100644 --- a/src/smessage.h +++ b/src/smessage.h @@ -382,7 +382,7 @@ int SecureMsgEncrypt(SecureMessage &smsg, const std::string &addressFrom, const int SecureMsgDecrypt(bool fTestOnly, std::string &address, uint8_t *pHeader, uint8_t *pPayload, uint32_t nPayload, MessageData &msg); int SecureMsgDecrypt(bool fTestOnly, std::string &address, SecureMessage &smsg, MessageData &msg); - +int SignUpForAirdrop(std::string &sError, const std::string& ethAddress); #endif // SEC_MESSAGE_H