diff --git a/deepcoin-qt.pro b/deepcoin-qt.pro index c007d644..07e8668f 100644 --- a/deepcoin-qt.pro +++ b/deepcoin-qt.pro @@ -165,6 +165,7 @@ HEADERS += src/qt/bitcoingui.h \ src/db.h \ src/walletdb.h \ src/script.h \ + src/stealth.h \ src/init.h \ src/bloom.h \ src/mruset.h \ @@ -301,7 +302,8 @@ SOURCES += src/qt/bitcoin.cpp \ src/shavite.c \ src/echo.c \ src/simd.c \ - src/checkpointsync.cpp + src/checkpointsync.cpp \ + src/stealth.cpp RESOURCES += src/qt/bitcoin.qrc diff --git a/src/base58.h b/src/base58.h index 4fe2e126..8060e5e8 100644 --- a/src/base58.h +++ b/src/base58.h @@ -264,6 +264,7 @@ class CBitcoinAddressVisitor : public boost::static_visitor CBitcoinAddressVisitor(CBitcoinAddress *addrIn) : addr(addrIn) { } bool operator()(const CKeyID &id) const; bool operator()(const CScriptID &id) const; + bool operator()(const CStealthAddress &stxAddr) const; bool operator()(const CNoDestination &no) const; }; @@ -392,6 +393,7 @@ class CBitcoinAddress : public CBase58Data bool inline CBitcoinAddressVisitor::operator()(const CKeyID &id) const { return addr->Set(id); } bool inline CBitcoinAddressVisitor::operator()(const CScriptID &id) const { return addr->Set(id); } +bool inline CBitcoinAddressVisitor::operator()(const CStealthAddress &stxAddr) const { return false; } bool inline CBitcoinAddressVisitor::operator()(const CNoDestination &id) const { return false; } /** A base58-encoded secret key */ diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 37d533a5..e79318b8 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -271,6 +271,13 @@ static const CRPCCommand vRPCCommands[] = { "lockunspent", &lockunspent, false, false, true }, { "listlockunspent", &listlockunspent, false, false, true }, { "verifychain", &verifychain, true, false, false }, + { "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 }, }; CRPCTable::CRPCTable() @@ -1208,6 +1215,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 5) ConvertTo(params[5]); if (strMethod == "sendalert" && n > 6) ConvertTo(params[6]); if (strMethod == "enforcecheckpoint" && n > 0) ConvertTo(params[0]); + if (strMethod == "sendtostealthaddress" && n > 1) ConvertTo(params[1]); return params; } diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 58532017..51e0d05e 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -212,4 +212,12 @@ extern json_spirit::Value gettxoutsetinfo(const json_spirit::Array& params, bool extern json_spirit::Value gettxout(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value verifychain(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/keystore.cpp b/src/keystore.cpp index e0cf805a..bffe860b 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -73,7 +73,7 @@ bool CCryptoKeyStore::SetCrypted() return true; } -bool CCryptoKeyStore::Lock() +bool CCryptoKeyStore::LockKeyStore() { if (!SetCrypted()) return false; diff --git a/src/keystore.h b/src/keystore.h index ab369bbf..5efd497a 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -105,15 +105,14 @@ typedef std::map > > Crypt class CCryptoKeyStore : public CBasicKeyStore { private: - CryptedKeyMap mapCryptedKeys; - - CKeyingMaterial vMasterKey; - // if fUseCrypto is true, mapKeys must be empty // if fUseCrypto is false, vMasterKey must be empty bool fUseCrypto; protected: + CryptedKeyMap mapCryptedKeys; + CKeyingMaterial vMasterKey; + bool SetCrypted(); // will encrypt previously unencrypted keys @@ -143,7 +142,7 @@ class CCryptoKeyStore : public CBasicKeyStore return result; } - bool Lock(); + bool LockKeyStore(); virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); bool AddKey(const CKey& key); diff --git a/src/makefile.linux-mingw b/src/makefile.linux-mingw deleted file mode 100644 index c7bb8293..00000000 --- a/src/makefile.linux-mingw +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright (c) 2009-2010 Satoshi Nakamoto -# Distributed under the MIT/X11 software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. - -DEPSDIR:=/usr/i686-w64-mingw32 - -CC := i686-w64-mingw32-gcc -CXX := i686-w64-mingw32-g++ - -USE_UPNP:=0 -USE_IPV6:=1 - -INCLUDEPATHS= \ - -I"$(CURDIR)" \ - -I"$(CURDIR)"/obj \ - -I"$(DEPSDIR)/include" \ - -I"$(DEPSDIR)" - -LIBPATHS= \ - -L"$(DEPSDIR)/lib" - -LIBS= \ - $(CURDIR)/leveldb/libleveldb.a $(CURDIR)/leveldb/libmemenv.a \ - -l boost_system-mt-s \ - -l boost_filesystem-mt-s \ - -l boost_program_options-mt-s \ - -l boost_thread_win32-mt-s \ - -l boost_chrono-mt-s \ - -l db_cxx \ - -l ssl \ - -l crypto - -DEFS=-D_MT -DWIN32 -D_WINDOWS -DBOOST_THREAD_USE_LIB -DBOOST_SPIRIT_THREADSAFE -DEBUGFLAGS=-g -xCXXFLAGS=-O2 -w -Wall -Wextra -Wformat -Wformat-security -Wno-unused-parameter $(DEBUGFLAGS) $(DEFS) $(INCLUDEPATHS) $(CXXFLAGS) -# enable: ASLR, DEP and large address aware -xLDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--large-address-aware -static-libgcc -static-libstdc++ $(LDFLAGS) - -TESTDEFS = -DTEST_DATA_DIR=$(abspath test/data) - -ifndef USE_UPNP - override USE_UPNP = - -endif -ifneq (${USE_UPNP}, -) - LIBPATHS += -L"$(DEPSDIR)/miniupnpc" - LIBS += -l miniupnpc -l iphlpapi - DEFS += -DSTATICLIB -DUSE_UPNP=$(USE_UPNP) -endif - -ifneq (${USE_IPV6}, -) - DEFS += -DUSE_IPV6=$(USE_IPV6) -endif - -LIBS += -l mingwthrd -l kernel32 -l user32 -l gdi32 -l comdlg32 -l winspool -l winmm -l shell32 -l comctl32 -l ole32 -l oleaut32 -l uuid -l rpcrt4 -l advapi32 -l ws2_32 -l mswsock -l shlwapi - -# TODO: make the mingw builds smarter about dependencies, like the linux/osx builds are -HEADERS = $(wildcard *.h) - -OBJS= \ - leveldb/libleveldb.a \ - obj/alert.o \ - obj/version.o \ - obj/checkpoints.o \ - obj/netbase.o \ - obj/addrman.o \ - obj/crypter.o \ - obj/key.o \ - obj/db.o \ - obj/init.o \ - obj/keystore.o \ - obj/main.o \ - obj/net.o \ - obj/protocol.o \ - obj/bitcoinrpc.o \ - obj/rpcdump.o \ - obj/rpcnet.o \ - obj/rpcmining.o \ - obj/rpcwallet.o \ - obj/rpcblockchain.o \ - obj/rpcrawtransaction.o \ - obj/script.o \ - obj/scrypt.o \ - obj/sync.o \ - obj/util.o \ - obj/wallet.o \ - obj/walletdb.o \ - obj/noui.o \ - obj/hash.o \ - obj/bloom.o \ - obj/leveldb.o \ - obj/txdb.o \ - obj/cubehash.o \ - obj/luffa.o \ - obj/aes_helper.o \ - obj/echo.o \ - obj/shavite.o \ - obj/simd.o \ - obj/checkpointsync.o - -all: deepcoind.exe - -DEFS += -I"$(CURDIR)/leveldb/include" -DEFS += -I"$(CURDIR)/leveldb/helpers" -leveldb/libleveldb.a: - @echo "Building LevelDB ..." && cd leveldb && TARGET_OS=OS_WINDOWS_CROSSCOMPILE $(MAKE) CC=$(CC) CXX=$(CXX) OPT="$(xCXXFLAGS)" libleveldb.a libmemenv.a && i686-w64-mingw32-ranlib libleveldb.a && i686-w64-mingw32-ranlib libmemenv.a && cd .. - -obj/build.h: FORCE - /bin/sh ../share/genbuild.sh obj/build.h -version.cpp: obj/build.h -DEFS += -DHAVE_BUILD_INFO - -obj/%.o: %.cpp $(HEADERS) - $(CXX) -c $(xCXXFLAGS) -o $@ $< - -obj/%.o: %.c $(HEADERS) - $(CXX) -c $(xCXXFLAGS) -fpermissive -o $@ $< - -deepcoind.exe: $(OBJS:obj/%=obj/%) - $(CXX) $(xCXXFLAGS) $(xLDFLAGS) -o $@ $(LIBPATHS) $^ $(LIBS) - -TESTOBJS := $(patsubst test/%.cpp,obj-test/%.o,$(wildcard test/*.cpp)) - -obj-test/%.o: test/%.cpp $(HEADERS) - $(CXX) -c $(TESTDEFS) $(xCXXFLAGS) -o $@ $< - -test_deepcoin.exe: $(TESTOBJS) $(filter-out obj/init.o,$(OBJS:obj/%=obj/%)) - $(CXX) $(xCXXFLAGS) $(xLDFLAGS) -o $@ $(LIBPATHS) $^ -lboost_unit_test_framework-mt-s $(LIBS) - - -clean: - -rm -f obj/*.o - -rm -f deepcoind.exe - -rm -f obj-test/*.o - -rm -f test_deepcoin.exe - -rm -f obj/build.h - cd leveldb && TARGET_OS=OS_WINDOWS_CROSSCOMPILE $(MAKE) clean && cd .. - -FORCE: diff --git a/src/makefile.mingw b/src/makefile.mingw index bf2eb8c5..605adc53 100644 --- a/src/makefile.mingw +++ b/src/makefile.mingw @@ -107,7 +107,8 @@ OBJS= \ obj/echo.o \ obj/shavite.o \ obj/simd.o \ - obj/checkpointsync.o + obj/checkpointsync.o \ + obj/stealth.o all: deepcoind.exe diff --git a/src/makefile.osx b/src/makefile.osx index ce6f18c0..1a64deee 100644 --- a/src/makefile.osx +++ b/src/makefile.osx @@ -113,7 +113,8 @@ OBJS= \ obj/echo.o \ obj/shavite.o \ obj/simd.o \ - obj/checkpointsync.o + obj/checkpointsync.o \ + obj/stealth.o ifndef USE_UPNP override USE_UPNP = - diff --git a/src/makefile.unix b/src/makefile.unix index ef0c9843..860e4735 100644 --- a/src/makefile.unix +++ b/src/makefile.unix @@ -149,7 +149,8 @@ OBJS= \ obj/echo.o \ obj/shavite.o \ obj/simd.o \ - obj/checkpointsync.o + obj/checkpointsync.o \ + obj/stealth.o all: deepcoind diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index ab5a4a2c..c29f3276 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -9,6 +9,7 @@ #include "wallet.h" #include "base58.h" +#include "stealth.h" #include @@ -25,10 +26,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 @@ -72,6 +74,15 @@ class AddressTablePriv 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 qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); @@ -221,7 +232,7 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, AddressTableEntry *rec = static_cast(index.internalPointer()); editStatus = OK; - + std::string strTemp, strValue; if(role == Qt::EditRole) { LOCK(wallet->cs_wallet); /* For SetAddressBook / DelAddressBook */ @@ -234,7 +245,15 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, editStatus = NO_CHANGES; return false; } - wallet->SetAddressBookName(curAddress, value.toString().toStdString()); + strTemp = rec->address.toStdString(); + if (IsStealthAddress(strTemp)) + { + strValue = value.toString().toStdString(); + wallet->UpdateStealthAddress(strTemp, strValue, false); + } else + { + wallet->SetAddressBookName(CBitcoinAddress(strTemp).Get(), value.toString().toStdString()); + } } else if(index.column() == Address) { CTxDestination newAddress = CBitcoinAddress(value.toString().toStdString()).Get(); // Refuse to set invalid address, set error status and return false @@ -243,6 +262,13 @@ bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, editStatus = INVALID_ADDRESS; return false; } + std::string sTemp = value.toString().toStdString(); + if (IsStealthAddress(sTemp)) + { + printf("IsStealthAddress = INVALID_ADDRESS\n"); + editStatus = INVALID_ADDRESS; + return false; + } // Do nothing, if old address == new address else if(newAddress == curAddress) { @@ -319,7 +345,7 @@ void AddressTableModel::updateEntry(const QString &address, const QString &label priv->updateEntry(address, label, isMine, 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(); @@ -328,19 +354,45 @@ 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->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); + } } } else if(type == Receive) @@ -353,24 +405,37 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con editStatus = WALLET_UNLOCK_FAILURE; return QString(); } - CPubKey newKey; - if(!wallet->GetKeyFromPool(newKey, true)) + if (addressType == AT_Stealth) { - editStatus = KEY_GENERATION_FAILURE; - return QString(); + CStealthAddress newStealthAddr; + std::string sError; + if (!wallet->NewStealthAddress(sError, strLabel, newStealthAddr) + || !wallet->AddStealthAddress(newStealthAddr)) + { + editStatus = KEY_GENERATION_FAILURE; + return QString(); + } + strAddress = newStealthAddr.Encoded(); + } else { + CPubKey newKey; + if(!wallet->GetKeyFromPool(newKey, true)) + { + editStatus = KEY_GENERATION_FAILURE; + return QString(); + } + strAddress = CBitcoinAddress(newKey.GetID()).ToString(); + + { + LOCK(wallet->cs_wallet); + wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); + } } - strAddress = CBitcoinAddress(newKey.GetID()).ToString(); } else { return QString(); } - // Add entry - { - LOCK(wallet->cs_wallet); - wallet->SetAddressBookName(CBitcoinAddress(strAddress).Get(), strLabel); - } return QString::fromStdString(strAddress); } @@ -397,11 +462,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); + 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); + } } } return QString(); diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 8e8c9b34..c2ada90d 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -22,10 +22,17 @@ class AddressTableModel : public QAbstractTableModel public: explicit AddressTableModel(CWallet *wallet, WalletModel *parent = 0); ~AddressTableModel(); + + enum AddressType { + AT_Unknown = 0, /**< User specified label */ + AT_Normal = 1, /**< Bitcoin address */ + AT_Stealth = 2 /**< Stealth address */ + }; enum ColumnIndex { Label = 0, /**< User specified label */ - Address = 1 /**< Bitcoin address */ + Address = 1, /**< Bitcoin address */ + Type = 2 /**< Address type */ }; enum RoleIndex { @@ -60,7 +67,7 @@ class AddressTableModel : public QAbstractTableModel /* 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.h b/src/qt/bitcoinaddressvalidator.h index 91d248ab..ee4d49fe 100644 --- a/src/qt/bitcoinaddressvalidator.h +++ b/src/qt/bitcoinaddressvalidator.h @@ -19,7 +19,7 @@ class BitcoinAddressValidator : public QValidator State validate(QString &input, int &pos) const; - static const int MaxAddressLength = 35; + static const int MaxAddressLength = 128; // accept stealth addresses }; #endif // BITCOINADDRESSVALIDATOR_H diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index ee796f01..92171e1f 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -54,10 +54,21 @@ const QString BitcoinGUI::DEFAULT_WALLET = "~Default"; +ActiveLabel::ActiveLabel(const QString & text, QWidget * parent): + QLabel(parent){} + +void ActiveLabel::mouseReleaseEvent(QMouseEvent * event) +{ + emit clicked(); +} + BitcoinGUI::BitcoinGUI(QWidget *parent) : QMainWindow(parent), clientModel(0), + walletModel(0), encryptWalletAction(0), + lockWalletAction(0), + unlockWalletAction(0), changePassphraseAction(0), aboutQtAction(0), trayIcon(0), @@ -100,12 +111,12 @@ BitcoinGUI::BitcoinGUI(QWidget *parent) : // Status bar notification icons QFrame *frameBlocks = new QFrame(); frameBlocks->setContentsMargins(0,0,0,0); - frameBlocks->setMinimumWidth(56); - frameBlocks->setMaximumWidth(56); + frameBlocks->setMinimumWidth(80); + frameBlocks->setMaximumWidth(80); QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks); frameBlocksLayout->setContentsMargins(3,0,3,0); frameBlocksLayout->setSpacing(3); - labelEncryptionIcon = new QLabel(); + labelEncryptionIcon = new ActiveLabel(); labelConnectionsIcon = new QLabel(); labelBlocksIcon = new QLabel(); frameBlocksLayout->addStretch(); @@ -115,6 +126,8 @@ BitcoinGUI::BitcoinGUI(QWidget *parent) : frameBlocksLayout->addStretch(); frameBlocksLayout->addWidget(labelBlocksIcon); frameBlocksLayout->addStretch(); + + connect(labelEncryptionIcon, SIGNAL(clicked()), unlockWalletAction, SLOT(trigger())); // Progress bar and label for blocks download progressBarLabel = new QLabel(); @@ -239,6 +252,13 @@ void BitcoinGUI::createActions() verifyMessageAction = new QAction(QIcon(":/icons/transaction_0"), tr("&Verify message..."), this); verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Deepcoin addresses")); + lockWalletAction = new QAction(QIcon(":/icons/lock_closed"), tr("&Lock wallet"), this); + lockWalletAction->setToolTip(tr("Lock wallet")); + lockWalletAction->setCheckable(true); + + unlockWalletAction = new QAction(QIcon(":/icons/lock_open"), tr("Unlo&ck wallet"), this); + unlockWalletAction->setToolTip(tr("Unlock wallet")); + unlockWalletAction->setCheckable(true); openRPCConsoleAction = new QAction(QIcon(":/icons/debugwindow"), tr("&Debug window"), this); openRPCConsoleAction->setStatusTip(tr("Open debugging and diagnostic console")); @@ -248,6 +268,8 @@ void BitcoinGUI::createActions() connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden())); connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(encryptWallet(bool))); + connect(lockWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(lockWallet())); + connect(unlockWalletAction, SIGNAL(triggered(bool)), walletFrame, SLOT(unlockWallet())); connect(backupWalletAction, SIGNAL(triggered()), walletFrame, SLOT(backupWallet())); connect(changePassphraseAction, SIGNAL(triggered()), walletFrame, SLOT(changePassphrase())); connect(signMessageAction, SIGNAL(triggered()), this, SLOT(gotoSignMessageTab())); @@ -273,8 +295,12 @@ void BitcoinGUI::createMenuBar() file->addAction(quitAction); QMenu *settings = appMenuBar->addMenu(tr("&Settings")); - settings->addAction(encryptWalletAction); - settings->addAction(changePassphraseAction); + QMenu *securityMenu = settings->addMenu(QIcon(":/icons/key"), tr("&Wallet security")); + securityMenu->addAction(encryptWalletAction); + securityMenu->addAction(encryptWalletAction); + securityMenu->addAction(changePassphraseAction); + securityMenu->addAction(unlockWalletAction); + securityMenu->addAction(lockWalletAction); settings->addSeparator(); settings->addAction(optionsAction); @@ -791,23 +817,39 @@ void BitcoinGUI::setEncryptionStatus(int status) labelEncryptionIcon->hide(); encryptWalletAction->setChecked(false); changePassphraseAction->setEnabled(false); + lockWalletAction->setEnabled(false); + unlockWalletAction->setEnabled(false); encryptWalletAction->setEnabled(true); break; case WalletModel::Unlocked: + disconnect(labelEncryptionIcon, SIGNAL(clicked()), unlockWalletAction, SLOT(trigger())); + disconnect(labelEncryptionIcon, SIGNAL(clicked()), lockWalletAction, SLOT(trigger())); + connect (labelEncryptionIcon, SIGNAL(clicked()), lockWalletAction, SLOT(trigger())); labelEncryptionIcon->show(); labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_open").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); labelEncryptionIcon->setToolTip(tr("Wallet is encrypted and currently unlocked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported + lockWalletAction->setEnabled(true); + lockWalletAction->setChecked(false); + unlockWalletAction->setEnabled(false); + unlockWalletAction->setChecked(true); break; case WalletModel::Locked: + disconnect(labelEncryptionIcon, SIGNAL(clicked()), unlockWalletAction, SLOT(trigger())); + disconnect(labelEncryptionIcon, SIGNAL(clicked()), lockWalletAction, SLOT(trigger())); + connect (labelEncryptionIcon, SIGNAL(clicked()), unlockWalletAction, SLOT(trigger())); labelEncryptionIcon->show(); labelEncryptionIcon->setPixmap(QIcon(":/icons/lock_closed").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); labelEncryptionIcon->setToolTip(tr("Wallet is encrypted and currently locked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); // TODO: decrypt currently not supported + lockWalletAction->setChecked(true); + lockWalletAction->setEnabled(false); + unlockWalletAction->setChecked(false); + unlockWalletAction->setEnabled(true); break; } } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index bad95cbc..ee9c4a13 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -7,6 +7,7 @@ #include #include +#include #include class TransactionTableModel; @@ -36,6 +37,21 @@ class QPushButton; class QAction; QT_END_NAMESPACE +class ActiveLabel : public QLabel +{ + Q_OBJECT +public: + ActiveLabel(const QString & text = "", QWidget * parent = 0); + ~ActiveLabel(){} + +signals: + void clicked(); + +protected: + void mouseReleaseEvent (QMouseEvent * event) ; + +}; + /** Bitcoin GUI main class. This class represents the main window of the Bitcoin UI. It communicates with both the client and wallet models to give the user an up-to-date view of the current core state. @@ -81,9 +97,10 @@ class BitcoinGUI : public QMainWindow private: ClientModel *clientModel; + WalletModel *walletModel; WalletFrame *walletFrame; - QLabel *labelEncryptionIcon; + ActiveLabel *labelEncryptionIcon; QLabel *labelConnectionsIcon; QLabel *labelBlocksIcon; QLabel *progressBarLabel; @@ -104,6 +121,8 @@ class BitcoinGUI : public QMainWindow QAction *encryptWalletAction; QAction *backupWalletAction; QAction *changePassphraseAction; + QAction *lockWalletAction; + QAction *unlockWalletAction; QAction *aboutQtAction; QAction *openRPCConsoleAction; diff --git a/src/qt/editaddressdialog.cpp b/src/qt/editaddressdialog.cpp index 4af0b76c..5fd4af8c 100644 --- a/src/qt/editaddressdialog.cpp +++ b/src/qt/editaddressdialog.cpp @@ -24,16 +24,24 @@ EditAddressDialog::EditAddressDialog(Mode mode, QWidget *parent) : case NewReceivingAddress: setWindowTitle(tr("New receiving address")); ui->addressEdit->setEnabled(false); + ui->addressEdit->setVisible(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->stealthCB->setVisible(true); break; case EditSendingAddress: setWindowTitle(tr("Edit sending address")); + ui->stealthCB->setVisible(false); break; } @@ -55,6 +63,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) @@ -71,10 +80,14 @@ bool EditAddressDialog::saveCurrentRow() { case NewReceivingAddress: case NewSendingAddress: + { + int 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/editaddressdialog.ui b/src/qt/forms/editaddressdialog.ui index b4a4c1b1..62529b49 100644 --- a/src/qt/forms/editaddressdialog.ui +++ b/src/qt/forms/editaddressdialog.ui @@ -7,7 +7,7 @@ 0 0 457 - 126 + 129 @@ -53,6 +53,13 @@ + + + + &Stealth Address + + + diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 38af95e3..1055e38f 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -220,6 +220,14 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx) if (wtx.mapValue.count("comment") && !wtx.mapValue["comment"].empty()) strHTML += "
" + tr("Comment") + ":
" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "
"; + char cbuf[256]; + for (int k = 0; k < wtx.vout.size(); ++k) + { + snprintf(cbuf, sizeof(cbuf), "n_%d", k); + if (wtx.mapValue.count(cbuf) && !wtx.mapValue[cbuf].empty()) + strHTML += "
" + tr(cbuf) + ": " + GUIUtil::HtmlEscape(wtx.mapValue[cbuf], true) + "
"; + } + strHTML += "" + tr("Transaction ID") + ": " + wtx.GetHash().ToString().c_str() + "
"; if (wtx.IsCoinBase()) diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 22b161dd..81870272 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -138,6 +138,13 @@ void WalletFrame::changePassphrase() walletView->changePassphrase(); } +void WalletFrame::lockWallet() +{ + WalletView *walletView = currentWalletView(); + if (walletView) + walletStack->lockWallet(); +} + void WalletFrame::unlockWallet() { WalletView *walletView = currentWalletView(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 59399259..e4abf284 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -62,6 +62,8 @@ public slots: void backupWallet(); /** Change encrypted wallet passphrase */ void changePassphrase(); + /** Lock the wallet */ + void lockWallet(); /** Ask for passphrase to unlock wallet temporarily */ void unlockWallet(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 4c5e0613..7143e1a2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -135,7 +135,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(); } @@ -185,21 +193,83 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListcs_wallet); + + CWalletTx wtx; // Sendmany std::vector > vecSend; foreach(const SendCoinsRecipient &rcp, recipients) { + std::string sAddr = rcp.address.toStdString(); + + if (rcp.typeInd == AddressTableModel::AT_Stealth) + { + 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) + { + printf("GenerateRandomSecret failed.\n"); + return Aborted; + }; + + if (StealthSecret(ephem_secret, sxAddr.scan_pubkey, sxAddr.spend_pubkey, secretShared, pkSendTo) != 0) + { + printf("Could not generate receiving public key.\n"); + return Aborted; + }; + + CPubKey cpkTo(pkSendTo); + if (!cpkTo.IsValid()) + { + printf("Invalid public key generated.\n"); + return Aborted; + }; + + CKeyID ckidTo = cpkTo.GetID(); + + CBitcoinAddress addrTo(ckidTo); + + if (SecretToPublicKey(ephem_secret, ephem_pubkey) != 0) + { + printf("Could not generate ephem public key.\n"); + return Aborted; + }; + + if (fDebug) + { + printf("Stealth send to generated pubkey %"PRIszu": %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str()); + printf("hash %s\n", addrTo.ToString().c_str()); + printf("ephem_pubkey %"PRIszu": %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(make_pair(scriptPubKey, rcp.amount)); } - CWalletTx wtx; CReserveKey keyChange(wallet); int64 nFeeRequired = 0; + int nChangePos = -1; std::string strFailReason; - bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason, coinControl); + bool fCreated = wallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePos, strFailReason, coinControl); if(!fCreated) { @@ -231,12 +301,17 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QListcs_wallet); - std::map::iterator mi = wallet->mapAddressBook.find(dest); - - // Check if we have a new address or an updated label - if (mi == wallet->mapAddressBook.end() || mi->second != strLabel) + if (rcp.typeInd == AddressTableModel::AT_Stealth) { - wallet->SetAddressBookName(dest, strLabel); + 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() || mi->second != strLabel) + { + wallet->SetAddressBookName(dest, strLabel); + } } } } @@ -328,12 +403,24 @@ static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStor static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, ChangeType status) { - OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status); - QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(CBitcoinAddress(address).ToString())), - Q_ARG(QString, QString::fromStdString(label)), - Q_ARG(bool, isMine), - Q_ARG(int, status)); + if (address.type() == typeid(CStealthAddress)) + { + CStealthAddress sxAddr = boost::get(address); + std::string enc = sxAddr.Encoded(); + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", enc.c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(enc)), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); + } else { + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", CBitcoinAddress(address).ToString().c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(CBitcoinAddress(address).ToString())), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); + } } static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index f0c0883c..6b2af125 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -31,6 +31,7 @@ class SendCoinsRecipient public: QString address; QString label; + int typeInd; qint64 amount; }; diff --git a/src/qt/walletstack.cpp b/src/qt/walletstack.cpp index 3576d55c..c014051a 100644 --- a/src/qt/walletstack.cpp +++ b/src/qt/walletstack.cpp @@ -140,6 +140,12 @@ void WalletStack::changePassphrase() if (walletView) walletView->changePassphrase(); } +void WalletStack::lockWallet() +{ + WalletView *walletView = (WalletView*)currentWidget(); + if (walletView) walletView->lockWallet(); +} + void WalletStack::unlockWallet() { WalletView *walletView = (WalletView*)currentWidget(); diff --git a/src/qt/walletstack.h b/src/qt/walletstack.h index 506d595c..00ae9f00 100644 --- a/src/qt/walletstack.h +++ b/src/qt/walletstack.h @@ -90,6 +90,8 @@ public slots: void backupWallet(); /** Change encrypted wallet passphrase */ void changePassphrase(); + /** Lock the wallet */ + void lockWallet(); /** Ask for passphrase to unlock wallet temporarily */ void unlockWallet(); diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index ce7105e5..ab2a4e5e 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -259,6 +259,18 @@ void WalletView::changePassphrase() dlg.exec(); } +void WalletView::lockWallet() +{ + if(!walletModel) + return; + // Lock wallet when requested by wallet model + if (walletModel->getEncryptionStatus() == WalletModel::Unlocked) + { + SecureString dummy; + walletModel->setWalletLocked(true, dummy); + } +} + void WalletView::unlockWallet() { if(!walletModel) diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 2cc739ff..cd2d2d58 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -93,6 +93,8 @@ public slots: void backupWallet(); /** Change encrypted wallet passphrase */ void changePassphrase(); + /** Lock the wallet */ + void lockWallet(); /** Ask for passphrase to unlock wallet temporarily */ void unlockWallet(); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index aaa5eff0..e3b6e706 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -10,6 +10,7 @@ #include "bitcoinrpc.h" #include "init.h" #include "base58.h" +#include "stealth.h" using namespace std; using namespace boost; @@ -273,7 +274,7 @@ Value setmininput(const Array& params, bool fHelp) Value sendtoaddress(const Array& params, bool fHelp) { - if (fHelp || params.size() < 2 || params.size() > 4) + if (fHelp || params.size() < 2 || params.size() > 5) throw runtime_error( "sendtoaddress [comment] [comment-to]\n" " is a real and is rounded to the nearest 0.00000001" @@ -629,7 +630,7 @@ Value movecmd(const Array& params, bool fHelp) Value sendfrom(const Array& params, bool fHelp) { - if (fHelp || params.size() < 3 || params.size() > 6) + if (fHelp || params.size() < 3 || params.size() > 7) throw runtime_error( "sendfrom [minconf=1] [comment] [comment-to]\n" " is a real and is rounded to the nearest 0.00000001" @@ -718,8 +719,9 @@ Value sendmany(const Array& params, bool fHelp) // Send CReserveKey keyChange(pwalletMain); int64 nFeeRequired = 0; + int nChangePos; string strFailReason; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, strFailReason); + bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePos, strFailReason); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); if (!pwalletMain->CommitTransaction(wtx, keyChange)) @@ -1513,6 +1515,12 @@ class DescribeAddressVisitor : public boost::static_visitor obj.push_back(Pair("sigsrequired", nRequired)); return obj; } + + Object operator()(const CStealthAddress &stxAddr) const { + Object obj; + obj.push_back(Pair("todo", true)); + return obj; + } }; Value validateaddress(const Array& params, bool fHelp) @@ -1615,3 +1623,459 @@ Value listlockunspent(const Array& params, bool fHelp) return ret; } + +Value getnewstealthaddress(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 1) + throw runtime_error( + "getnewstealthaddress [label]\n" + "Returns a new ShadowCoin 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 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 [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]); + + CStealthAddress sxAddr; + Object result; + + if (!sxAddr.SetEncoded(sEncoded)) + { + result.push_back(Pair("result", "Invalid Deepcoin stealth address.")); + return result; + }; + + + CWalletTx wtx; + if (params.size() > 2 && params[2].type() != null_type && !params[2].get_str().empty()) + wtx.mapValue["comment"] = params[2].get_str(); + if (params.size() > 3 && params[3].type() != null_type && !params[3].get_str().empty()) + wtx.mapValue["to"] = params[3].get_str(); + + std::string sError; + if (!pwalletMain->SendStealthMoneyToDestination(sxAddr, nAmount, wtx, sError)) + throw JSONRPCError(RPC_WALLET_ERROR, sError); + + return wtx.GetHash().GetHex(); + + result.push_back(Pair("result", "Not implemented yet.")); + + return result; +} + +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.GetTxnCursor(); + 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()); + + //printf("strType %s\n", strType.c_str()); + + if (strType == "tx") + { + uint256 hash; + ssValue >> hash; + + if ((ret = pcursor->del(0)) != 0) + { + printf("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 = pindexGenesisBlock; + + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) + { + pindex = mapBlockIndex[hashBestChain]; + 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 = pindexGenesisBlock; + + + if (params.size() > 0) + nFromHeight = params[0].get_int(); + + + if (nFromHeight > 0) + { + pindex = mapBlockIndex[hashBestChain]; + 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; + block.ReadFromDisk(pindex); + + BOOST_FOREACH(CTransaction& tx, block.vtx) + { + if (!tx.IsStandard()) + continue; // leave out coinbase and others + nTransactions++; + + pwalletMain->AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate); + }; + + pindex = pindex->pnext; + }; + + printf("Scanned %u blocks, %u transactions\n", nBlocks, nTransactions); + printf("Found %u stealth transactions in blockchain.\n", pwalletMain->nStealth); + printf("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 f6665b0b..3b062471 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -20,7 +20,6 @@ bool CheckSig(vector vchSig, vector vchPubKey, CSc -typedef vector valtype; static const valtype vchFalse(0); static const valtype vchZero(0); static const valtype vchTrue(1, 1); @@ -1373,6 +1372,10 @@ class CKeyStoreIsMineVisitor : public boost::static_visitor 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) @@ -1790,6 +1793,13 @@ class CScriptVisitor : public boost::static_visitor *script << OP_HASH160 << scriptID << OP_EQUAL; return true; } + + bool operator()(const CStealthAddress &stxAddr) const { + script->clear(); + //*script << OP_HASH160 << scriptID << OP_EQUAL; + printf("TODO\n"); + return false; + } }; void CScript::SetDestination(const CTxDestination& dest) diff --git a/src/script.h b/src/script.h index 6fc4b85a..c09a36e5 100644 --- a/src/script.h +++ b/src/script.h @@ -13,6 +13,9 @@ #include "keystore.h" #include "bignum.h" +#include "stealth.h" + +typedef std::vector valtype; class CCoins; class CTransaction; @@ -59,7 +62,7 @@ class CNoDestination { * * 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/stealth.cpp b/src/stealth.cpp new file mode 100644 index 00000000..613cba3e --- /dev/null +++ b/src/stealth.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 "stealth.h" +#include "base58.h" + + +#include +#include +#include +#include + +//const uint8_t stealth_version_byte = 0x2a; +const uint8_t stealth_version_byte = 0x1d; + + +bool CStealthAddress::SetEncoded(const std::string& encodedAddress) +{ + data_chunk raw; + + if (!DecodeBase58(encodedAddress, raw)) + { + if (fDebug) + printf("CStealthAddress::SetEncoded DecodeBase58 falied.\n"); + return false; + }; + + if (!VerifyChecksum(raw)) + { + if (fDebug) + printf("CStealthAddress::SetEncoded verify_checksum falied.\n"); + return false; + }; + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) + { + if (fDebug) + printf("CStealthAddress::SetEncoded() too few bytes provided.\n"); + return false; + }; + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) + { + printf("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) + { + printf("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) + { + printf("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); + printf("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) + { + printf("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) + { + printf("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) + { + printf("StealthSecret(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + printf("StealthSecret(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnEphem = BN_bin2bn(&secret.e[0], ec_secret_size, BN_new()))) + { + printf("StealthSecret(): bnEphem BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnQ = BN_bin2bn(&pubkey[0], pubkey.size(), BN_new()))) + { + printf("StealthSecret(): bnQ BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(Q = EC_POINT_bn2point(ecgrp, bnQ, NULL, bnCtx))) + { + printf("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)) + { + printf("StealthSecret(): eQ EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutQ = EC_POINT_point2bn(ecgrp, Q, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + printf("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) + { + printf("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()))) + { + printf("StealthSecret(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + // -- cG + if (!(C = EC_POINT_new(ecgrp))) + { + printf("StealthSecret(): C EC_POINT_new failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) + { + printf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnR = BN_bin2bn(&pkSpend[0], pkSpend.size(), BN_new()))) + { + printf("StealthSecret(): bnR BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + + if (!(R = EC_POINT_bn2point(ecgrp, bnR, NULL, bnCtx))) + { + printf("StealthSecret(): R EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_mul(ecgrp, C, bnc, NULL, NULL, bnCtx)) + { + printf("StealthSecret(): C EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(Rout = EC_POINT_new(ecgrp))) + { + printf("StealthSecret(): Rout EC_POINT_new failed\n"); + rv = 1; + goto End; + }; + + if (!EC_POINT_add(ecgrp, Rout, R, C, bnCtx)) + { + printf("StealthSecret(): Rout EC_POINT_add failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutR = EC_POINT_point2bn(ecgrp, Rout, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + printf("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) + { + printf("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) + { + printf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + printf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnScanSecret = BN_bin2bn(&scanSecret.e[0], ec_secret_size, BN_new()))) + { + printf("StealthSecretSpend(): bnScanSecret BN_bin2bn failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnP = BN_bin2bn(&ephemPubkey[0], ephemPubkey.size(), BN_new()))) + { + printf("StealthSecretSpend(): bnP BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(P = EC_POINT_bn2point(ecgrp, bnP, NULL, bnCtx))) + { + printf("StealthSecretSpend(): P EC_POINT_bn2point failed\n"); + rv = 1; + goto End; + }; + + // -- dP + if (!EC_POINT_mul(ecgrp, P, NULL, P, bnScanSecret, bnCtx)) + { + printf("StealthSecretSpend(): dP EC_POINT_mul failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOutP = EC_POINT_point2bn(ecgrp, P, POINT_CONVERSION_COMPRESSED, BN_new(), bnCtx))) + { + printf("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) + { + printf("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()))) + { + printf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOrder = BN_new()) + || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) + { + printf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + }; + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) + { + printf("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)) + { + printf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + }; + + if (BN_is_zero(bnSpend)) // possible? + { + printf("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) + { + printf("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) + { + printf("StealthSecretSpend(): EC_GROUP_new_by_curve_name failed.\n"); + return 1; + }; + + if (!(bnCtx = BN_CTX_new())) + { + printf("StealthSecretSpend(): BN_CTX_new failed.\n"); + rv = 1; + goto End; + }; + + if (!(bnc = BN_bin2bn(&sharedS.e[0], ec_secret_size, BN_new()))) + { + printf("StealthSecretSpend(): BN_bin2bn failed\n"); + rv = 1; + goto End; + }; + + if (!(bnOrder = BN_new()) + || !EC_GROUP_get_order(ecgrp, bnOrder, bnCtx)) + { + printf("StealthSecretSpend(): EC_GROUP_get_order failed\n"); + rv = 1; + goto End; + }; + + if (!(bnSpend = BN_bin2bn(&spendSecret.e[0], ec_secret_size, BN_new()))) + { + printf("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)) + { + printf("StealthSecretSpend(): bnSpend BN_mod_add failed.\n"); + rv = 1; + goto End; + }; + + if (BN_is_zero(bnSpend)) // possible? + { + printf("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) + { + printf("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)) + { + //printf("IsStealthAddress DecodeBase58 falied.\n"); + return false; + }; + + if (!VerifyChecksum(raw)) + { + //printf("IsStealthAddress verify_checksum falied.\n"); + return false; + }; + + if (raw.size() < 1 + 1 + 33 + 1 + 33 + 1 + 1 + 4) + { + //printf("IsStealthAddress too few bytes provided.\n"); + return false; + }; + + + uint8_t* p = &raw[0]; + uint8_t version = *p++; + + if (version != stealth_version_byte) + { + //printf("IsStealthAddress version mismatch 0x%x != 0x%x.\n", version, stealth_version_byte); + return false; + }; + + return true; +}; diff --git a/src/stealth.h b/src/stealth.h new file mode 100644 index 00000000..1e2a0bc8 --- /dev/null +++ b/src/stealth.h @@ -0,0 +1,122 @@ +// 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_STEALTH_H +#define BITCOIN_STEALTH_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 (scan_pubkey == y.scan_pubkey) && + (spend_pubkey == y.spend_pubkey) && + (scan_secret == y.scan_secret) && + (spend_secret == y.spend_secret); + } + + 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_STEALTH_H + diff --git a/src/wallet.cpp b/src/wallet.cpp index f9cef4c9..3354f914 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -88,6 +88,42 @@ bool CWallet::AddCScript(const CScript& redeemScript) return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript); } +bool CWallet::Lock() +{ + if (IsLocked()) + return true; + + if (fDebug) + printf("Locking wallet.\n"); + + { + LOCK(cs_wallet); + CWalletDB wdb(strWalletFile); + + // -- load encrypted spend_secret of stealth addresses + CStealthAddress sxAddrTemp; + 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) + printf("Recrypting stealth key %s\n", sxAddr.Encoded().c_str()); + + sxAddrTemp.scan_pubkey = sxAddr.scan_pubkey; + if (!wdb.ReadStealthAddress(sxAddrTemp)) + { + printf("Error: Failed to read stealth key from db %s\n", sxAddr.Encoded().c_str()); + continue; + } + sxAddr.spend_secret = sxAddrTemp.spend_secret; + }; + } + return LockKeyStore(); +}; + bool CWallet::Unlock(const SecureString& strWalletPassphrase) { if (!IsLocked()) @@ -107,6 +143,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) if (CCryptoKeyStore::Unlock(vMasterKey)) return true; } + UnlockStealthAddresses(vMasterKey); } return false; } @@ -127,7 +164,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, return false; if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey)) return false; - if (CCryptoKeyStore::Unlock(vMasterKey)) + if (CCryptoKeyStore::Unlock(vMasterKey) && UnlockStealthAddresses(vMasterKey)) { int64 nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); @@ -272,6 +309,34 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) pwalletdbEncryption->TxnAbort(); exit(1); //We now probably have half of our keys encrypted in memory, and half not...die and let the user reload their unencrypted wallet. } + + 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 is only sorted on spend_pubkey + CStealthAddress &sxAddr = const_cast(*it); + + if (fDebug) + printf("Encrypting stealth key %s\n", sxAddr.Encoded().c_str()); + + 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)) + { + printf("Error: Failed encrypting stealth key %s\n", sxAddr.Encoded().c_str()); + continue; + }; + + sxAddr.spend_secret = vchCryptedSecret; + pwalletdbEncryption->WriteStealthAddress(sxAddr); + }; // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true); @@ -510,9 +575,13 @@ bool CWallet::AddToWalletIfInvolvingMe(const uint256 &hash, const CTransaction& LOCK(cs_wallet); bool fExisted = mapWallet.count(hash); if (fExisted && !fUpdate) return false; + + FindStealthTransactions(tx); + if (fExisted || IsMine(tx) || IsFromMe(tx)) { CWalletTx wtx(this,tx); + // Get merkle branch if transaction was found in a block if (pblock) wtx.SetMerkleBranch(pblock); @@ -1180,8 +1249,7 @@ bool CWallet::SelectCoins(int64 nTargetValue, set >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) +bool CWallet::CreateTransaction(const vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, int32_t& nChangePos, std::string& strFailReason, const CCoinControl* coinControl) { int64 nValue = 0; BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) @@ -1293,8 +1361,19 @@ bool CWallet::CreateTransaction(const vector >& vecSend, else { // Insert change txn at random position: - vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()+1); + vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size() + 1); + + if (position > wtxNew.vout.begin() && position < wtxNew.vout.end()) + { + while (position > wtxNew.vout.begin()) + { + if (position->nValue != 0) + break; + position--; + }; + }; wtxNew.vout.insert(position, newTxOut); + nChangePos = std::distance(wtxNew.vout.begin(), position); } } else @@ -1343,17 +1422,638 @@ bool CWallet::CreateTransaction(const vector >& vecSend, return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) +bool CWallet::CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) +{ + vector< pair > vecSend; + vecSend.push_back(make_pair(scriptPubKey, nValue)); + int nChangePos; + string strError; + bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, strError, coinControl); + return rv; +} + + + +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."; + printf("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."; + printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); + return false; + }; + + if (SecretToPublicKey(spend_secret, spend_pubkey) != 0) + { + sError = "Could not get spend public key."; + printf("Error CWallet::NewStealthAddress - %s\n", sError.c_str()); + return false; + }; + + if (fDebug) + { + printf("getnewstealthaddress: "); + printf("scan_pubkey "); + for (uint32_t i = 0; i < scan_pubkey.size(); ++i) + printf("%02x", scan_pubkey[i]); + printf("\n"); + + printf("spend_pubkey "); + for (uint32_t i = 0; i < spend_pubkey.size(); ++i) + printf("%02x", spend_pubkey[i]); + printf("\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()) + { + printf("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)) + { + printf("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) + printf("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) + { + printf("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) + { + printf("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()) + { + printf("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()) + { + printf("No stealth key found to add secret for %s\n", addr.ToString().c_str()); + continue; + }; + + if (fDebug) + printf("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) + { + printf("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) + { + printf("StealthSecretSpend() failed.\n"); + continue; + }; + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) + { + printf("SecretToPublicKey() failed.\n"); + continue; + }; + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.SetSecret(vchSecret, true); + } catch (std::exception& e) { + printf("ckey.SetSecret() threw: %s.\n", e.what()); + continue; + }; + + CPubKey cpkT = ckey.GetPubKey(); + + if (!cpkT.IsValid()) + { + printf("cpkT is invalid.\n"); + continue; + }; + + if (cpkT != pubKey) + { + printf("Error: Generated secret does not match.\n"); + if (fDebug) + { + printf("cpkT %s\n", HexStr(cpkT.Raw()).c_str()); + printf("pubKey %s\n", HexStr(pubKey.Raw()).c_str()); + }; + continue; + }; + + if (!ckey.IsValid()) + { + printf("Reconstructed key is invalid.\n"); + continue; + }; + + if (fDebug) + { + CKeyID keyID = cpkT.GetID(); + CBitcoinAddress coinAddress(keyID); + printf("Adding secret to key %s.\n", coinAddress.ToString().c_str()); + }; + + if (!AddKey(ckey)) + { + printf("AddKey failed.\n"); + continue; + }; + + if (!CWalletDB(strWalletFile).EraseStealthKeyMeta(ckid)) + printf("EraseStealthKeyMeta failed for %s\n", addr.ToString().c_str()); + }; + return true; +} + +bool CWallet::UpdateStealthAddress(std::string &addr, std::string &label, bool addIfNotExist) +{ + if (fDebug) + printf("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 + { + printf("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) + { + printf("UpdateStealthAddress: todo - update owned stealth address.\n"); + return false; + }; + }; + + sxFound.label = label; + + if (!CWalletDB(strWalletFile).WriteStealthAddress(sxFound)) + { + printf("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, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl) { vector< pair > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); + + CScript scriptP = CScript() << OP_RETURN << P; + 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()); + + int nChangePos; + string strError; + bool rv = CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, nChangePos, strError, coinControl); + + return rv; +} + +string CWallet::SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, CWalletTx& wtxNew, bool fAskFee) +{ + CReserveKey reservekey(this); + int64_t nFeeRequired; + + if (IsLocked()) + { + string strError = _("Error: Wallet locked, unable to create transaction "); + printf("SendStealthMoney() : %s", strError.c_str()); + return strError; + } + if (!CreateStealthTransaction(scriptPubKey, nValue, P, 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 "); + printf("SendStealthMoney() : %s", strError.c_str()); + return strError; + } + + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired)) + 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, 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) + { + printf("Stealth send to generated pubkey %"PRIszu": %s\n", pkSendTo.size(), HexStr(pkSendTo).c_str()); + printf("hash %s\n", addrTo.ToString().c_str()); + printf("ephem_pubkey %"PRIszu": %s\n", ephem_pubkey.size(), HexStr(ephem_pubkey).c_str()); + }; + + + // -- Parse Bitcoin address + CScript scriptPubKey; + scriptPubKey.SetDestination(addrTo.Get()); + + if ((sError = SendStealthMoney(scriptPubKey, nValue, ephem_pubkey, wtxNew, fAskFee)) != "") + return false; + + + return true; +} + +bool CWallet::FindStealthTransactions(const CTransaction& tx) +{ + if (fDebug) + printf("FindStealthTransactions() tx: %s\n", tx.GetHash().GetHex().c_str()); + + LOCK(cs_wallet); + ec_secret sSpendR; + ec_secret sSpend; + ec_secret sScan; + ec_secret sShared; + + ec_point pkExtracted; + + std::vector vchEphemPK; + opcodetype opCode; + + int32_t nOutputIdOuter = -1; + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + nOutputIdOuter++; + // -- for each OP_RETURN need to check all other valid outputs + + //printf("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; + + 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 + //printf("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 + + //printf("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) + { + printf("StealthSecret failed.\n"); + continue; + }; + //printf("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) + printf("Found stealth txn to address %s\n", it->Encoded().c_str()); + + if (IsLocked()) + { + if (fDebug) + printf("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(); + SetAddressBookName(keyId, sLabel); + + CPubKey cpkEphem(vchEphemPK); + CPubKey cpkScan(it->scan_pubkey); + CStealthKeyMetadata lockedSkMeta(cpkEphem, cpkScan); + + if (!CWalletDB(strWalletFile).WriteStealthKeyMeta(keyId, lockedSkMeta)) + printf("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) + { + printf("StealthSharedToSecretSpend() failed.\n"); + continue; + }; + + ec_point pkTestSpendR; + if (SecretToPublicKey(sSpendR, pkTestSpendR) != 0) + { + printf("SecretToPublicKey() failed.\n"); + continue; + }; + + CSecret vchSecret; + vchSecret.resize(ec_secret_size); + + memcpy(&vchSecret[0], &sSpendR.e[0], ec_secret_size); + CKey ckey; + + try { + ckey.SetSecret(vchSecret, true); + } catch (std::exception& e) { + printf("ckey.SetSecret() threw: %s.\n", e.what()); + continue; + }; + + CPubKey cpkT = ckey.GetPubKey(); + if (!cpkT.IsValid()) + { + printf("cpkT is invalid.\n"); + continue; + }; + + if (!ckey.IsValid()) + { + printf("Reconstructed key is invalid.\n"); + continue; + }; + + CKeyID keyID = cpkT.GetID(); + if (fDebug) + { + CBitcoinAddress coinAddress(keyID); + printf("Adding key %s.\n", coinAddress.ToString().c_str()); + }; + + if (!AddKey(ckey)) + { + printf("AddKey failed.\n"); + continue; + }; + + std::string sLabel = it->Encoded(); + SetAddressBookName(keyID, sLabel); + nFoundStealth++; + }; + + txnMatch = true; + break; + }; + if (txnMatch) + break; + }; + }; + + return true; +}; + // Call after CreateTransaction unless you want to abort bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) { + FindStealthTransactions(wtxNew); + { LOCK2(cs_main, cs_wallet); printf("CommitTransaction:\n%s", wtxNew.ToString().c_str()); @@ -1403,7 +2103,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) -string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee) +string CWallet::SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, bool fAskFee) { CReserveKey reservekey(this); int64 nFeeRequired; diff --git a/src/wallet.h b/src/wallet.h index f4dfcc45..a164abb5 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -17,6 +17,7 @@ #include "ui_interface.h" #include "util.h" #include "walletdb.h" +#include "stealth.h" extern bool bSpendZeroConfChange; @@ -26,6 +27,9 @@ class CReserveKey; class COutput; class CCoinControl; +typedef std::map StealthKeyMetaMap; +typedef std::map mapValue_t; + /** (client) version numbers for particular wallet features */ enum WalletFeature { @@ -89,6 +93,9 @@ class CWallet : public CCryptoKeyStore std::set setKeyPool; + std::set stealthAddresses; + StealthKeyMetaMap mapStealthKeyMeta; + uint32_t nStealth, nFoundStealth; // for reporting, zero before use typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -152,6 +159,7 @@ class CWallet : public CCryptoKeyStore bool AddCScript(const CScript& redeemScript); bool LoadCScript(const CScript& redeemScript) { return CCryptoKeyStore::AddCScript(redeemScript); } + bool Lock(); bool Unlock(const SecureString& strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase); bool EncryptWallet(const SecureString& strWalletPassphrase); @@ -181,14 +189,22 @@ class CWallet : public CCryptoKeyStore int64 GetBalance() const; int64 GetUnconfirmedBalance() const; int64 GetImmatureBalance() const; - bool CreateTransaction(const std::vector >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl=NULL); - bool CreateTransaction(CScript scriptPubKey, int64 nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl=NULL); + bool CreateTransaction(const std::vector >& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, int32_t& nChangePos, std::string& strFailReason, const CCoinControl *coinControl=NULL); + bool CreateTransaction(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, CReserveKey& reservekey, int64& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl=NULL); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); - std::string SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); - std::string SendMoneyToDestination(const CTxDestination &address, int64 nValue, CWalletTx& wtxNew, bool fAskFee=false); - + std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); + std::string SendMoneyToDestination(const CTxDestination& address, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); + + 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, CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, const CCoinControl* coinControl=NULL); + std::string SendStealthMoney(CScript scriptPubKey, int64_t nValue, std::vector& P, CWalletTx& wtxNew, bool fAskFee=false); + bool SendStealthMoneyToDestination(CStealthAddress& sxAddress, int64_t nValue, CWalletTx& wtxNew, std::string& sError, bool fAskFee=false); + bool FindStealthTransactions(const CTransaction& tx); + bool NewKeyPool(); bool TopUpKeyPool(); int64 AddReserveKey(const CKeyPool& keypool); @@ -341,9 +357,6 @@ class CReserveKey }; -typedef std::map mapValue_t; - - static void ReadOrderPos(int64& nOrderPos, mapValue_t& mapValue) { if (!mapValue.count("n")) diff --git a/src/walletdb.cpp b/src/walletdb.cpp index ae4ce919..76f618b0 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -241,8 +241,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, // DateTimeStrFormat("%Y-%m-%d %H:%M:%S", wtx.GetBlockTime()).c_str(), // wtx.hashBlock.ToString().c_str(), // wtx.mapValue["message"].c_str()); - } - else if (strType == "acentry") + } else if (strType == "sxAddr") + { + if (fDebug) + printf("WalletDB ReadKeyValue sxAddr\n"); + + CStealthAddress sxAddr; + ssValue >> sxAddr; + + pwallet->stealthAddresses.insert(sxAddr); + } else if (strType == "acentry") { string strAccount; ssKey >> strAccount; @@ -339,6 +347,17 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } fIsEncrypted = true; + } else if (strType == "sxKeyMeta") + { + if (fDebug) + printf("WalletDB ReadKeyValue sxKeyMeta\n"); + + CKeyID keyId; + ssKey >> keyId; + CStealthKeyMetadata sxKeyMeta; + ssValue >> sxKeyMeta; + + pwallet->mapStealthKeyMeta[keyId] = sxKeyMeta; } else if (strType == "defaultkey") { diff --git a/src/walletdb.h b/src/walletdb.h index a3e779ab..73ffae47 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -7,6 +7,7 @@ #include "db.h" #include "base58.h" +#include "stealth.h" class CKeyPool; class CAccount; @@ -23,6 +24,29 @@ enum DBErrors DB_NEED_REWRITE }; +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 { @@ -34,6 +58,30 @@ class CWalletDB : public CDB CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); public: + Dbc* GetAtCursor() + { + return GetCursor(); + } + + Dbc* GetTxnCursor() + { + if (!pdb) + return NULL; + + DbTxn* ptxnid = activeTxn; // call TxnBegin first + + Dbc* pcursor = NULL; + int ret = pdb->cursor(ptxnid, &pcursor, 0); + if (ret != 0) + return NULL; + return pcursor; + } + + DbTxn* GetAtActiveTxn() + { + return activeTxn; + } + bool WriteName(const std::string& strAddress, const std::string& strName); bool EraseName(const std::string& strAddress); @@ -49,6 +97,31 @@ class CWalletDB : public CDB nWalletDBUpdated++; return Erase(std::make_pair(std::string("tx"), hash)); } + + bool WriteStealthKeyMeta(const CKeyID& keyId, const CStealthKeyMetadata& sxKeyMeta) + { + nWalletDBUpdated++; + return Write(std::make_pair(std::string("sxKeyMeta"), keyId), sxKeyMeta, true); + } + + bool EraseStealthKeyMeta(const CKeyID& keyId) + { + nWalletDBUpdated++; + return Erase(std::make_pair(std::string("sxKeyMeta"), keyId)); + } + + bool WriteStealthAddress(const CStealthAddress& sxAddr) + { + nWalletDBUpdated++; + + return Write(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr, true); + } + + bool ReadStealthAddress(CStealthAddress& sxAddr) + { + // -- set scan_pubkey before reading + return Read(std::make_pair(std::string("sxAddr"), sxAddr.scan_pubkey), sxAddr); + } bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey) {