diff --git a/src/Makefile.am b/src/Makefile.am index a49073fae..f51987363 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -42,6 +42,7 @@ BITCOIN_CORE_H = \ core.h \ crypter.h \ db.h \ + encryptionutils.h \ richlistdb.h \ servicelistdb.h \ serviceitemlistdb.h \ @@ -148,6 +149,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet.cpp \ walletdb.cpp \ jeeq.cpp \ + encryptionutils.cpp \ $(BITCOIN_CORE_H) libbitcoin_common_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) diff --git a/src/encryptionutils.cpp b/src/encryptionutils.cpp new file mode 100644 index 000000000..57802411b --- /dev/null +++ b/src/encryptionutils.cpp @@ -0,0 +1,62 @@ +#include "encryptionutils.h" +#include "wallet.h" +#include "key.h" +#include "util.h" +#include "base58.h" +#include "jeeq.h" + +/* + Validate a public key. +*/ +bool validatePublicKeyFromHexStr(std::string pubKeyHex) { + CPubKey pubKey(ParseHex(pubKeyHex)); + return pubKey.IsFullyValid(); +} + +/* + Returns the Smileycoin address derived from a public key +*/ +std::string addressFromPublicKey(std::string pubKeyHex) { + CPubKey pubKey(ParseHex(pubKeyHex)); + CBitcoinAddress address(pubKey.GetID()); + return CBitcoinAddress(pubKey.GetID()).ToString(); +} + +/* + Encrypts a string using a public key. Returns the data as + hex encoded string +*/ +bool encryptString(std::string data, std::string pubKeyHex, std::string &resultHex) { + CPubKey pubKey(ParseHex(pubKeyHex)); + if (!pubKey.IsFullyValid()) { + return false; + } + + std::vector encryptedData = Jeeq::EncryptMessage(pubKey, data); + if (encryptedData.size() == 0) + return false; + + resultHex = HexStr(encryptedData); + return true; +} + +bool decryptData(std::string txData, CBitcoinAddress address, const CWallet *wallet, std::string &decryptedData) { + CKeyID keyID; + if (!address.GetKeyID(keyID)) + return false; + + CKey vchSecret; + if (!wallet->GetKey(keyID, vchSecret)) + return false; + + if (!IsHex(txData)) + return false; + + std::string decryptedString = Jeeq::DecryptMessage(vchSecret, ParseHex(txData)); + + if (decryptedString.length() == 0) + return false; + + decryptedData = HexStr(decryptedString); + return true; +} \ No newline at end of file diff --git a/src/encryptionutils.h b/src/encryptionutils.h new file mode 100644 index 000000000..423b24921 --- /dev/null +++ b/src/encryptionutils.h @@ -0,0 +1,19 @@ +/* + Utility functions used for ElGamal encryption + of data. +*/ + +#ifndef BITCOIN_ENCRYPTIONUTILS_H +#define BITCOIN_ENCRYPTIONUTILS_H + +#include + +class CWallet; +class CBitcoinAddress; + +bool validatePublicKeyFromHexStr(std::string pubKeyHex); +std::string addressFromPublicKey(std::string pubKeyHex); +bool encryptString(std::string data, std::string pubKeyHex, std::string &resultHex); +bool decryptData(std::string txData, CBitcoinAddress address, const CWallet *wallet, std::string &decryptedData); + +#endif \ No newline at end of file diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index c6c6dad0e..bf76cb359 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -157,6 +157,13 @@ + + + + Data can be sent encrypted by receivers public key. + + + @@ -704,6 +711,9 @@ + + + @@ -1266,6 +1276,9 @@ + + + diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a899d1f26..0ebe399f4 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -85,6 +85,14 @@ void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent) widget->setCheckValidator(new BitcoinAddressCheckValidator(parent)); } +void setupSendAddressWidget(QValidatedLineEdit *widget, QWidget *parent) +{ + // Removes validator to allow public keys. + parent->setFocusProxy(widget); + widget->setFont(bitcoinAddressFont()); + widget->setPlaceholderText(QObject::tr("Enter a Smileycoin address (e.g. AafeSfiXVkHpcPmb9nQJTDAE5sKybkJAzz)")); +} + void setupAmountWidget(QLineEdit *widget, QWidget *parent) { QDoubleValidator *amountValidator = new QDoubleValidator(parent); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index cf63f9c6e..418a2ccdc 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -38,6 +38,7 @@ namespace GUIUtil // Set up widgets for address and amounts void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent); + void setupSendAddressWidget(QValidatedLineEdit *widget, QWidget *parent); void setupAmountWidget(QLineEdit *widget, QWidget *parent); // Parse "bitcoin:" URI into recipient object, return true on successful parsing diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 3d0a765e0..375d359cf 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -8,6 +8,7 @@ #include "addressbookpage.h" #include "addresstablemodel.h" #include "guiutil.h" +#include "encryptionutils.h" #include "optionsmodel.h" #include "walletmodel.h" #include "ui_interface.h" @@ -33,11 +34,13 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : ui->addAsData->setPlaceholderText(tr("Enter a message to send with your transfer")); // normal bitcoin address field - GUIUtil::setupAddressWidget(ui->payTo, this); + GUIUtil::setupSendAddressWidget(ui->payTo, this); // just a label for displaying bitcoin address(es) ui->payTo_is->setFont(GUIUtil::bitcoinAddressFont()); ui->addAsData2->addItem("HEX"); ui->addAsData2->addItem("ASCII"); + ui->addAsEncrypted->addItem("Unencrypted"); + ui->addAsEncrypted->addItem("Encrypted"); } SendCoinsEntry::~SendCoinsEntry() @@ -122,6 +125,8 @@ bool SendCoinsEntry::validate() if (!model) return false; + bool isPubKey = validatePublicKeyFromHexStr(ui->payTo->text().toStdString()); + bool isAddress = model->validateAddress(ui->payTo->text()); // Check input validity bool retval = true; @@ -129,12 +134,32 @@ bool SendCoinsEntry::validate() if (recipient.paymentRequest.IsInitialized()) return retval; - if (!model->validateAddress(ui->payTo->text())) + if (!isPubKey && !isAddress) { ui->payTo->setValid(false); retval = false; } + if (ui->addAsEncrypted->currentText() == "Encrypted" && isAddress) { + QMessageBox::critical(0, tr("Public key was rejected!"), tr("Data encryption requires public key instead of address")); + retval = false; + } + + if (ui->addAsEncrypted->currentText() == "Encrypted" && isPubKey) { + std::string encryptedString; + if (encryptString( + hexToAscii(ui->addAsData->text().toStdString()), + ui->payTo->text().toStdString(), + encryptedString) + ) { + if (encryptedString.size() > 160 /* Max 80 bytes * 2 for the hex format */) { + QMessageBox::critical(0, tr("Data input was rejected!"), + tr("The data input is to long for encryption")); + retval = false; + } + } + } + if (!ui->payAmount->validate()) { retval = false; @@ -163,16 +188,44 @@ SendCoinsRecipient SendCoinsEntry::getValue() return recipient; // Normal payment - recipient.address = ui->payTo->text(); + if (validatePublicKeyFromHexStr(ui->payTo->text().toStdString())) { + recipient.address = QString::fromStdString(addressFromPublicKey(ui->payTo->text().toStdString())); + } else { + recipient.address = ui->payTo->text(); + } recipient.label = ui->addAsLabel->text(); recipient.amount = ui->payAmount->value(); recipient.message = ui->messageTextLabel->text(); if(ui->addAsData2->currentText() == "ASCII") { QString asciiData = ui->addAsData->text(); - recipient.data = asciiData.toLatin1().toHex(); + + if (ui->addAsEncrypted->currentText() == "Encrypted") { + // Encrypt the data before setting it onto recipient object + std::string pubKeyHex = ui->payTo->text().toStdString(); + std::string encryptedHexString; + if (encryptString(asciiData.toStdString(), pubKeyHex, encryptedHexString)){ + recipient.data = QString::fromStdString(encryptedHexString); + } + } else { + // Send data unencrypted + recipient.data = asciiData.toLatin1().toHex(); + } } else { - recipient.data = ui->addAsData->text(); + if (ui->addAsEncrypted->currentText() == "Encrypted") { + // Encrypt the data before setting it onto recipient object + QString asciiData = ui->addAsData->text(); + std::string pubKeyHex = ui->payTo->text().toStdString(); + std::string asciiHex = hexToAscii(asciiData.toStdString()); + std::string encryptedHexString; + if (encryptString(asciiHex, pubKeyHex, encryptedHexString)){ + recipient.data = QString::fromStdString(encryptedHexString); + } + } else { + // Send data unencrypted + recipient.data = ui->addAsData->text(); + } + } return recipient; diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 7f4f4059b..4730906d3 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -7,6 +7,8 @@ #include "base58.h" #include "wallet.h" #include "guiutil.h" +#include "encryptionutils.h" +#include "jeeq.h" #include @@ -38,6 +40,10 @@ QList TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(); std::map mapValue = wtx.mapValue; + bool txDataExists = false; + bool txDataIsEncrypted = false; + std::string txData; + if (nNet > 0 || wtx.IsCoinBase()) { // @@ -58,15 +64,30 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // Check if data exists in transaction std::string hexString = HexStr(txout2.scriptPubKey); if (hexString.substr(0, 2) == "6a") { - sub.data += hexString.substr(4, hexString.size()); + txDataExists = true; + txData = hexString.substr(4, hexString.size()); + if (txData.substr(0, 8) == "6a6a0000") // 6a6a0000 start of jeeq encryption header + txDataIsEncrypted = true; } } if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { // Received by Bitcoin Address + CBitcoinAddress addr(address); sub.type = TransactionRecord::RecvWithAddress; - sub.address = CBitcoinAddress(address).ToString(); + sub.address = addr.ToString(); + + // Add data. Decrypt it if needed. + if (txDataExists) { + if (txDataIsEncrypted) { + std::string decryptedData; + if (decryptData(txData, addr, wallet, decryptedData)) + sub.data += decryptedData; + } else { + sub.data += txData; + } + } } else { @@ -109,22 +130,31 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // int64_t nTxFee = nDebit - wtx.GetValueOut(); + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { + const CTxOut &txout2 = wtx.vout[nOut]; + // Check if data exists in transaction + std::string hexString = HexStr(txout2.scriptPubKey); + if (hexString.substr(0, 2) == "6a") { + txDataExists = true; + txData = hexString.substr(4, hexString.size()); + if (txData.substr(0, 8) == "6a6a0000") + txDataIsEncrypted = true; + } + } + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { const CTxOut& txout = wtx.vout[nOut]; TransactionRecord sub(hash, nTime); sub.idx = parts.size(); - // Check if data exists in transaction - std::string hexString = HexStr(txout.scriptPubKey); - if (hexString.substr(0, 2) == "6a") { - sub.data += hexString.substr(4, hexString.size()); - } - - if(wallet->IsMine(txout)) + bool sentToSelf = false; + bool isMine = wallet->IsMine(txout); + if(isMine && !txDataIsEncrypted) { // Ignore parts sent to self, as this is usually the change // from a transaction sent back to our own address. + // If data is encrypted we need the address. continue; } @@ -132,17 +162,38 @@ QList TransactionRecord::decomposeTransaction(const CWallet * if (ExtractDestination(txout.scriptPubKey, address)) { // Sent to Bitcoin Address + CBitcoinAddress addr(address); sub.type = TransactionRecord::SendToAddress; - sub.address = CBitcoinAddress(address).ToString(); + sub.address = addr.ToString(); + + + if (txDataExists) { + if (txDataIsEncrypted) { + std::string decryptedData; + if (decryptData(txData, addr, wallet, decryptedData)) + { + sentToSelf = true; + sub.data += decryptedData; + } + else if (isMine) + { + // We don't need to record change. + continue; + } + } else + sub.data += txData; + } } else { + if (txDataExists) continue; // Transaction with data has already been recorded // Sent to IP, or other non-address transaction like OP_EVAL sub.type = TransactionRecord::SendToOther; sub.address = mapValue["to"]; } int64_t nValue = txout.nValue; + if (sentToSelf) nValue = 0; /* Add fee to first output */ if (nTxFee > 0) { @@ -265,4 +316,4 @@ QString TransactionRecord::getTxID() const QString TransactionRecord::formatSubTxId(const uint256 &hash, int vout) { return QString::fromStdString(hash.ToString() + strprintf("-%03d", vout)); -} +} \ No newline at end of file diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index b6c370bef..9230059b5 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -180,6 +180,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 1) ConvertTo(params[1]); if (strMethod == "keypoolrefill" && n > 0) ConvertTo(params[0]); if (strMethod == "getrawmempool" && n > 0) ConvertTo(params[0]); + if (strMethod == "sendcodedmessage" && n > 1) ConvertTo(params[1]); return params; } diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 999a98cc0..2d5f5d91f 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -13,6 +13,7 @@ #include "richlistdb.h" #include "servicelistdb.h" #include "serviceitemlistdb.h" +#include "jeeq.h" #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" #ifdef ENABLE_WALLET @@ -831,11 +832,11 @@ Value getubilist(const Array& params, bool fHelp) throw runtime_error("getubilist \"address\"\n" "Returns all UBI recipient addresses that belong to the specified UBI service address\n" ); - + CBitcoinAddress address = CBitcoinAddress(params[0].get_str()); if(!address.IsValid()) throw runtime_error("Not a valid Smileycoin address"); - + std::multiset > > services; ServiceList.GetServiceAddresses(services); bool isUbi = false; @@ -856,7 +857,7 @@ Value getubilist(const Array& params, bool fHelp) ServiceItemList.GetUbiList(info); int i = 0; - + for(std::set< std::pair< std::string, std::tuple > >::const_iterator it = info.begin(); it!=info.end(); it++ ) { std::string toAddress = get<1>(it->second); @@ -864,7 +865,7 @@ Value getubilist(const Array& params, bool fHelp) obj.push_back(Pair("UBI Recipient Address: ", it->first)); } } - + return obj; } @@ -875,18 +876,18 @@ Value getdexlist(const Array& params, bool fHelp) throw runtime_error("getdexlist \"address\"\n" "Returns all DEX addresses that belong to the specified DEX service address\n" ); - + Object obj; std::multiset>> info; ServiceItemList.GetDexList(info); - + for(std::set< std::pair< std::string, std::tuple > >::const_iterator it = info.begin(); it!=info.end(); it++ ) { obj.push_back(Pair("DEX Address: ", it->first)); obj.push_back(Pair("Description: ", get<2>(it->second))); } - + return obj; } @@ -897,18 +898,18 @@ Value getnpolist(const Array& params, bool fHelp) throw runtime_error("getnpolist\n" "Returns all non profit organization addresses\n" ); - + Object obj; /*std::multiset>> info; ServiceItemList.GetNpoList(info); - + for(std::set< std::pair< CScript, std::tuple > >::const_iterator it = info.begin(); it!=info.end(); it++ ) { obj.push_back(Pair("Npo name: ", get<1>(it->second))); obj.push_back(Pair("Npo address: ", get<2>(it->second))); }*/ - + return obj; } @@ -919,18 +920,18 @@ Value getbooklist(const Array& params, bool fHelp) throw runtime_error("getbooklist \"address\"\n" "Returns all book chapters that belong to the specified book service address\n" ); - + Object obj; std::multiset>> info; ServiceItemList.GetBookList(info); - + //TODO bæta við book name, book author og year for(std::set< std::pair< std::string, std::tuple > >::const_iterator it = info.begin(); it!=info.end(); it++ ) { obj.push_back(Pair("Chapter Number: ", get<2>(it->second))); obj.push_back(Pair("Chapter Address: ", it->first)); } - + return obj; } @@ -944,7 +945,7 @@ Value getaddressinfo(const Array& params, bool fHelp) if(!address.IsValid()) throw runtime_error("Invalid Smileycoin address"); Object obj; - CScript key; + CScript key; key.SetDestination(address.Get()); std::pair value; if(!pcoinsTip->GetAddressInfo(key, value)) @@ -952,7 +953,7 @@ Value getaddressinfo(const Array& params, bool fHelp) obj.push_back(Pair("Balance", ValueFromAmount(value.first))); obj.push_back(Pair("Height", value.second)); - return obj; + return obj; } #ifdef ENABLE_WALLET @@ -1138,7 +1139,7 @@ Value createmultisig(const Array& params, bool fHelp) result.push_back(Pair("address", address.ToString())); result.push_back(Pair("redeemScript", HexStr(inner.begin(), inner.end()))); - return result; + return result; } Value verifymessage(const Array& params, bool fHelp) @@ -1193,4 +1194,73 @@ Value verifymessage(const Array& params, bool fHelp) return (pubkey.GetID() == keyID); } +Value sendcodedmessage(const Array ¶ms, bool fHelp) +{ + if (fHelp || params.size() != 3) + throw runtime_error( + "sendcodedmessage \"publickey\" \"value\" \"message\"\n" + "\nSend an encrypted message\n" + "\nArguments:\n" + "1. \"publickey\" (string, required) Hex encoded public key for encryptnh. Derives address \n" + ". \"value\" (number, required) The amount of coins to be sent along with the message \n" + "2. \"message\" (string, required) The message that was signed.\n" + "\nResult:\n" + "hex (string) The transaction hash in hex.\n" + "\nExamples:\n" + "\nUnlock the wallet for 30 seconds\n" + + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + + "\nSend the message\n" + HelpExampleCli("sendcodedmessage", "\"03d9f7c799c1fb80334ba8244bd73061917a3f2f5fd20ac9549d6992cb45ae52c4\" \"100\" \"my message\"")); + + string strPubKeyHex = params[0].get_str(); + double value = params[1].get_real(); + string strMessage = params[2].get_str(); + + if (!IsHex(strPubKeyHex)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not a valid hex format for public key"); + + const CPubKey pubKey(ParseHex(strPubKeyHex)); + if ( !pubKey.IsFullyValid() ) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not a valid public key hex"); + + // Encrypt message using the public key + using namespace Jeeq; + std::vector encryptedMessage = EncryptMessage(pubKey, strMessage); + + // OP_RETURN data can not be over 80 bytes + if (encryptedMessage.size() > 80) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Message is too long"); + + // Address to send to + CBitcoinAddress address(pubKey.GetID()); + // Build transaction outputs + CScript scriptPubKey; + CScript messageScript; + scriptPubKey.SetDestination(address.Get()); + messageScript << OP_RETURN << encryptedMessage; + + + EnsureWalletIsUnlocked(); + + // Prepare parameters for CreateTransaction()x + vector> sendVec; + sendVec.push_back(make_pair(scriptPubKey, AmountFromValue(value))); + sendVec.push_back(make_pair(messageScript, 0)); + + + CWalletTx wtx; + CReserveKey keyChange(pwalletMain); + int64_t nFeeRequired = 0; + string strFailReason; + + // Create and send tranasction + bool fCreated = pwalletMain->CreateTransaction(sendVec, wtx, keyChange, nFeeRequired, strFailReason); + if (!fCreated) + throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + if (!pwalletMain->CommitTransaction(wtx, keyChange)) + throw JSONRPCError(RPC_WALLET_ERROR, "Committing transaction failed."); + + return wtx.GetHash().GetHex(); +} + #pragma clang diagnostic pop + diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index c4999d599..dd073a366 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -325,6 +325,7 @@ static const CRPCCommand vRPCCommands[] = { "walletlock", &walletlock, true, false, true }, { "walletpassphrasechange", &walletpassphrasechange, false, false, true }, { "walletpassphrase", &walletpassphrase, true, false, true }, + { "sendcodedmessage", &sendcodedmessage, true, false, true }, /* Wallet-enabled mining */ { "getgenerate", &getgenerate, true, false, false }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 91284fd8b..c858972d6 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -148,6 +148,7 @@ extern json_spirit::Value getdexlist(const json_spirit::Array& params, bool fHel extern json_spirit::Value getnpolist(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getbooklist(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getaddressinfo(const json_spirit::Array& params, bool fHelp); //in rpcmisc.cpp +extern json_spirit::Value sendcodedmessage(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getgenerate(const json_spirit::Array& params, bool fHelp); // in rpcmining.cpp extern json_spirit::Value setgenerate(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getnetworkhashps(const json_spirit::Array& params, bool fHelp);