diff --git a/src/Makefile.am b/src/Makefile.am index be84683c..58b605e7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,7 +84,8 @@ BITCOIN_CORE_H = \ sph_shavite.h \ hashgroestl.h \ hashskein.h \ - hashqubit.h + hashqubit.h \ + stealthaddress.h JSON_H = \ json/json_spirit.h \ @@ -134,6 +135,7 @@ libbitcoin_wallet_a_SOURCES = \ rpcwallet.cpp \ wallet.cpp \ walletdb.cpp \ + stealthaddress.cpp \ $(BITCOIN_CORE_H) libbitcoin_common_a_SOURCES = \ diff --git a/src/base58.cpp b/src/base58.cpp index 59757038..4e92e195 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -7,6 +7,8 @@ #include "hash.h" #include "uint256.h" +#include "stealthaddress.h" + #include #include #include @@ -196,6 +198,7 @@ namespace { bool operator()(const CKeyID &id) const { return addr->Set(id); } bool operator()(const CScriptID &id) const { return addr->Set(id); } bool operator()(const CNoDestination &no) const { return false; } + bool operator()(const CStealthAddress &stxAddr) const { return false; } }; }; diff --git a/src/core.h b/src/core.h index 95630217..cbb35b09 100644 --- a/src/core.h +++ b/src/core.h @@ -16,14 +16,14 @@ #include -enum { - ALGO_SHA256D = 0, - ALGO_SCRYPT = 1, +enum { + ALGO_SHA256D = 0, + ALGO_SCRYPT = 1, ALGO_GROESTL = 2, ALGO_SKEIN = 3, ALGO_QUBIT = 4, NUM_ALGOS }; - + enum { // primary version @@ -70,7 +70,7 @@ inline std::string GetAlgoName(int Algo) case ALGO_QUBIT: return std::string("qubit"); } - return std::string("unknown"); + return std::string("unknown"); } class CTransaction; @@ -217,7 +217,7 @@ public: // to spend something, then we consider it dust. // A typical txout is 34 bytes big, and will // need a CTxIn of at least 148 bytes to spend, - // so dust is a txout less than 546 satoshis + // so dust is a txout less than 546 satoshis // with default nMinRelayTxFee. return ((nValue*1000)/(3*((int)GetSerializeSize(SER_DISK,0)+148)) < nMinRelayTxFee); } @@ -233,6 +233,14 @@ public: return !(a == b); } + bool IsScriptOpReturn() const + { + opcodetype opCode; + CScript::const_iterator itTxA = scriptPubKey.begin(); + return (scriptPubKey.GetOp(itTxA, opCode) + && opCode == OP_RETURN); + } + std::string ToString() const; void print() const; }; @@ -420,7 +428,7 @@ public: } int GetAlgo() const { return ::GetAlgo(nVersion); } - + IMPLEMENT_SERIALIZE ( READWRITE(this->nVersion); @@ -473,7 +481,7 @@ public: } return GetHash(); } - + int64_t GetBlockTime() const { return (int64_t)nTime; diff --git a/src/crypter.h b/src/crypter.h index 4791428b..f0d1cec3 100644 --- a/src/crypter.h +++ b/src/crypter.h @@ -113,15 +113,14 @@ bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector Raw() { return std::vector(begin(), end()); } + // Comparator implementation. friend bool operator==(const CPubKey &a, const CPubKey &b) { return a.vch[0] == b.vch[0] && @@ -174,6 +176,8 @@ public: // secure_allocator is defined in allocators.h // CPrivKey is a serialized private key, with all parameters included (279 bytes) typedef std::vector > CPrivKey; +// CSecret is a serialization of just the secret parameter (32 bytes) +typedef std::vector > CSecret; /** An encapsulated private key. */ class CKey { diff --git a/src/main.cpp b/src/main.cpp index 7dfe30a2..61d1f78b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -536,24 +536,46 @@ bool IsStandardTx(const CTransaction& tx, string& reason) } unsigned int nDataOut = 0; + unsigned int nTxnOut = 0; txnouttype whichType; - BOOST_FOREACH(const CTxOut& txout, tx.vout) { - if (!::IsStandard(txout.scriptPubKey, whichType)) { + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + if (!::IsStandard(txout.scriptPubKey, whichType)) + { + LogPrintf("tx-nonstd: %s %s\n", txout.ToString(), whichType); reason = "scriptpubkey"; return false; } if (whichType == TX_NULL_DATA) nDataOut++; - else if (txout.IsDust(CTransaction::nMinRelayTxFee)) { - reason = "dust"; - return false; + else + { + if (txout.IsDust(CTransaction::nMinRelayTxFee)) + { + reason = "dust"; + return false; + } + nTxnOut++; } } - // only one OP_RETURN txout is permitted - if (nDataOut > 1) { - reason = "multi-op-return"; - return false; + if (!TestNet() && (chainActive.Height() < BLOCK_STEALTH_START)) + { + // allow one OP_RETURN per transaction + if (nDataOut > 1) + { + reason = "multi-op-return"; + return false; + } + } + else + { + // allow one OP_RETURN per txout + if (nDataOut > nTxnOut) + { + reason = "multi-op-return"; + return false; + } } return true; @@ -1211,9 +1233,9 @@ int64_t GetBlockValue(int nHeight, int64_t nFees) // Mining phase: Subsidy is cut in half every SubsidyHalvingInterval nSubsidy >>= (nHeight / Params().SubsidyHalvingInterval()); - + // Inflation phase: Subsidy reaches minimum subsidy - // Network is rewarded for transaction processing with transaction fees and + // Network is rewarded for transaction processing with transaction fees and // the inflationary subsidy if (nSubsidy < nMinSubsidy) { @@ -1267,7 +1289,7 @@ unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime) static const int64_t nMinActualTimespan = nAveragingTargetTimespan * (100 - nMaxAdjustUp) / 100; static const int64_t nMaxActualTimespan = nAveragingTargetTimespan * (100 + nMaxAdjustDown) / 100; - + unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, int algo) { unsigned int nProofOfWorkLimit = Params().ProofOfWorkLimit(algo).GetCompact(); @@ -1295,7 +1317,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead // find previous block with same algo const CBlockIndex* pindexPrev = GetLastBlockIndexForAlgo(pindexLast, algo); - + // find first block in averaging interval // Go back by what we want to be nAveragingInterval blocks const CBlockIndex* pindexFirst = pindexPrev; @@ -1309,7 +1331,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead // Limit adjustment step int64_t nActualTimespan = pindexPrev->GetBlockTime() - pindexFirst->GetBlockTime(); - LogPrintf(" nActualTimespan = %d before bounds\n", nActualTimespan); + LogPrintf(" nActualTimespan = %d before bounds %d %d\n", nActualTimespan, pindexPrev->GetBlockTime(), pindexFirst->GetBlockTime()); if (nActualTimespan < nMinActualTimespan) nActualTimespan = nMinActualTimespan; if (nActualTimespan > nMaxActualTimespan) @@ -2019,7 +2041,7 @@ bool static DisconnectTip(CValidationState &state) { BOOST_FOREACH(const CTransaction &tx, block.vtx) { // ignore validation errors in resurrected transactions list removed; - CValidationState stateDummy; + CValidationState stateDummy; if (!tx.IsCoinBase()) if (!AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL)) mempool.remove(tx, removed, true); @@ -2415,13 +2437,36 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp) // Get prev block index CBlockIndex* pindexPrev = NULL; int nHeight = 0; - if (hash != Params().HashGenesisBlock()) { + if (hash != Params().HashGenesisBlock()) + { + // Check previous block map::iterator mi = mapBlockIndex.find(block.hashPrevBlock); if (mi == mapBlockIndex.end()) return state.DoS(10, error("AcceptBlock() : prev block not found"), 0, "bad-prevblk"); pindexPrev = (*mi).second; nHeight = pindexPrev->nHeight+1; + // Check count of sequence of same algo + if ( (TestNet() && (nHeight > 200)) + || (nHeight > nBlockSequentialAlgoRuleStart) ) + { + int nAlgo = block.GetAlgo(); + int nAlgoCount = 1; + CBlockIndex* piPrev = pindexPrev; + while (piPrev && (nAlgoCount <= nBlockSequentialAlgoMaxCount)) + { + if (piPrev->GetAlgo() != nAlgo) + break; + nAlgoCount++; + piPrev = piPrev->pprev; + } + if (nAlgoCount > nBlockSequentialAlgoMaxCount) + { + return state.DoS(100, error("AcceptBlock() : too many blocks from same algo"), + REJECT_INVALID, "algo-toomany"); + } + } + // Check proof of work if (block.nBits != GetNextWorkRequired(pindexPrev, &block, block.GetAlgo())) return state.DoS(100, error("AcceptBlock() : incorrect proof of work"), @@ -2459,6 +2504,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp) } } // Enforce block.nVersion=2 rule that the coinbase starts with serialized block height + /* if (block.nVersion >= 2) { // if 750 of the last 1,000 blocks are version 2 or greater (51/100 if testnet): @@ -2472,6 +2518,7 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp) REJECT_INVALID, "bad-cb-height"); } } + */ } // Write block to history file @@ -4394,8 +4441,8 @@ bool SendMessages(CNode* pto, bool fSendTrickle) // in flight for over two minutes, since we first had a chance to // process an incoming block. int64_t nNow = GetTimeMicros(); - if (!pto->fDisconnect && state.nBlocksInFlight && - state.nLastBlockReceive < state.nLastBlockProcess - BLOCK_DOWNLOAD_TIMEOUT*1000000 && + if (!pto->fDisconnect && state.nBlocksInFlight && + state.nLastBlockReceive < state.nLastBlockProcess - BLOCK_DOWNLOAD_TIMEOUT*1000000 && state.vBlocksInFlight.front().nTime < state.nLastBlockProcess - 2*BLOCK_DOWNLOAD_TIMEOUT*1000000) { LogPrintf("Peer %s is stalling block download, disconnecting\n", state.name.c_str()); pto->fDisconnect = true; diff --git a/src/main.h b/src/main.h index fc2c37c4..f7975de6 100644 --- a/src/main.h +++ b/src/main.h @@ -67,6 +67,9 @@ static const int MAX_BLOCKS_IN_TRANSIT_PER_PEER = 128; /** Timeout in seconds before considering a block download peer unresponsive. */ static const unsigned int BLOCK_DOWNLOAD_TIMEOUT = 60; +/** Stealth addresses */ +static const int64_t BLOCK_STEALTH_START = 740000; + #ifdef USE_UPNP static const int fHaveUPnP = true; #else @@ -685,6 +688,9 @@ enum BlockStatus { }; const int64_t nBlockAlgoWorkWeightStart = 142000; // block where algo work weighting starts +const int64_t nBlockAlgoNormalisedWorkStart = 740000; // block where algo combined weight starts +const int64_t nBlockSequentialAlgoRuleStart = 740000; // block where sequential algo rule starts +const int nBlockSequentialAlgoMaxCount = 6; // maximum sequential blocks of same algo /** The block chain is a tree shaped structure starting with the * genesis block at the root, with each block potentially having multiple @@ -778,7 +784,7 @@ public: } int GetAlgo() const { return ::GetAlgo(nVersion); } - + CDiskBlockPos GetBlockPos() const { CDiskBlockPos ret; if (nStatus & BLOCK_HAVE_DATA) { @@ -820,6 +826,21 @@ public: return (int64_t)nTime; } + CBigNum GetPrevWorkForAlgo(int algo) const + { + CBigNum nWork; + CBlockIndex* pindex = this->pprev; + while (pindex) + { + if (pindex->GetAlgo() == algo) + { + return pindex->GetBlockWork(); + } + pindex = pindex->pprev; + } + return Params().ProofOfWorkLimit(algo); + } + CBigNum GetBlockWork() const { CBigNum bnTarget; @@ -829,7 +850,7 @@ public: return (CBigNum(1)<<256) / (bnTarget+1); } - int GetAlgoWorkFactor() const + int GetAlgoWorkFactor() const { if (!TestNet() && (nHeight < nBlockAlgoWorkWeightStart)) { @@ -842,7 +863,7 @@ public: switch (GetAlgo()) { case ALGO_SHA256D: - return 1; + return 1; // work factor = absolute work ratio * optimisation factor case ALGO_SCRYPT: return 1024 * 4; @@ -860,10 +881,29 @@ public: CBigNum GetBlockWorkAdjusted() const { CBigNum bnRes; - bnRes = GetBlockWork() * GetAlgoWorkFactor(); + if ((TestNet() && (nHeight > 500)) || + (nHeight >= nBlockAlgoNormalisedWorkStart)) + { + // Adjusted block work is the sum of work of this block and the + // most recent work of one block of each algo + CBigNum nBlockWork = GetBlockWork(); + int nAlgo = GetAlgo(); + for (int algo = 0; algo < NUM_ALGOS; algo++) + { + if (algo != nAlgo) + { + nBlockWork += GetPrevWorkForAlgo(algo); + } + } + bnRes = nBlockWork / NUM_ALGOS; + } + else + { + bnRes = GetBlockWork() * GetAlgoWorkFactor(); + } return bnRes; } - + bool CheckIndex() const { int algo = GetAlgo(); diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index dfbd445c..26c3d7ac 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -27,10 +27,11 @@ struct AddressTableEntry Type type; QString label; QString address; + bool stealth; AddressTableEntry() {} - AddressTableEntry(Type type, const QString &label, const QString &address): - type(type), label(label), address(address) {} + AddressTableEntry(Type type, const QString &label, const QString &address, const bool &stealth = false): + type(type), label(label), address(address), stealth(stealth) {} }; struct AddressTableEntryLessThan @@ -90,6 +91,17 @@ public: QString::fromStdString(strName), QString::fromStdString(address.ToString()))); } + + std::set::iterator it; + for (it = wallet->stealthAddresses.begin(); it != wallet->stealthAddresses.end(); ++it) + { + bool fMine = !(it->scan_secret.size() < 1); + cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending, + QString::fromStdString(it->label), + QString::fromStdString(it->Encoded()), + true)); + }; + } // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order // Even though the map is already sorted this re-sorting step is needed because the originating map @@ -242,6 +254,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, std::string strPurpose = (rec->type == AddressTableEntry::Sending ? "send" : "receive"); editStatus = OK; + std::string strTemp, strValue; if(role == Qt::EditRole) { LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */ @@ -254,7 +267,15 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, editStatus = NO_CHANGES; return false; } - wallet->SetAddressBook(curAddress, value.toString().toStdString(), strPurpose); + strTemp = rec->address.toStdString(); + if (IsStealthAddress(strTemp)) + { + strValue = value.toString().toStdString(); + wallet->UpdateStealthAddress(strTemp, strValue, false); + } else + { + wallet->SetAddressBook(CBitcoinAddress(strTemp).Get(), value.toString().toStdString(), strPurpose); + } } else if(index.column() == Address) { CTxDestination newAddress = CBitcoinAddress(value.toString().toStdString()).Get(); // Refuse to set invalid address, set error status and return false @@ -340,7 +361,7 @@ void AddressTableModel::updateEntry(const QString &address, priv->updateEntry(address, label, isMine, purpose, status); } -QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) +QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, int addressType) { std::string strLabel = label.toStdString(); std::string strAddress = address.toStdString(); @@ -349,53 +370,93 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con if(type == Send) { - if(!walletModel->validateAddress(address)) + if (strAddress.length() > 75) { - editStatus = INVALID_ADDRESS; - return QString(); - } - // Check for duplicate addresses - { - LOCK(wallet->cs_wallet); - if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get())) + CStealthAddress sxAddr; + if (!sxAddr.SetEncoded(strAddress)) { - editStatus = DUPLICATE_ADDRESS; + editStatus = INVALID_ADDRESS; return QString(); } + + // -- Check for duplicate addresses + { + LOCK(wallet->cs_wallet); + + if (wallet->stealthAddresses.count(sxAddr)) + { + editStatus = DUPLICATE_ADDRESS; + return QString(); + }; + + sxAddr.label = strLabel; + wallet->AddStealthAddress(sxAddr); + } + } + else + { + if(!walletModel->validateAddress(address)) + { + editStatus = INVALID_ADDRESS; + return QString(); + } + // Check for duplicate addresses + { + LOCK(wallet->cs_wallet); + if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress).Get())) + { + editStatus = DUPLICATE_ADDRESS; + return QString(); + } + wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel, + (type == Send ? "send" : "receive")); + } } } else if(type == Receive) { - // Generate a new address to associate with given label - CPubKey newKey; - if(!wallet->GetKeyFromPool(newKey)) + WalletModel::UnlockContext ctx(walletModel->requestUnlock()); + if(!ctx.isValid()) { - WalletModel::UnlockContext ctx(walletModel->requestUnlock()); - if(!ctx.isValid()) + // Unlock wallet failed or was cancelled + editStatus = WALLET_UNLOCK_FAILURE; + return QString(); + } + + // Generate a new address to associate with given label + if (addressType == AT_Stealth) + { + CStealthAddress newStealthAddr; + std::string sError; + if (!wallet->NewStealthAddress(sError, strLabel, newStealthAddr) + || !wallet->AddStealthAddress(newStealthAddr)) { - // Unlock wallet failed or was cancelled - editStatus = WALLET_UNLOCK_FAILURE; + editStatus = KEY_GENERATION_FAILURE; return QString(); } + strAddress = newStealthAddr.Encoded(); + } else + { + CPubKey newKey; if(!wallet->GetKeyFromPool(newKey)) { editStatus = KEY_GENERATION_FAILURE; return QString(); } + strAddress = CBitcoinAddress(newKey.GetID()).ToString(); + + { + LOCK(wallet->cs_wallet); + wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel, + (type == Send ? "send" : "receive")); + } } - strAddress = CBitcoinAddress(newKey.GetID()).ToString(); } else { return QString(); } - // Add entry - { - LOCK(wallet->cs_wallet); - wallet->SetAddressBook(CBitcoinAddress(strAddress).Get(), strLabel, - (type == Send ? "send" : "receive")); - } return QString::fromStdString(strAddress); } @@ -422,11 +483,28 @@ QString AddressTableModel::labelForAddress(const QString &address) const { { LOCK(wallet->cs_wallet); - CBitcoinAddress address_parsed(address.toStdString()); - std::map::iterator mi = wallet->mapAddressBook.find(address_parsed.Get()); - if (mi != wallet->mapAddressBook.end()) + + std::string sAddr = address.toStdString(); + + if (sAddr.length() > 75) { - return QString::fromStdString(mi->second.name); + CStealthAddress sxAddr; + if (!sxAddr.SetEncoded(sAddr)) + return QString(); + std::set:: iterator it; + it = wallet->stealthAddresses.find(sxAddr); + if (it == wallet->stealthAddresses.end()) + return QString(); + return QString::fromStdString(it->label); + } + else + { + CBitcoinAddress address_parsed(sAddr); + std::map::iterator mi = wallet->mapAddressBook.find(address_parsed.Get()); + if (mi != wallet->mapAddressBook.end()) + { + return QString::fromStdString(mi->second.name); + } } } return QString(); diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 71691f5a..374ecd78 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -24,9 +24,18 @@ public: explicit AddressTableModel(CWallet *wallet, WalletModel *parent = 0); ~AddressTableModel(); - enum ColumnIndex { + enum AddressType + { + AT_Unknown = 0, + AT_Normal = 1, + AT_Stealth = 2 + }; + + enum ColumnIndex + { Label = 0, /**< User specified label */ - Address = 1 /**< Bitcoin address */ + Address = 1, /**< Bitcoin address */ + Type = 2 /**< Address type */ }; enum RoleIndex { @@ -61,7 +70,7 @@ public: /* Add an address to the model. Returns the added address on success, and an empty string otherwise. */ - QString addRow(const QString &type, const QString &label, const QString &address); + QString addRow(const QString &type, const QString &label, const QString &address, int addressType); /* Look up label for address in address book, if not found return empty string. */ diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index 293cc168..83a36ad3 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -88,8 +88,15 @@ BitcoinAddressCheckValidator::BitcoinAddressCheckValidator(QObject *parent) : QValidator::State BitcoinAddressCheckValidator::validate(QString &input, int &pos) const { Q_UNUSED(pos); + string saddr = input.toStdString(); + // Validate stealth address + if (saddr.length() > 75) + { + if (IsStealthAddress(saddr)) + return QValidator::Acceptable; + }; // Validate the passed Bitcoin address - CBitcoinAddress addr(input.toStdString()); + CBitcoinAddress addr(saddr); if (addr.IsValid()) return QValidator::Acceptable; diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index de3702d8..9cd69675 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -27,16 +27,23 @@ EditAddressDialog::EditAddressDialog(Mode mode, QWidget *parent) : case NewReceivingAddress: setWindowTitle(tr("New receiving address")); ui->addressEdit->setEnabled(false); + ui->stealthCB->setEnabled(true); + ui->stealthCB->setVisible(true); break; case NewSendingAddress: setWindowTitle(tr("New sending address")); + ui->stealthCB->setVisible(false); break; case EditReceivingAddress: setWindowTitle(tr("Edit receiving address")); ui->addressEdit->setEnabled(false); + ui->addressEdit->setVisible(true); + ui->stealthCB->setEnabled(false); + ui->addressEdit->setVisible(true); break; case EditSendingAddress: setWindowTitle(tr("Edit sending address")); + ui->stealthCB->setVisible(false); break; } @@ -58,6 +65,7 @@ void EditAddressDialog::setModel(AddressTableModel *model) mapper->setModel(model); mapper->addMapping(ui->labelEdit, AddressTableModel::Label); mapper->addMapping(ui->addressEdit, AddressTableModel::Address); + mapper->addMapping(ui->stealthCB, AddressTableModel::Type); } void EditAddressDialog::loadRow(int row) @@ -70,14 +78,17 @@ bool EditAddressDialog::saveCurrentRow() if(!model) return false; + int typeInd; switch(mode) { case NewReceivingAddress: case NewSendingAddress: + typeInd = ui->stealthCB->isChecked() ? AddressTableModel::AT_Stealth : AddressTableModel::AT_Normal; address = model->addRow( mode == NewSendingAddress ? AddressTableModel::Send : AddressTableModel::Receive, ui->labelEdit->text(), - ui->addressEdit->text()); + ui->addressEdit->text(), + typeInd); break; case EditReceivingAddress: case EditSendingAddress: diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index f40c4460..b6b7ff62 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -6,7 +6,7 @@ 0 0 - 760 + 960 380 diff --git a/src/qt/forms/editaddressdialog.ui b/src/qt/forms/editaddressdialog.ui index c1aea363..84c68899 100644 --- a/src/qt/forms/editaddressdialog.ui +++ b/src/qt/forms/editaddressdialog.ui @@ -7,7 +7,7 @@ 0 0 457 - 126 + 130 @@ -53,6 +53,13 @@ + + + + Stealth Address + + + diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index f2c76c83..7707b60b 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -140,7 +140,7 @@ void ReceiveCoinsDialog::on_receiveButton_clicked() } } else { /* Generate new receiving address */ - address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, ""); + address = model->getAddressTableModel()->addRow(AddressTableModel::Receive, label, "", AddressTableModel::AT_Normal); } SendCoinsRecipient info(address, label, ui->reqAmount->value(), ui->reqMessage->text()); diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index e0f56f8c..780ccd61 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -11,6 +11,8 @@ #include "optionsmodel.h" #include "walletmodel.h" +#include "stealthaddress.h" + #include #include @@ -147,6 +149,14 @@ SendCoinsRecipient SendCoinsEntry::getValue() // Normal payment recipient.address = ui->payTo->text(); + + // address type + if (recipient.address.length() > 75 + && IsStealthAddress(recipient.address.toStdString())) + recipient.typeInd = AddressTableModel::AT_Stealth; + else + recipient.typeInd = AddressTableModel::AT_Normal; + recipient.label = ui->addAsLabel->text(); recipient.amount = ui->payAmount->value(); recipient.message = ui->messageTextLabel->text(); diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 5a3728f4..881dc094 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -109,6 +109,11 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // from a transaction sent back to our own address. continue; } + if (txout.IsScriptOpReturn()) + { + // OP_RETURN txout + continue; + } CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address)) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 37d82ec0..37e8757e 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -18,6 +18,8 @@ #include "wallet.h" #include "walletdb.h" // for BackupWallet +#include "stealthaddress.h" + #include #include @@ -159,7 +161,15 @@ void WalletModel::updateAddressBook(const QString &address, const QString &label bool WalletModel::validateAddress(const QString &address) { - CBitcoinAddress addressParsed(address.toStdString()); + std::string sAddr = address.toStdString(); + + if (sAddr.length() > 75) + { + if (IsStealthAddress(sAddr)) + return true; + }; + + CBitcoinAddress addressParsed(sAddr); return addressParsed.IsValid(); } @@ -212,8 +222,79 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact setAddress.insert(rcp.address); ++nAddresses; + // stealth + std::string sAddr = rcp.address.toStdString(); + + if (rcp.typeInd == AddressTableModel::AT_Stealth) + { + if ( (!TestNet() && (chainActive.Height() < BLOCK_STEALTH_START)) + || (TestNet() && (chainActive.Height() < 200)) ) + { + emit message(tr("Send Coins"), tr("Stealth addresses not yet supported"), + CClientUIInterface::MSG_ERROR); + return InvalidAddress; + } + + CStealthAddress sxAddr; + if (sxAddr.SetEncoded(sAddr)) + { + ec_secret ephem_secret; + ec_secret secretShared; + ec_point pkSendTo; + ec_point ephem_pubkey; + + + if (GenerateRandomSecret(ephem_secret) != 0) + { + LogPrintf("GenerateRandomSecret failed.\n"); + return InvalidAddress; + }; + + if (StealthSecret(ephem_secret, sxAddr.scan_pubkey, sxAddr.spend_pubkey, secretShared, pkSendTo) != 0) + { + LogPrintf("Could not generate receiving public key.\n"); + return InvalidAddress; + }; + + CPubKey cpkTo(pkSendTo); + if (!cpkTo.IsValid()) + { + LogPrintf("Invalid public key generated.\n"); + return InvalidAddress; + }; + + CKeyID ckidTo = cpkTo.GetID(); + + CBitcoinAddress addrTo(ckidTo); + + if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0) + { + LogPrintf("Could not generate ephem public key.\n"); + return InvalidAddress; + }; + + if (fDebug) + { + LogPrintf("Stealth send to generated pubkey %ui: %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str()); + LogPrintf("hash %s\n", addrTo.ToString().c_str()); + LogPrintf("ephem_pubkey %ui: %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey).c_str()); + }; + + CScript scriptPubKey; + scriptPubKey.SetDestination(addrTo.Get()); + + vecSend.push_back(make_pair(scriptPubKey, rcp.amount)); + + CScript scriptP = CScript() << OP_RETURN << ephem_pubkey; + + vecSend.push_back(make_pair(scriptP, 0)); + + continue; + }; // else drop through to normal + } + CScript scriptPubKey; - scriptPubKey.SetDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); + scriptPubKey.SetDestination(CBitcoinAddress(sAddr).Get()); vecSend.push_back(std::pair(scriptPubKey, rcp.amount)); total += rcp.amount; @@ -246,7 +327,28 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact CWalletTx *newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); - bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl); + + // int nChangePos = -1; + bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, /*nChangePos,*/ strFailReason, coinControl); + + /* + std::map::iterator it; + for (it = mapStealthNarr.begin(); it != mapStealthNarr.end(); ++it) + { + int pos = it->first; + if (nChangePos > -1 && it->first >= nChangePos) + pos++; + + char key[64]; + if (snprintf(key, sizeof(key), "n_%u", pos) < 1) + { + printf("CreateStealthTransaction(): Error creating narration key."); + continue; + }; + wtx.mapValue[key] = it->second; + }; + */ + transaction.setTransactionFee(nFeeRequired); if(!fCreated) @@ -309,17 +411,24 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran { LOCK(wallet->cs_wallet); - std::map::iterator mi = wallet->mapAddressBook.find(dest); + // stealth + if (rcp.typeInd == AddressTableModel::AT_Stealth) + { + wallet->UpdateStealthAddress(strAddress, strLabel, true); + } else + { + std::map::iterator mi = wallet->mapAddressBook.find(dest); - // Check if we have a new address or an updated label - if (mi == wallet->mapAddressBook.end()) - { - wallet->SetAddressBook(dest, strLabel, "send"); - } - else if (mi->second.name != strLabel) - { - wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose - } + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end()) + { + wallet->SetAddressBook(dest, strLabel, "send"); + } + else if (mi->second.name != strLabel) + { + wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + } + }; } } emit coinsSent(wallet, rcp, transaction_array); @@ -423,13 +532,27 @@ static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, QString strLabel = QString::fromStdString(label); QString strPurpose = QString::fromStdString(purpose); - qDebug() << "NotifyAddressBookChanged : " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); - QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, - Q_ARG(QString, strAddress), - Q_ARG(QString, strLabel), - Q_ARG(bool, isMine), - Q_ARG(QString, strPurpose), - Q_ARG(int, status)); + if (address.type() == typeid(CStealthAddress)) + { + CStealthAddress sxAddr = boost::get(address); + std::string enc = sxAddr.Encoded(); + qDebug() << "NotifyAddressBookChanged : " + QString::fromStdString(enc) + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(enc)), + Q_ARG(QString, strLabel), + Q_ARG(bool, isMine), + Q_ARG(QString, strPurpose), + Q_ARG(int, status)); + } else + { + qDebug() << "NotifyAddressBookChanged : " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + strPurpose + " status=" + QString::number(status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, strAddress), + Q_ARG(QString, strLabel), + Q_ARG(bool, isMine), + Q_ARG(QString, strPurpose), + Q_ARG(int, status)); + } } // queue notifications to show a non freezing progress dialog e.g. for rescan diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ccf590aa..54f347f8 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -47,6 +47,7 @@ public: // Todo: This is a hack, should be replaced with a cleaner solution! QString address; QString label; + int typeInd; qint64 amount; // If from a payment request, this is used for storing the memo QString message; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 4d359770..92c55d2c 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -20,16 +20,17 @@ void ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out, bool fIncludeH double GetDifficulty(const CBlockIndex* blockindex, int algo) { unsigned int nBits; - + // Floating point number that is a multiple of the minimum difficulty, // minimum difficulty = 1.0. if (blockindex == NULL) { - if (chainActive.Tip() == NULL) + CBlockIndex* tip = chainActive.Tip(); + if (tip == NULL) nBits = Params().ProofOfWorkLimit(ALGO_SHA256D).GetCompact(); else { - blockindex = GetLastBlockIndexForAlgo(chainActive.Tip(), algo); + blockindex = GetLastBlockIndexForAlgo(tip, algo); if (blockindex == NULL) nBits = Params().ProofOfWorkLimit(algo).GetCompact(); else @@ -38,11 +39,11 @@ double GetDifficulty(const CBlockIndex* blockindex, int algo) } else nBits = blockindex->nBits; - + int nShift = (nBits >> 24) & 0xff; double dDiff = - (double)0x0000ffff / (double)(blockindex->nBits & 0x00ffffff); + (double)0x0000ffff / (double)(nBits & 0x00ffffff); while (nShift < 29) { diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index c53c344a..fa60e12c 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -176,6 +176,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 == "sendtostealthaddress" && n > 1) ConvertTo(params[1]); return params; } diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 672996d8..570fe1fc 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -81,6 +81,7 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("difficulty_skein", (double)GetDifficulty(NULL, ALGO_SKEIN))); obj.push_back(Pair("difficulty_qubit", (double)GetDifficulty(NULL, ALGO_QUBIT))); obj.push_back(Pair("testnet", TestNet())); + #ifdef ENABLE_WALLET if (pwalletMain) { obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); @@ -92,6 +93,7 @@ Value getinfo(const Array& params, bool fHelp) #endif obj.push_back(Pair("relayfee", ValueFromAmount(CTransaction::nMinRelayTxFee))); obj.push_back(Pair("errors", GetWarnings("statusbar"))); + return obj; } @@ -130,6 +132,14 @@ public: obj.push_back(Pair("sigsrequired", nRequired)); return obj; } + + Object operator()(const CStealthAddress &stxAddr) const { + Object obj; + obj.push_back(Pair("isstealth", true)); + obj.push_back(Pair("label", stxAddr.label)); + obj.push_back(Pair("address", stxAddr.Encoded())); + return obj; + } }; #endif diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 9b15a76e..91be77f3 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -315,6 +315,16 @@ static const CRPCCommand vRPCCommands[] = { "gethashespersec", &gethashespersec, true, false, false }, { "getwork", &getwork, true, false, true }, { "setgenerate", &setgenerate, true, true, false }, + + /* Stealth */ + { "getnewstealthaddress", &getnewstealthaddress, false, false, true}, + { "liststealthaddresses", &liststealthaddresses, false, false, true}, + { "importstealthaddress", &importstealthaddress, false, false, true}, + { "sendtostealthaddress", &sendtostealthaddress, false, false, true}, + { "clearwallettransactions",&clearwallettransactions,false, false, true}, + { "scanforalltxns", &scanforalltxns, false, false, true}, + { "scanforstealthtxns", &scanforstealthtxns, false, false, true}, + #endif // ENABLE_WALLET }; diff --git a/src/rpcserver.h b/src/rpcserver.h index 7e8cd903..fc529068 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -190,5 +190,12 @@ extern json_spirit::Value verifychain(const json_spirit::Array& params, bool fHe extern json_spirit::Value makekeypair(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getnewstealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value liststealthaddresses(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value importstealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value sendtostealthaddress(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value clearwallettransactions(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value scanforalltxns(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value scanforstealthtxns(const json_spirit::Array& params, bool fHelp); #endif diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 198685eb..91ac9d26 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -1917,3 +1917,462 @@ Value getwalletinfo(const Array& params, bool fHelp) obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); return obj; } + +Value getnewstealthaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getnewstealthaddress [label]\n" + "Returns a new stealth address for receiving payments anonymously."); + + if (pwalletMain->IsLocked()) + throw runtime_error("Failed: Wallet must be unlocked."); + + std::string sLabel; + if (params.size() > 0) + sLabel = params[0].get_str(); + + CStealthAddress sxAddr; + std::string sError; + if (!pwalletMain->NewStealthAddress(sError, sLabel, sxAddr)) + throw runtime_error(std::string("Could not get new stealth address: ") + sError); + + if (!pwalletMain->AddStealthAddress(sxAddr)) + throw runtime_error("Could not save to wallet."); + + return sxAddr.Encoded(); +} + +Value liststealthaddresses(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "liststealthaddresses [show_secrets=0]\n" + "List owned stealth addresses."); + + bool fShowSecrets = false; + + if (params.size() > 0) + { + std::string str = params[0].get_str(); + + if (str == "0" || str == "n" || str == "no" || str == "-" || str == "false") + fShowSecrets = false; + else + fShowSecrets = true; + }; + + if (fShowSecrets) + { + if (pwalletMain->IsLocked()) + throw runtime_error("Failed: Wallet must be unlocked."); + }; + + Object result; + + std::set::iterator it; + for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) + { + if (it->scan_secret.size() < 1) + continue; // stealth address is not owned + + if (fShowSecrets) + { + Object objA; + objA.push_back(Pair("label", it->label)); + objA.push_back(Pair("address", it->Encoded())); + objA.push_back(Pair("scan_secret", HexStr(it->scan_secret.begin(), it->scan_secret.end()))); + objA.push_back(Pair("spend_secret", HexStr(it->spend_secret.begin(), it->spend_secret.end()))); + result.push_back(Pair("stealth_address", objA)); + } else + { + result.push_back(Pair("stealth_address", it->Encoded() + " - " + it->label)); + }; + }; + + return result; +} + +Value importstealthaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2) + throw runtime_error( + "importstealthaddress [label]\n" + "Import an owned stealth addresses."); + + std::string sScanSecret = params[0].get_str(); + std::string sSpendSecret = params[1].get_str(); + std::string sLabel; + + if (params.size() > 2) + { + sLabel = params[2].get_str(); + }; + + std::vector vchScanSecret; + std::vector vchSpendSecret; + + if (IsHex(sScanSecret)) + { + vchScanSecret = ParseHex(sScanSecret); + } else + { + if (!DecodeBase58(sScanSecret, vchScanSecret)) + throw runtime_error("Could not decode scan secret as hex or base58."); + }; + + if (IsHex(sSpendSecret)) + { + vchSpendSecret = ParseHex(sSpendSecret); + } else + { + if (!DecodeBase58(sSpendSecret, vchSpendSecret)) + throw runtime_error("Could not decode spend secret as hex or base58."); + }; + + if (vchScanSecret.size() != 32) + throw runtime_error("Scan secret is not 32 bytes."); + if (vchSpendSecret.size() != 32) + throw runtime_error("Spend secret is not 32 bytes."); + + + ec_secret scan_secret; + ec_secret spend_secret; + + memcpy(&scan_secret.e[0], &vchScanSecret[0], 32); + memcpy(&spend_secret.e[0], &vchSpendSecret[0], 32); + + ec_point scan_pubkey, spend_pubkey; + if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) + throw runtime_error("Could not get scan public key."); + + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) + throw runtime_error("Could not get spend public key."); + + + CStealthAddress sxAddr; + sxAddr.label = sLabel; + sxAddr.scan_pubkey = scan_pubkey; + sxAddr.spend_pubkey = spend_pubkey; + + sxAddr.scan_secret = vchScanSecret; + sxAddr.spend_secret = vchSpendSecret; + + Object result; + bool fFound = false; + // -- find if address already exists + std::set::iterator it; + for (it = pwalletMain->stealthAddresses.begin(); it != pwalletMain->stealthAddresses.end(); ++it) + { + CStealthAddress &sxAddrIt = const_cast(*it); + if (sxAddrIt.scan_pubkey == sxAddr.scan_pubkey + && sxAddrIt.spend_pubkey == sxAddr.spend_pubkey) + { + if (sxAddrIt.scan_secret.size() < 1) + { + sxAddrIt.scan_secret = sxAddr.scan_secret; + sxAddrIt.spend_secret = sxAddr.spend_secret; + fFound = true; // update stealth address with secrets + break; + }; + + result.push_back(Pair("result", "Import failed - stealth address exists.")); + return result; + }; + }; + + if (fFound) + { + result.push_back(Pair("result", "Success, updated " + sxAddr.Encoded())); + } else + { + pwalletMain->stealthAddresses.insert(sxAddr); + result.push_back(Pair("result", "Success, imported " + sxAddr.Encoded())); + }; + + + if (!pwalletMain->AddStealthAddress(sxAddr)) + throw runtime_error("Could not save to wallet."); + + return result; +} + +Value sendtostealthaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 5) + throw runtime_error( + "sendtostealthaddress [narration] [comment] [comment-to]\n" + " is a real and is rounded to the nearest 0.000001" + + HelpRequiringPassphrase()); + + if (pwalletMain->IsLocked()) + throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); + + std::string sEncoded = params[0].get_str(); + int64_t nAmount = AmountFromValue(params[1]); + + std::string sNarr; + if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) + sNarr = params[2].get_str(); + + if (sNarr.length() > 24) + throw runtime_error("Narration must be 24 characters or less."); + + CStealthAddress sxAddr; + Object result; + + if (!sxAddr.SetEncoded(sEncoded)) + { + result.push_back(Pair("result", "Invalid stealth address.")); + return result; + }; + + + CWalletTx wtx; + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["comment"] = params[3].get_str(); + if (params.size() > 4 && params[4].type() != null_type && !params[4].get_str().empty()) + wtx.mapValue["to"] = params[4].get_str(); + + std::string sError; + if (!pwalletMain->SendStealthMoneyToDestination(sxAddr, nAmount, sNarr, wtx, sError)) + throw JSONRPCError(RPC_WALLET_ERROR, sError); + + return wtx.GetHash().GetHex(); +} + +Value clearwallettransactions(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "clearwallettransactions \n" + "delete all transactions from wallet - reload with scanforalltxns\n" + "Warning: Backup your wallet first!"); + + Object result; + + uint32_t nTransactions = 0; + + char cbuf[256]; + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + CWalletDB walletdb(pwalletMain->strWalletFile); + walletdb.TxnBegin(); + Dbc* pcursor = walletdb.GetCursor(); + if (!pcursor) + throw runtime_error("Cannot get wallet DB cursor"); + + Dbt datKey; + Dbt datValue; + + datKey.set_flags(DB_DBT_USERMEM); + datValue.set_flags(DB_DBT_USERMEM); + + std::vector vchKey; + std::vector vchType; + std::vector vchKeyData; + std::vector vchValueData; + + vchKeyData.resize(100); + vchValueData.resize(100); + + datKey.set_ulen(vchKeyData.size()); + datKey.set_data(&vchKeyData[0]); + + datValue.set_ulen(vchValueData.size()); + datValue.set_data(&vchValueData[0]); + + unsigned int fFlags = DB_NEXT; // same as using DB_FIRST for new cursor + while (true) + { + int ret = pcursor->get(&datKey, &datValue, fFlags); + + if (ret == ENOMEM + || ret == DB_BUFFER_SMALL) + { + if (datKey.get_size() > datKey.get_ulen()) + { + vchKeyData.resize(datKey.get_size()); + datKey.set_ulen(vchKeyData.size()); + datKey.set_data(&vchKeyData[0]); + }; + + if (datValue.get_size() > datValue.get_ulen()) + { + vchValueData.resize(datValue.get_size()); + datValue.set_ulen(vchValueData.size()); + datValue.set_data(&vchValueData[0]); + }; + // -- try once more, when DB_BUFFER_SMALL cursor is not expected to move + ret = pcursor->get(&datKey, &datValue, fFlags); + }; + + if (ret == DB_NOTFOUND) + break; + else + if (datKey.get_data() == NULL || datValue.get_data() == NULL + || ret != 0) + { + snprintf(cbuf, sizeof(cbuf), "wallet DB error %d, %s", ret, db_strerror(ret)); + throw runtime_error(cbuf); + }; + + CDataStream ssValue(SER_DISK, CLIENT_VERSION); + ssValue.SetType(SER_DISK); + ssValue.clear(); + ssValue.write((char*)datKey.get_data(), datKey.get_size()); + + ssValue >> vchType; + + + std::string strType(vchType.begin(), vchType.end()); + + //LogPrintf("strType %s\n", strType.c_str()); + + if (strType == "tx") + { + uint256 hash; + ssValue >> hash; + + if ((ret = pcursor->del(0)) != 0) + { + LogPrintf("Delete transaction failed %d, %s\n", ret, db_strerror(ret)); + continue; + }; + + pwalletMain->mapWallet.erase(hash); + pwalletMain->NotifyTransactionChanged(pwalletMain, hash, CT_DELETED); + + nTransactions++; + }; + }; + + pcursor->close(); + walletdb.TxnCommit(); + + + //pwalletMain->mapWallet.clear(); + } + + snprintf(cbuf, sizeof(cbuf), "Removed %u transactions.", nTransactions); + result.push_back(Pair("complete", std::string(cbuf))); + result.push_back(Pair("", "Reload with scanforstealthtxns or re-download blockchain.")); + + return result; +} + +Value scanforalltxns(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "scanforalltxns [fromHeight]\n" + "Scan blockchain for owned transactions."); + + Object result; + int32_t nFromHeight = 0; + + + CBlockIndex *pindex = chainActive.Genesis(); + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) + { + // pindex = mapBlockIndex[hashBestChain]; + pindex = mapBlockIndex[chainActive.Tip()->GetBlockHash()]; + while (pindex->nHeight > nFromHeight + && pindex->pprev) + pindex = pindex->pprev; + }; + + if (pindex == NULL) + throw runtime_error("Genesis block is not set."); + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + pwalletMain->MarkDirty(); + + pwalletMain->ScanForWalletTransactions(pindex, true); + pwalletMain->ReacceptWalletTransactions(); + } + + result.push_back(Pair("result", "Scan complete.")); + + return result; +} + +Value scanforstealthtxns(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "scanforstealthtxns [fromHeight]\n" + "Scan blockchain for owned stealth transactions."); + + Object result; + + uint32_t nBlocks = 0; + uint32_t nTransactions = 0; + int32_t nFromHeight = 0; + + CBlockIndex *pindex = chainActive.Genesis(); + + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) + { + // pindex = mapBlockIndex[hashBestChain]; + pindex = mapBlockIndex[chainActive.Tip()->GetBlockHash()]; + while (pindex->nHeight > nFromHeight + && pindex->pprev) + pindex = pindex->pprev; + }; + + if (pindex == NULL) + throw runtime_error("Genesis block is not set."); + + // -- locks in AddToWalletIfInvolvingMe + + bool fUpdate = true; // todo: option? + + pwalletMain->nStealth = 0; + pwalletMain->nFoundStealth = 0; + + while (pindex) + { + nBlocks++; + CBlock block; + ReadBlockFromDisk(block, pindex); + + BOOST_FOREACH(CTransaction& tx, block.vtx) + { + string reason; + if (!IsStandardTx(tx, reason)) + continue; // leave out coinbase and others + nTransactions++; + + pwalletMain->AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate); + }; + + pindex = chainActive.Next(pindex); + }; + + LogPrintf("Scanned %u blocks, %u transactions\n", nBlocks, nTransactions); + LogPrintf("Found %u stealth transactions in blockchain.\n", pwalletMain->nStealth); + LogPrintf("Found %u new owned stealth transactions.\n", pwalletMain->nFoundStealth); + + char cbuf[256]; + snprintf(cbuf, sizeof(cbuf), "%u new stealth transactions.", pwalletMain->nFoundStealth); + + result.push_back(Pair("result", "Scan complete.")); + result.push_back(Pair("found", std::string(cbuf))); + + return result; +} diff --git a/src/script.cpp b/src/script.cpp index 810ba16d..8a3be72f 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1454,6 +1454,7 @@ public: bool operator()(const CNoDestination &dest) const { return false; } bool operator()(const CKeyID &keyID) const { return keystore->HaveKey(keyID); } bool operator()(const CScriptID &scriptID) const { return keystore->HaveCScript(scriptID); } + bool operator()(const CStealthAddress &stxAddr) const { return stxAddr.scan_secret.size() == ec_secret_size; } }; bool IsMine(const CKeyStore &keystore, const CTxDestination &dest) @@ -1589,6 +1590,10 @@ public: Process(script); } + void operator()(const CStealthAddress &stxAddr) { + CScript script; + } + void operator()(const CNoDestination &none) {} }; @@ -1931,6 +1936,14 @@ public: *script << OP_HASH160 << scriptID << OP_EQUAL; return true; } + + bool operator()(const CStealthAddress &stxAddr) const { + script->clear(); + //*script << OP_HASH160 << scriptID << OP_EQUAL; + LogPrintf("TODO\n"); + return false; + } + }; void CScript::SetDestination(const CTxDestination& dest) diff --git a/src/script.h b/src/script.h index 657ac0b3..a4e586a5 100644 --- a/src/script.h +++ b/src/script.h @@ -10,6 +10,8 @@ #include "key.h" #include "util.h" +#include "stealthaddress.h" + #include #include #include @@ -67,7 +69,7 @@ public: * * CScriptID: TX_SCRIPTHASH destination * A CTxDestination is the internal data type encoded in a CBitcoinAddress */ -typedef boost::variant CTxDestination; +typedef boost::variant CTxDestination; const char* GetTxnOutputType(txnouttype t); diff --git a/src/stealthaddress.cpp b/src/stealthaddress.cpp new file mode 100644 index 00000000..670d631a --- /dev/null +++ b/src/stealthaddress.cpp @@ -0,0 +1,683 @@ +// Copyright (c) 2014 The ShadowCoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. + +#include "stealthaddress.h" +#include "base58.h" + + +#include +#include +#include +#include + +//const uint8_t stealth_version_byte = 0x2a; +const uint8_t stealth_version_byte = 0x28; + + +bool CStealthAddress::SetEncoded(const std::string& encodedAddress) +{ + data_chunk raw; + + if (!DecodeBase58(encodedAddress, raw)) + { + if (fDebug) + LogPrintf("CStealthAddress::SetEncoded DecodeBase58 falied.\n"); + return false; + }; + + if (!VerifyChecksum(raw)) + { + if (fDebug) + LogPrintf("CStealthAddress::SetEncoded verify_checksum falied.\n"); + return false; + }; + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) + { + if (fDebug) + LogPrintf("CStealthAddress::SetEncoded() too few bytes provided.\n"); + return false; + }; + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) + { + LogPrintf("CStealthAddress::SetEncoded version mismatch 0x%x != 0x%x.\n", version, stealth_version_byte); + return false; + }; + + options = *p++; + + scan_pubkey.resize(33); + memcpy(&scan_pubkey[0], p, 33); + p += 33; + //uint8_t spend_pubkeys = *p++; + p++; + + spend_pubkey.resize(33); + memcpy(&spend_pubkey[0], p, 33); + + return true; +}; + +std::string CStealthAddress::Encoded() const +{ + // https://wiki.unsystem.net/index.php/DarkWallet/Stealth#Address_format + // [version] [options] [scan_key] [N] ... [Nsigs] [prefix_length] ... + + data_chunk raw; + raw.push_back(stealth_version_byte); + + raw.push_back(options); + + raw.insert(raw.end(), scan_pubkey.begin(), scan_pubkey.end()); + raw.push_back(1); // number of spend pubkeys + raw.insert(raw.end(), spend_pubkey.begin(), spend_pubkey.end()); + raw.push_back(0); // number of signatures + raw.push_back(0); // ? + + AppendChecksum(raw); + + return EncodeBase58(raw); +}; + + +uint32_t BitcoinChecksum(uint8_t* p, uint32_t nBytes) +{ + if (!p || nBytes == 0) + return 0; + + uint8_t hash1[32]; + SHA256(p, nBytes, (uint8_t*)hash1); + uint8_t hash2[32]; + SHA256((uint8_t*)hash1, sizeof(hash1), (uint8_t*)hash2); + + // -- checksum is the 1st 4 bytes of the hash + uint32_t checksum = from_little_endian(&hash2[0]); + + return checksum; +}; + +void AppendChecksum(data_chunk& data) +{ + uint32_t checksum = BitcoinChecksum(&data[0], data.size()); + + // -- to_little_endian + std::vector tmp(4); + + //memcpy(&tmp[0], &checksum, 4); + for (int i = 0; i < 4; ++i) + { + tmp[i] = checksum & 0xFF; + checksum >>= 8; + }; + + data.insert(data.end(), tmp.begin(), tmp.end()); +}; + +bool VerifyChecksum(const data_chunk& data) +{ + if (data.size() < 4) + return false; + + uint32_t checksum = from_little_endian(data.end() - 4); + + return BitcoinChecksum((uint8_t*)&data[0], data.size()-4) == checksum; +}; + + +int GenerateRandomSecret(ec_secret& out) +{ + RandAddSeedPerfmon(); + + static uint256 max("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140"); + static uint256 min(16000); // increase? min valid key is 1 + + uint256 test; + + int i; + // -- check max, try max 32 times + for (i = 0; i < 32; ++i) + { + RAND_bytes((unsigned char*) test.begin(), 32); + if (test > min && test < max) + { + memcpy(&out.e[0], test.begin(), 32); + break; + }; + }; + + if (i > 31) + { + LogPrintf("Error: GenerateRandomSecret failed to generate a valid key.\n"); + return 1; + }; + + return 0; +}; + +int SecretToPublicKey(const ec_secret& secret, ec_point& out) +{ + // -- public key = private * G + int rv = 0; + + EC_GROUP *ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) + { + LogPrintf("SecretToPublicKey(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + BIGNUM* bnIn = BN_bin2bn(&secret.e[0], ec_secret_size, BN_new()); + if (!bnIn) + { + EC_GROUP_free(ecgrp); + LogPrintf("SecretToPublicKey(): BN_bin2bn failed\n"); + return 1; + }; + + EC_POINT* pub = EC_POINT_new(ecgrp); + + + EC_POINT_mul(ecgrp, pub, bnIn, NULL, NULL, NULL); + + BIGNUM* bnOut = EC_POINT_point2bn(ecgrp, pub, POINT_CONVERSION_COMPRESSED, BN_new(), NULL); + if (!bnOut) + { + LogPrintf("SecretToPublicKey(): point2bn failed\n"); + rv = 1; + } else + { + out.resize(ec_compressed_size); + if (BN_num_bytes(bnOut) != (int) ec_compressed_size + || BN_bn2bin(bnOut, &out[0]) != (int) ec_compressed_size) + { + LogPrintf("SecretToPublicKey(): bnOut incorrect length.\n"); + rv = 1; + }; + + BN_free(bnOut); + }; + + EC_GROUP_free(ecgrp); + BN_free(bnIn); + EC_POINT_free(pub); + + return rv; +}; + + +int StealthSecret(ec_secret& secret, ec_point& pubkey, const ec_point& pkSpend, ec_secret& sharedSOut, ec_point& pkOut) +{ + /* + + send: + secret = ephem_secret, pubkey = scan_pubkey + + receive: + secret = scan_secret, pubkey = ephem_pubkey + c = H(dP) + + Q = public scan key (EC point, 33 bytes) + d = private scan key (integer, 32 bytes) + R = public spend key + f = private spend key + + Q = dG + R = fG + + Sender (has Q and R, not d or f): + + P = eG + + c = H(eQ) = H(dP) + R' = R + cG + + + Recipient gets R' and P + + test 0 and infinity? + */ + + int rv = 0; + std::vector vchOutQ; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnEphem = NULL; + BIGNUM* bnQ = NULL; + EC_POINT* Q = NULL; + BIGNUM* bnOutQ = NULL; + BIGNUM* bnc = NULL; + EC_POINT* C = NULL; + BIGNUM* bnR = NULL; + EC_POINT* R = NULL; + EC_POINT* Rout = NULL; + BIGNUM* bnOutR = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) + { + LogPrintf("StealthSecret(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + LogPrintf("StealthSecret(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnEphem = BN_bin2bn(&secret.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecret(): bnEphem BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnQ = BN_bin2bn(&pubkey[0], pubkey.size(), BN_new()))) + { + LogPrintf("StealthSecret(): bnQ BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(Q = EC_POINT_bn2point(ecgrp, bnQ, NULL, bnCtx))) + { + LogPrintf("StealthSecret(): Q EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + // -- eQ + // EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); + // EC_POINT_mul calculates the value generator * n + q * m and stores the result in r. The value n may be NULL in which case the result is just q * m. + if (!EC_POINT_mul(ecgrp, Q, NULL, Q, bnEphem, bnCtx)) + { + LogPrintf("StealthSecret(): eQ EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutQ = EC_POINT_point2bn(ecgrp, Q, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + LogPrintf("StealthSecret(): Q EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + + vchOutQ.resize(ec_compressed_size); + if (BN_num_bytes(bnOutQ) != (int) ec_compressed_size + || BN_bn2bin(bnOutQ, &vchOutQ[0]) != (int) ec_compressed_size) + { + LogPrintf("StealthSecret(): bnOutQ incorrect length.\n"); + rv = 1; + goto End; + }; + + SHA256(&vchOutQ[0], vchOutQ.size(), &sharedSOut.e[0]); + + if (!(bnc = BN_bin2bn(&sharedSOut.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecret(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + // -- cG + if (!(C = EC_POINT_new(ecgrp))) + { + LogPrintf("StealthSecret(): C EC_POINT_new failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) + { + LogPrintf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnR = BN_bin2bn(&pkSpend[0], pkSpend.size(), BN_new()))) + { + LogPrintf("StealthSecret(): bnR BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + + if (!(R = EC_POINT_bn2point(ecgrp, bnR, NULL, bnCtx))) + { + LogPrintf("StealthSecret(): R EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) + { + LogPrintf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(Rout = EC_POINT_new(ecgrp))) + { + LogPrintf("StealthSecret(): Rout EC_POINT_new failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_add(ecgrp, Rout, R, C, bnCtx)) + { + LogPrintf("StealthSecret(): Rout EC_POINT_add failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutR = EC_POINT_point2bn(ecgrp, Rout, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + LogPrintf("StealthSecret(): Rout EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + + pkOut.resize(ec_compressed_size); + if (BN_num_bytes(bnOutR) != (int) ec_compressed_size + || BN_bn2bin(bnOutR, &pkOut[0]) != (int) ec_compressed_size) + { + LogPrintf("StealthSecret(): pkOut incorrect length.\n"); + rv = 1; + goto End; + }; + + End: + if (bnOutR) BN_free(bnOutR); + if (Rout) EC_POINT_free(Rout); + if (R) EC_POINT_free(R); + if (bnR) BN_free(bnR); + if (C) EC_POINT_free(C); + if (bnc) BN_free(bnc); + if (bnOutQ) BN_free(bnOutQ); + if (Q) EC_POINT_free(Q); + if (bnQ) BN_free(bnQ); + if (bnEphem) BN_free(bnEphem); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +}; + + +int StealthSecretSpend(ec_secret& scanSecret, ec_point& ephemPubkey, ec_secret& spendSecret, ec_secret& secretOut) +{ + /* + + c = H(dP) + R' = R + cG [without decrypting wallet] + = (f + c)G [after decryption of wallet] + Remember: mod curve.order, pad with 0x00s where necessary? + */ + + int rv = 0; + std::vector vchOutP; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnScanSecret = NULL; + BIGNUM* bnP = NULL; + EC_POINT* P = NULL; + BIGNUM* bnOutP = NULL; + BIGNUM* bnc = NULL; + BIGNUM* bnOrder = NULL; + BIGNUM* bnSpend = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) + { + LogPrintf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + LogPrintf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnScanSecret = BN_bin2bn(&scanSecret.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecretSpend(): bnScanSecret BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnP = BN_bin2bn(&ephemPubkey[0], ephemPubkey.size(), BN_new()))) + { + LogPrintf("StealthSecretSpend(): bnP BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(P = EC_POINT_bn2point(ecgrp, bnP, NULL, bnCtx))) + { + LogPrintf("StealthSecretSpend(): P EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + // -- dP + if (!EC_POINT_mul(ecgrp, P, NULL, P, bnScanSecret, bnCtx)) + { + LogPrintf("StealthSecretSpend(): dP EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutP = EC_POINT_point2bn(ecgrp, P, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + LogPrintf("StealthSecretSpend(): P EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + + vchOutP.resize(ec_compressed_size); + if (BN_num_bytes(bnOutP) != (int) ec_compressed_size + || BN_bn2bin(bnOutP, &vchOutP[0]) != (int) ec_compressed_size) + { + LogPrintf("StealthSecretSpend(): bnOutP incorrect length.\n"); + rv = 1; + goto End; + }; + + uint8_t hash1[32]; + SHA256(&vchOutP[0], vchOutP.size(), (uint8_t*)hash1); + + + if (!(bnc = BN_bin2bn(&hash1[0], 32, BN_new()))) + { + LogPrintf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOrder = BN_new()) + || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) + { + LogPrintf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + }; + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecretSpend(): bnSpend BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + //if (!BN_add(r, a, b)) return 0; + //return BN_nnmod(r, r, m, ctx); + if (!BN_mod_add(bnSpend, bnSpend, bnc, bnOrder, bnCtx)) + { + LogPrintf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + }; + + if (BN_is_zero(bnSpend)) // possible? + { + LogPrintf("StealthSecretSpend(): bnSpend is zero.\n"); + rv = 1; + goto End; + }; + + if (BN_num_bytes(bnSpend) != (int) ec_secret_size + || BN_bn2bin(bnSpend, &secretOut.e[0]) != (int) ec_secret_size) + { + LogPrintf("StealthSecretSpend(): bnSpend incorrect length.\n"); + rv = 1; + goto End; + }; + + End: + if (bnSpend) BN_free(bnSpend); + if (bnOrder) BN_free(bnOrder); + if (bnc) BN_free(bnc); + if (bnOutP) BN_free(bnOutP); + if (P) EC_POINT_free(P); + if (bnP) BN_free(bnP); + if (bnScanSecret) BN_free(bnScanSecret); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +}; + + +int StealthSharedToSecretSpend(ec_secret& sharedS, ec_secret& spendSecret, ec_secret& secretOut) +{ + + int rv = 0; + std::vector vchOutP; + + BN_CTX* bnCtx = NULL; + BIGNUM* bnc = NULL; + BIGNUM* bnOrder = NULL; + BIGNUM* bnSpend = NULL; + + EC_GROUP* ecgrp = EC_GROUP_new_by_curve_name(NID_secp256k1); + + if (!ecgrp) + { + LogPrintf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + LogPrintf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnc = BN_bin2bn(&sharedS.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOrder = BN_new()) + || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) + { + LogPrintf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + }; + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) + { + LogPrintf("StealthSecretSpend(): bnSpend BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + //if (!BN_add(r, a, b)) return 0; + //return BN_nnmod(r, r, m, ctx); + if (!BN_mod_add(bnSpend, bnSpend, bnc, bnOrder, bnCtx)) + { + LogPrintf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + }; + + if (BN_is_zero(bnSpend)) // possible? + { + LogPrintf("StealthSecretSpend(): bnSpend is zero.\n"); + rv = 1; + goto End; + }; + + if (BN_num_bytes(bnSpend) != (int) ec_secret_size + || BN_bn2bin(bnSpend, &secretOut.e[0]) != (int) ec_secret_size) + { + LogPrintf("StealthSecretSpend(): bnSpend incorrect length.\n"); + rv = 1; + goto End; + }; + + End: + if (bnSpend) BN_free(bnSpend); + if (bnOrder) BN_free(bnOrder); + if (bnc) BN_free(bnc); + if (bnCtx) BN_CTX_free(bnCtx); + EC_GROUP_free(ecgrp); + + return rv; +}; + +bool IsStealthAddress(const std::string& encodedAddress) +{ + data_chunk raw; + + if (!DecodeBase58(encodedAddress, raw)) + { + LogPrintf("IsStealthAddress DecodeBase58 falied.\n"); + return false; + }; + + if (!VerifyChecksum(raw)) + { + LogPrintf("IsStealthAddress verify_checksum falied.\n"); + return false; + }; + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) + { + LogPrintf("IsStealthAddress too few bytes provided.\n"); + return false; + }; + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) + { + LogPrintf("IsStealthAddress version mismatch 0x%x != 0x%x.\n", version, stealth_version_byte); + return false; + }; + + return true; +}; diff --git a/src/stealthaddress.h b/src/stealthaddress.h new file mode 100644 index 00000000..72f06073 --- /dev/null +++ b/src/stealthaddress.h @@ -0,0 +1,119 @@ +// Copyright (c) 2014 The ShadowCoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file license.txt or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_STEALTHADDRESS_H +#define BITCOIN_STEALTHADDRESS_H + +#include "util.h" +#include "serialize.h" + +#include +#include +#include +#include + + +typedef std::vector data_chunk; + +const size_t ec_secret_size = 32; +const size_t ec_compressed_size = 33; +const size_t ec_uncompressed_size = 65; + +typedef struct ec_secret { uint8_t e[ec_secret_size]; } ec_secret; +typedef data_chunk ec_point; + +typedef uint32_t stealth_bitfield; + +struct stealth_prefix +{ + uint8_t number_bits; + stealth_bitfield bitfield; +}; + +template +T from_big_endian(Iterator in) +{ + //VERIFY_UNSIGNED(T); + T out = 0; + size_t i = sizeof(T); + while (0 < i) + out |= static_cast(*in++) << (8 * --i); + return out; +} + +template +T from_little_endian(Iterator in) +{ + //VERIFY_UNSIGNED(T); + T out = 0; + size_t i = 0; + while (i < sizeof(T)) + out |= static_cast(*in++) << (8 * i++); + return out; +} + +class CStealthAddress +{ +public: + CStealthAddress() + { + options = 0; + } + + uint8_t options; + ec_point scan_pubkey; + ec_point spend_pubkey; + //std::vector spend_pubkeys; + size_t number_signatures; + stealth_prefix prefix; + + mutable std::string label; + data_chunk scan_secret; + data_chunk spend_secret; + + bool SetEncoded(const std::string& encodedAddress); + std::string Encoded() const; + + bool operator <(const CStealthAddress& y) const + { + return memcmp(&scan_pubkey[0], &y.scan_pubkey[0], ec_compressed_size) < 0; + } + + bool operator ==(const CStealthAddress& y) const + { + return memcmp(&scan_pubkey[0], &y.scan_pubkey[0], ec_compressed_size) == 0; + } + + IMPLEMENT_SERIALIZE + ( + READWRITE(this->options); + READWRITE(this->scan_pubkey); + READWRITE(this->spend_pubkey); + READWRITE(this->label); + + READWRITE(this->scan_secret); + READWRITE(this->spend_secret); + ); + + + +}; + +void AppendChecksum(data_chunk& data); + +bool VerifyChecksum(const data_chunk& data); + +int GenerateRandomSecret(ec_secret& out); + +int SecretToPublicKey(const ec_secret& secret, ec_point& out); + +int StealthSecret(ec_secret& secret, ec_point& pubkey, const ec_point& pkSpend, ec_secret& sharedSOut, ec_point& pkOut); +int StealthSecretSpend(ec_secret& scanSecret, ec_point& ephemPubkey, ec_secret& spendSecret, ec_secret& secretOut); +int StealthSharedToSecretSpend(ec_secret& sharedS, ec_secret& spendSecret, ec_secret& secretOut); + +bool IsStealthAddress(const std::string& encodedAddress); + + +#endif // BITCOIN_STEALTHADDRESS_H + diff --git a/src/wallet.cpp b/src/wallet.cpp index fa7aecdd..22143d59 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -128,6 +128,35 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } +bool CWallet::Lock() +{ + if (IsLocked()) + return true; + { + LOCK(cs_wallet); + CWalletDB wdb(strWalletFile); + CStealthAddress sxAddrTemp; + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) + { + if (it->scan_secret.size() < 32) + continue; + CStealthAddress &sxAddr = const_cast(*it); + if (fDebug) + LogPrintf("Recrypting stealth key %s\n", sxAddr.Encoded().c_str()); + sxAddrTemp.scan_pubkey = sxAddr.scan_pubkey; + if (!wdb.ReadStealthAddress(sxAddrTemp)) + { + LogPrintf("Error: Failed to read stealth key from db %s\n", sxAddr.Encoded().c_str()); + continue; + } + sxAddr.spend_secret = sxAddrTemp.spend_secret; + }; + } + // return LockKeyStore(); + return true; +} + bool CWallet::Unlock(const SecureString& strWalletPassphrase) { CCrypter crypter; @@ -144,6 +173,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) if (CCryptoKeyStore::Unlock(vMasterKey)) return true; } + // UnlockStealthAddresses(vMasterKey); } return false; } @@ -593,6 +623,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& AssertLockHeld(cs_wallet); bool fExisted = mapWallet.count(hash); if (fExisted && !fUpdate) return false; + + mapValue_t mapNarr; + FindStealthTransactions(tx, mapNarr); + if (fExisted || IsMine(tx) || IsFromMe(tx)) { CWalletTx wtx(this,tx); @@ -1242,14 +1276,20 @@ bool CWallet::CreateTransaction(const vector >& vecSend, int64_t nTotalValue = nValue + nFeeRet; double dPriority = 0; + // vouts to the payees BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend) { CTxOut txout(s.second, s.first); - if (txout.IsDust(CTransaction::nMinRelayTxFee)) + CScript::const_iterator itTxA = txout.scriptPubKey.begin(); + if (!txout.IsScriptOpReturn()) { - strFailReason = _("Transaction amount too small"); - return false; + if (txout.IsDust(CTransaction::nMinRelayTxFee)) + { + LogPrintf("%s\n", txout.ToString()); + strFailReason = _("Transaction amount too small"); + return false; + } } wtxNew.vout.push_back(txout); } @@ -1428,8 +1468,724 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) return true; } +bool CWallet::NewStealthAddress(std::string& sError, std::string& sLabel, CStealthAddress& sxAddr) +{ + ec_secret scan_secret; + ec_secret spend_secret; + + if (GenerateRandomSecret(scan_secret) != 0 + || GenerateRandomSecret(spend_secret) != 0) + { + sError = "GenerateRandomSecret failed."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); + return false; + }; + + ec_point scan_pubkey, spend_pubkey; + if (SecretToPublicKey(scan_secret, scan_pubkey) != 0) + { + sError = "Could not get scan public key."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); + return false; + }; + + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) + { + sError = "Could not get spend public key."; + LogPrintf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); + return false; + }; + + if (fDebug) + { + LogPrintf("getnewstealthaddress: "); + LogPrintf("scan_pubkey "); + for (uint32_t i = 0; i < scan_pubkey.size(); ++i) + LogPrintf("%02x", scan_pubkey[i]); + LogPrintf("\n"); + + LogPrintf("spend_pubkey "); + for (uint32_t i = 0; i < spend_pubkey.size(); ++i) + LogPrintf("%02x", spend_pubkey[i]); + LogPrintf("\n"); + }; + sxAddr.label = sLabel; + sxAddr.scan_pubkey = scan_pubkey; + sxAddr.spend_pubkey = spend_pubkey; + + sxAddr.scan_secret.resize(32); + memcpy(&sxAddr.scan_secret[0], &scan_secret.e[0], 32); + sxAddr.spend_secret.resize(32); + memcpy(&sxAddr.spend_secret[0], &spend_secret.e[0], 32); + + return true; +} + +bool CWallet::AddStealthAddress(CStealthAddress& sxAddr) +{ + LOCK(cs_wallet); + + // must add before changing spend_secret + stealthAddresses.insert(sxAddr); + + bool fOwned = sxAddr.scan_secret.size() == ec_secret_size; + + if (fOwned) + { + // -- owned addresses can only be added when wallet is unlocked + if (IsLocked()) + { + LogPrintf("Error: CWallet::AddStealthAddress wallet must be unlocked.\n"); + stealthAddresses.erase(sxAddr); + return false; + }; + + if (IsCrypted()) + { + std::vector vchCryptedSecret; + CSecret vchSecret; + vchSecret.resize(32); + memcpy(&vchSecret[0], &sxAddr.spend_secret[0], 32); + + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); + if (!EncryptSecret(vMasterKey, vchSecret, iv, vchCryptedSecret)) + { + LogPrintf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded().c_str()); + stealthAddresses.erase(sxAddr); + return false; + }; + sxAddr.spend_secret = vchCryptedSecret; + }; + }; + + bool rv = CWalletDB(strWalletFile).WriteStealthAddress(sxAddr); + + if (rv) + NotifyAddressBookChanged(this, sxAddr, sxAddr.label, fOwned, "", CT_NEW); + + return rv; +} + +bool CWallet::UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn) +{ + // -- decrypt spend_secret of Stealth Addresses + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) + { + if (it->scan_secret.size() < 32) + continue; // Stealth Address is not owned + + // -- CStealthAddress are only sorted on spend_pubkey + CStealthAddress &sxAddr = const_cast(*it); + + if (fDebug) + LogPrintf("Decrypting stealth key %s\n", sxAddr.Encoded().c_str()); + + CSecret vchSecret; + uint256 iv = Hash(sxAddr.spend_pubkey.begin(), sxAddr.spend_pubkey.end()); + if(!DecryptSecret(vMasterKeyIn, sxAddr.spend_secret, iv, vchSecret) + || vchSecret.size() != 32) + { + LogPrintf("Error: Failed decrypting stealth key %s\n", sxAddr.Encoded().c_str()); + continue; + }; + + ec_secret testSecret; + memcpy(&testSecret.e[0], &vchSecret[0], 32); + ec_point pkSpendTest; + + if (SecretToPublicKey(testSecret, pkSpendTest) != 0 + || pkSpendTest != sxAddr.spend_pubkey) + { + LogPrintf("Error: Failed decrypting stealth key, public key mismatch %s\n", sxAddr.Encoded().c_str()); + continue; + }; + + sxAddr.spend_secret.resize(32); + memcpy(&sxAddr.spend_secret[0], &vchSecret[0], 32); + }; + + CryptedKeyMap::iterator mi = mapCryptedKeys.begin(); + for (; mi != mapCryptedKeys.end(); ++mi) + { + CPubKey &pubKey = (*mi).second.first; + std::vector &vchCryptedSecret = (*mi).second.second; + if (vchCryptedSecret.size() != 0) + continue; + + CKeyID ckid = pubKey.GetID(); + CBitcoinAddress addr(ckid); + + StealthKeyMetaMap::iterator mi = mapStealthKeyMeta.find(ckid); + if (mi == mapStealthKeyMeta.end()) + { + LogPrintf("Error: No metadata found to add secret for %s\n", addr.ToString().c_str()); + continue; + }; + + CStealthKeyMetadata& sxKeyMeta = mi->second; + + CStealthAddress sxFind; + sxFind.scan_pubkey = sxKeyMeta.pkScan.Raw(); + + std::set::iterator si = stealthAddresses.find(sxFind); + if (si == stealthAddresses.end()) + { + LogPrintf("No stealth key found to add secret for %s\n", addr.ToString().c_str()); + continue; + }; + + if (fDebug) + LogPrintf("Expanding secret for %s\n", addr.ToString().c_str()); + + ec_secret sSpendR; + ec_secret sSpend; + ec_secret sScan; + + if (si->spend_secret.size() != ec_secret_size + || si->scan_secret.size() != ec_secret_size) + { + LogPrintf("Stealth Address has no secret key for %s\n", addr.ToString().c_str()); + continue; + } + memcpy(&sScan.e[0], &si->scan_secret[0], ec_secret_size); + memcpy(&sSpend.e[0], &si->spend_secret[0], ec_secret_size); + + ec_point pkEphem = sxKeyMeta.pkEphem.Raw(); + if (StealthSecretSpend(sScan, pkEphem, sSpend, sSpendR) != 0) + { + LogPrintf("StealthSecretSpend() failed.\n"); + continue; + }; + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) + { + LogPrintf("SecretToPublicKey() failed.\n"); + continue; + }; + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.Set(vchSecret.begin(), vchSecret.end(), true); + } catch (std::exception& e) { + LogPrintf("ckey.SetSecret() threw: %s.\n", e.what()); + continue; + }; + + CPubKey cpkT = ckey.GetPubKey(); + + if (!cpkT.IsValid()) + { + LogPrintf("cpkT is invalid.\n"); + continue; + }; + + if (cpkT != pubKey) + { + LogPrintf("Error: Generated secret does not match.\n"); + if (fDebug) + { + LogPrintf("cpkT %s\n", HexStr(cpkT.Raw()).c_str()); + // LogPrintf("pubKey %s\n", HexStr(pubKey.Raw()).c_str()); + }; + continue; + }; + + if (!ckey.IsValid()) + { + LogPrintf("Reconstructed key is invalid.\n"); + continue; + }; + + if (fDebug) + { + CKeyID keyID = cpkT.GetID(); + CBitcoinAddress coinAddress(keyID); + LogPrintf("Adding secret to key %s.\n", coinAddress.ToString().c_str()); + }; + + if (!AddKey(ckey)) + { + LogPrintf("AddKey failed.\n"); + continue; + }; + + if (!CWalletDB(strWalletFile).EraseStealthKeyMeta(ckid)) + LogPrintf("EraseStealthKeyMeta failed for %s\n", addr.ToString().c_str()); + }; + return true; +} + +bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist) +{ + if (fDebug) + LogPrintf("UpdateStealthAddress %s\n", addr.c_str()); + + + CStealthAddress sxAddr; + + if (!sxAddr.SetEncoded(addr)) + return false; + + std::set::iterator it; + it = stealthAddresses.find(sxAddr); + + ChangeType nMode = CT_UPDATED; + CStealthAddress sxFound; + if (it == stealthAddresses.end()) + { + if (addIfNotExist) + { + sxFound = sxAddr; + sxFound.label = label; + stealthAddresses.insert(sxFound); + nMode = CT_NEW; + } else + { + LogPrintf("UpdateStealthAddress %s, not in set\n", addr.c_str()); + return false; + }; + } else + { + sxFound = const_cast(*it); + + if (sxFound.label == label) + { + // no change + return true; + }; + + it->label = label; // update in .stealthAddresses + + if (sxFound.scan_secret.size() == ec_secret_size) + { + LogPrintf("UpdateStealthAddress: todo - update owned Stealth Address.\n"); + return false; + }; + }; + + sxFound.label = label; + + if (!CWalletDB(strWalletFile).WriteStealthAddress(sxFound)) + { + LogPrintf("UpdateStealthAddress(%s) Write to db failed.\n", addr.c_str()); + return false; + }; + + bool fOwned = sxFound.scan_secret.size() == ec_secret_size; + NotifyAddressBookChanged(this, sxFound, sxFound.label, fOwned, "", nMode); + + return true; +} + +bool CWallet::CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl) +{ + vector< pair > vecSend; + vecSend.push_back(make_pair(scriptPubKey, nValue)); + + CScript scriptP = CScript() << OP_RETURN << P; + if (narr.size() > 0) + scriptP = scriptP << OP_RETURN << narr; + + vecSend.push_back(make_pair(scriptP, 0)); + + // -- shuffle inputs, change output won't mix enough as it must be not fully random for plantext narrations + std::random_shuffle(vecSend.begin(), vecSend.end()); + + string strError; + // int nChangePos; + bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, /*nChangePos,*/ strError, coinControl); + + /* + // -- the change txn is inserted in a random pos, check here to match narr to output + if (rv && narr.size() > 0) + { + for (unsigned int k = 0; k < wtxNew.vout.size(); ++k) + { + if (wtxNew.vout[k].scriptPubKey != scriptPubKey + || wtxNew.vout[k].nValue != nValue) + continue; + + char key[64]; + if (snprintf(key, sizeof(key), "n_%u", k) < 1) + { + LogPrintf("CreateStealthTransaction(): Error creating narration key."); + break; + }; + wtxNew.mapValue[key] = sNarr; + break; + }; + }; + */ + + return rv; +} + +string CWallet::SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee) +{ + CReserveKey reservekey(this); + int64_t nFeeRequired; + + if ( (!TestNet() && (chainActive.Height() < BLOCK_STEALTH_START)) + || (TestNet() && (chainActive.Height() < 200)) + ) + { + string strError = _("Error: Stealth addresses not yet supported"); + LogPrintf("SendStealthMoney() : %s", strError.c_str()); + return strError; + } + + if (IsLocked()) + { + string strError = _("Error: Wallet locked, unable to create transaction "); + LogPrintf("SendStealthMoney() : %s", strError.c_str()); + return strError; + } + + if (!CreateStealthTransaction(scriptPubKey, nValue, P, narr, sNarr, wtxNew, reservekey, nFeeRequired)) + { + string strError; + if (nValue + nFeeRequired > GetBalance()) + strError = strprintf(_("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds "), FormatMoney(nFeeRequired).c_str()); + else + strError = _("Error: Transaction creation failed "); + LogPrintf("SendStealthMoney() : %s", strError.c_str()); + return strError; + } + + /* Stealth TODO + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) + return "ABORTED"; + */ + + if (!CommitTransaction(wtxNew, reservekey)) + return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); + + return ""; +} + +bool CWallet::SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee) +{ + // -- Check amount + if (nValue <= 0) + { + sError = "Invalid amount"; + return false; + }; + if (nValue + nTransactionFee > GetBalance()) + { + sError = "Insufficient funds"; + return false; + }; + + ec_secret ephem_secret; + ec_secret secretShared; + ec_point pkSendTo; + ec_point ephem_pubkey; + + if (GenerateRandomSecret(ephem_secret) != 0) + { + sError = "GenerateRandomSecret failed."; + return false; + }; + + if (StealthSecret(ephem_secret, sxAddress.scan_pubkey, sxAddress.spend_pubkey, secretShared, pkSendTo) != 0) + { + sError = "Could not generate receiving public key."; + return false; + }; + + CPubKey cpkTo(pkSendTo); + if (!cpkTo.IsValid()) + { + sError = "Invalid public key generated."; + return false; + }; + + CKeyID ckidTo = cpkTo.GetID(); + + CBitcoinAddress addrTo(ckidTo); + + if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0) + { + sError = "Could not generate ephem public key."; + return false; + }; + + if (fDebug) + { + LogPrintf("Stealth send to generated pubkey %ui: %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str()); + LogPrintf("hash %s\n", addrTo.ToString().c_str()); + LogPrintf("ephem_pubkey %ui: %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey).c_str()); + }; + + std::vector vchNarr; + if (sNarr.length() > 0) + { + //SecMsgCrypter crypter; + //crypter.SetKey(&secretShared.e[0], &ephem_pubkey[0]); + + //if (!crypter.Encrypt((uint8_t*)&sNarr[0], sNarr.length(), vchNarr)) + //{ + // sError = "Narration encryption failed."; + // return false; + //}; + + if (vchNarr.size() > 48) + { + sError = "Encrypted narration is too long."; + return false; + }; + }; + + // -- Parse Bitcoin address + CScript scriptPubKey; + scriptPubKey.SetDestination(addrTo.Get()); + + if ((sError = SendStealthMoney(scriptPubKey, nValue, ephem_pubkey, vchNarr, sNarr, wtxNew, fAskFee)) != "") + return false; + + + return true; +} + +bool CWallet::FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNarr) +{ + if (fDebug) + LogPrintf("FindStealthTransactions() tx: %s\n", tx.GetHash().GetHex().c_str()); + + // mapNarr.clear(); + + LOCK(cs_wallet); + ec_secret sSpendR; + ec_secret sSpend; + ec_secret sScan; + ec_secret sShared; + + ec_point pkExtracted; + + std::vector vchEphemPK; + std::vector vchDataB; + std::vector vchENarr; + opcodetype opCode; + char cbuf[256]; + + int32_t nOutputIdOuter = -1; + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + nOutputIdOuter++; + // -- for each OP_RETURN need to check all other valid outputs + + //LogPrintf("txout scriptPubKey %s\n", txout.scriptPubKey.ToString().c_str()); + CScript::const_iterator itTxA = txout.scriptPubKey.begin(); + + if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK) + || opCode != OP_RETURN) + continue; + else + if (!txout.scriptPubKey.GetOp(itTxA, opCode, vchEphemPK) + || vchEphemPK.size() != 33) + { + // -- look for plaintext narrations + if (vchEphemPK.size() > 1 + && vchEphemPK[0] == 'n' + && vchEphemPK[1] == 'p') + { + if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && opCode == OP_RETURN + && txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && vchENarr.size() > 0) + { + std::string sNarr = std::string(vchENarr.begin(), vchENarr.end()); + + snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputIdOuter-1); // plaintext narration always matches preceding value output + mapNarr[cbuf] = sNarr; + } else + { + LogPrintf("Warning: FindStealthTransactions() tx: %s, Could not extract plaintext narration.\n", tx.GetHash().GetHex().c_str()); + }; + } + + continue; + } + + int32_t nOutputId = -1; + nStealth++; + BOOST_FOREACH(const CTxOut& txoutB, tx.vout) + { + nOutputId++; + + if (&txoutB == &txout) + continue; + + bool txnMatch = false; // only 1 txn will match an ephem pk + //LogPrintf("txoutB scriptPubKey %s\n", txoutB.scriptPubKey.ToString().c_str()); + + CTxDestination address; + if (!ExtractDestination(txoutB.scriptPubKey, address)) + continue; + + if (address.type() != typeid(CKeyID)) + continue; + + CKeyID ckidMatch = boost::get(address); + + if (HaveKey(ckidMatch)) // no point checking if already have key + continue; + + std::set::iterator it; + for (it = stealthAddresses.begin(); it != stealthAddresses.end(); ++it) + { + if (it->scan_secret.size() != ec_secret_size) + continue; // Stealth Address is not owned + + //LogPrintf("it->Encodeded() %s\n", it->Encoded().c_str()); + memcpy(&sScan.e[0], &it->scan_secret[0], ec_secret_size); + + if (StealthSecret(sScan, vchEphemPK, it->spend_pubkey, sShared, pkExtracted) != 0) + { + LogPrintf("StealthSecret failed.\n"); + continue; + }; + //LogPrintf("pkExtracted %"PRIszu": %s\n", pkExtracted.size(), HexStr(pkExtracted).c_str()); + + CPubKey cpkE(pkExtracted); + + if (!cpkE.IsValid()) + continue; + CKeyID ckidE = cpkE.GetID(); + + if (ckidMatch != ckidE) + continue; + + if (fDebug) + LogPrintf("Found stealth txn to address %s\n", it->Encoded().c_str()); + + if (IsLocked()) + { + if (fDebug) + LogPrintf("Wallet is locked, adding key without secret.\n"); + + // -- add key without secret + std::vector vchEmpty; + AddCryptedKey(cpkE, vchEmpty); + CKeyID keyId = cpkE.GetID(); + CBitcoinAddress coinAddress(keyId); + std::string sLabel = it->Encoded(); + std::string sPurpose; + SetAddressBook(keyId, sLabel, sPurpose); + + CPubKey cpkEphem(vchEphemPK); + CPubKey cpkScan(it->scan_pubkey); + CStealthKeyMetadata lockedSkMeta(cpkEphem, cpkScan); + + if (!CWalletDB(strWalletFile).WriteStealthKeyMeta(keyId, lockedSkMeta)) + LogPrintf("WriteStealthKeyMeta failed for %s\n", coinAddress.ToString().c_str()); + + mapStealthKeyMeta[keyId] = lockedSkMeta; + nFoundStealth++; + } else + { + if (it->spend_secret.size() != ec_secret_size) + continue; + memcpy(&sSpend.e[0], &it->spend_secret[0], ec_secret_size); + + + if (StealthSharedToSecretSpend(sShared, sSpend, sSpendR) != 0) + { + LogPrintf("StealthSharedToSecretSpend() failed.\n"); + continue; + }; + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) + { + LogPrintf("SecretToPublicKey() failed.\n"); + continue; + }; + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.Set(vchSecret.begin(), vchSecret.end(), true); + } catch (std::exception& e) { + LogPrintf("ckey.SetSecret() threw: %s.\n", e.what()); + continue; + }; + + CPubKey cpkT = ckey.GetPubKey(); + if (!cpkT.IsValid()) + { + LogPrintf("cpkT is invalid.\n"); + continue; + }; + + if (!ckey.IsValid()) + { + LogPrintf("Reconstructed key is invalid.\n"); + continue; + }; + + CKeyID keyID = cpkT.GetID(); + if (fDebug) + { + CBitcoinAddress coinAddress(keyID); + LogPrintf("Adding key %s.\n", coinAddress.ToString().c_str()); + }; + + if (!AddKey(ckey)) + { + LogPrintf("AddKey failed.\n"); + continue; + }; + + std::string sLabel = it->Encoded(); + std::string sPurpose; + SetAddressBook(keyID, sLabel, sPurpose); + nFoundStealth++; + }; + + if (txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && opCode == OP_RETURN + && txout.scriptPubKey.GetOp(itTxA, opCode, vchENarr) + && vchENarr.size() > 0) + { + //SecMsgCrypter crypter; + //crypter.SetKey(&sShared.e[0], &vchEphemPK[0]); + //std::vector vchNarr; + //if (!crypter.Decrypt(&vchENarr[0], vchENarr.size(), vchNarr)) + //{ + // LogPrintf("Decrypt narration failed.\n"); + // continue; + //}; + //std::string sNarr = std::string(vchNarr.begin(), vchNarr.end()); + + //snprintf(cbuf, sizeof(cbuf), "n_%d", nOutputId); + //mapNarr[cbuf] = sNarr; + }; + + txnMatch = true; + break; + }; + if (txnMatch) + break; + }; + }; + + return true; +}; string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew) { diff --git a/src/wallet.h b/src/wallet.h index 88ee8719..cc0f2fba 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -13,6 +13,7 @@ #include "ui_interface.h" #include "util.h" #include "walletdb.h" +#include "stealthaddress.h" #include #include @@ -39,6 +40,8 @@ class CReserveKey; class CScript; class CWalletTx; +typedef std::map StealthKeyMetaMap; + /** (client) version numbers for particular wallet features */ enum WalletFeature { @@ -94,6 +97,8 @@ public: StringMap destdata; }; +typedef std::map mapValue_t; + /** A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. */ @@ -141,6 +146,10 @@ public: MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID; + std::set stealthAddresses; + StealthKeyMetaMap mapStealthKeyMeta; + uint32_t nStealth, nFoundStealth; + CWallet() { SetNull(); @@ -222,6 +231,7 @@ public: /// Look up a destination data tuple in the store, return true if found false otherwise bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; + bool Lock(); bool Unlock(const SecureString& strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); @@ -258,6 +268,16 @@ public: bool CreateTransaction(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); + + bool NewStealthAddress(std::string& sError, std::string& sLabel, CStealthAddress& sxAddr); + bool AddStealthAddress(CStealthAddress& sxAddr); + bool UnlockStealthAddresses(const CKeyingMaterial& vMasterKeyIn); + bool UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist); + bool CreateStealthTransaction(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl = NULL); + string SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, std::vector& narr, std::string& sNarr, CWalletTx& wtxNew, bool fAskFee); + bool SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, std::string& sNarr, CWalletTx& wtxNew, std::string& sError, bool fAskFee = false); + bool FindStealthTransactions(const CTransaction& tx, mapValue_t& mapNarr); + std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew); std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew); @@ -422,10 +442,6 @@ public: void KeepKey(); }; - -typedef std::map mapValue_t; - - static void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) { if (!mapValue.count("n")) diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 80e9dded..d9524b60 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -112,6 +112,31 @@ bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false); } +bool CWalletDB::WriteStealthKeyMeta(const CKeyID& keyId, const CStealthKeyMetadata& sxKeyMeta) +{ + nWalletDBUpdated++; + return Write(std::make_pair(std::string("sxKeyMeta"), keyId), sxKeyMeta, true); +} + +bool CWalletDB::EraseStealthKeyMeta(const CKeyID& keyId) +{ + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("sxKeyMeta"), keyId)); +} + +bool CWalletDB::WriteStealthAddress(const CStealthAddress& sxAddr) +{ + nWalletDBUpdated++; + + return Write(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr, true); +} + +bool CWalletDB::ReadStealthAddress(CStealthAddress& sxAddr) +{ + // -- set scan_pubkey before reading + return Read(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr); +} + bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) { nWalletDBUpdated++; @@ -566,6 +591,28 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + else if (strType == "sxAddr") + { + if (fDebug) + LogPrintf("WalletDB ReadKeyValue sxAddr\n"); + + CStealthAddress sxAddr; + ssValue >> sxAddr; + + pwallet->stealthAddresses.insert(sxAddr); + } + else if (strType == "sxKeyMeta") + { + if (fDebug) + LogPrintf("WalletDB ReadKeyValue sxKeyMeta\n"); + + CKeyID keyId; + ssKey >> keyId; + CStealthKeyMetadata sxKeyMeta; + ssValue >> sxKeyMeta; + + pwallet->mapStealthKeyMeta[keyId] = sxKeyMeta; + } } catch (...) { return false; diff --git a/src/walletdb.h b/src/walletdb.h index 3bfb4360..dda7584b 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -7,6 +7,7 @@ #include "db.h" #include "key.h" +#include "stealthaddress.h" #include #include @@ -67,6 +68,29 @@ public: } }; +class CStealthKeyMetadata +{ +// -- used to get secret for keys created by stealth transaction with wallet locked +public: + CStealthKeyMetadata() {}; + + CStealthKeyMetadata(CPubKey pkEphem_, CPubKey pkScan_) + { + pkEphem = pkEphem_; + pkScan = pkScan_; + }; + + CPubKey pkEphem; + CPubKey pkScan; + + IMPLEMENT_SERIALIZE + ( + READWRITE(pkEphem); + READWRITE(pkScan); + ) + +}; + /** Access to the wallet database (wallet.dat) */ class CWalletDB : public CDB { @@ -93,6 +117,11 @@ public: bool WriteCScript(const uint160& hash, const CScript& redeemScript); + bool WriteStealthKeyMeta(const CKeyID& keyId, const CStealthKeyMetadata& sxKeyMeta); + bool EraseStealthKeyMeta(const CKeyID& keyId); + bool WriteStealthAddress(const CStealthAddress& sxAddr); + bool ReadStealthAddress(CStealthAddress& sxAddr); + bool WriteBestBlock(const CBlockLocator& locator); bool ReadBestBlock(CBlockLocator& locator);