From f4d507238537d3378200325f0abb3b998497d077 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 00:28:02 -0500 Subject: [PATCH 01/18] [wallet] [zk] Add note locking methods, with EXCLUSIVE_LOCKS_REQUIRED --- src/wallet/wallet.cpp | 34 ++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 3987e8e70..b0864e5c6 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3801,6 +3801,40 @@ void CWallet::ListLockedCoins(std::vector& vOutpts) const } } +// Z + +void CWallet::LockNote(const JSOutPoint& output) +{ + AssertLockHeld(cs_wallet); // setLockedNotes + setLockedNotes.insert(output); +} + +void CWallet::UnlockNote(const JSOutPoint& output) +{ + AssertLockHeld(cs_wallet); // setLockedNotes + setLockedNotes.erase(output); +} + +void CWallet::UnlockAllNotes() +{ + AssertLockHeld(cs_wallet); // setLockedNotes + setLockedNotes.clear(); +} + +bool CWallet::IsLockedNote(const JSOutPoint& outpt) const +{ + AssertLockHeld(cs_wallet); // setLockedNotes + + return (setLockedNotes.count(outpt) > 0); +} + +std::vector CWallet::ListLockedNotes() +{ + AssertLockHeld(cs_wallet); // setLockedNotes + std::vector vOutpts(setLockedNotes.begin(), setLockedNotes.end()); + return vOutpts; +} + /** @} */ // end of Actions void CWallet::GetKeyBirthTimes(std::map &mapKeyBirth) const { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index edc9ba5e3..f33631941 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -823,6 +823,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface std::map mapAddressBook; std::set setLockedCoins; + std::set setLockedNotes; const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -861,6 +862,13 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ListLockedCoins(std::vector& vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + // Z + bool IsLockedNote(const JSOutPoint& outpt) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void LockNote(const JSOutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void UnlockNote(const JSOutPoint& output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + void UnlockAllNotes() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + std::vector ListLockedNotes() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + /* * Rescan abort properties */ From eb0e6667b5ab9717ed494a5fa62212224cdbc9ed Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 00:49:29 -0500 Subject: [PATCH 02/18] [wallet] [zk] Add FindMyNotes and SetNoteData --- src/wallet/wallet.cpp | 70 ++++++++++++++++++++++++++++++++++++++++++- src/wallet/wallet.h | 3 ++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b0864e5c6..c886f6aa8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1049,7 +1049,8 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI bool fExisted = mapWallet.count(tx.GetHash()) != 0; if (fExisted && !fUpdate) return false; - if (fExisted || IsMine(tx) || IsFromMe(tx)) + auto noteData = FindMyNotes(tx); + if (fExisted || IsMine(tx) || IsFromMe(tx) || noteData.size() > 0) { /* Check if any keys in the wallet keypool that were supposed to be unused * have appeared in a new transaction. If so, remove those keys from the keypool. @@ -1077,6 +1078,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockI CWalletTx wtx(this, ptx); + if (noteData.size() > 0) { + wtx.SetNoteData(noteData); + } + // Get merkle branch if transaction was found in a block if (pIndex != nullptr) wtx.SetMerkleBranch(pIndex, posInBlock); @@ -1388,6 +1393,53 @@ bool CWallet::IsMine(const CTransaction& tx) const return false; } +/** + * Finds all output notes in the given transaction that have been sent to + * PaymentAddresses in this wallet. + * + * It should never be necessary to call this method with a CWalletTx, because + * the result of FindMyNotes (for the addresses available at the time) will + * already have been cached in CWalletTx.mapNoteData. + */ +mapNoteData_t CWallet::FindMyNotes(const CTransaction& tx) const +{ + LOCK(cs_SpendingKeyStore); + uint256 hash = tx.GetHash(); + + mapNoteData_t noteData; + for (size_t i = 0; i < tx.vjoinsplit.size(); i++) { + auto hSig = tx.vjoinsplit[i].h_sig(*pzcashParams, tx.joinSplitPubKey); + for (uint8_t j = 0; j < tx.vjoinsplit[i].ciphertexts.size(); j++) { + for (const NoteDecryptorMap::value_type& item : mapNoteDecryptors) { + try { + auto address = item.first; + JSOutPoint jsoutpt {hash, i, j}; + auto nullifier = GetNoteNullifier( + tx.vjoinsplit[i], + address, + item.second, + hSig, j); + if (nullifier) { + CNoteData nd {address, *nullifier}; + noteData.insert(std::make_pair(jsoutpt, nd)); + } else { + CNoteData nd {address}; + noteData.insert(std::make_pair(jsoutpt, nd)); + } + break; + } catch (const note_decryption_failed &err) { + // Couldn't decrypt with this decryptor + } catch (const std::exception &exc) { + // Unexpected failure + LogPrintf("FindMyNotes(): Unexpected error while testing decrypt:\n"); + LogPrintf("%s\n", exc.what()); + } + } + } + } + return noteData; +} + bool CWallet::IsFromMe(const CTransaction& tx) const { return (GetDebit(tx, ISMINE_ALL) > 0); @@ -2037,6 +2089,22 @@ CAmount CWalletTx::GetChange() const return nChangeCached; } +void CWalletTx::SetNoteData(mapNoteData_t ¬eData) +{ + mapNoteData.clear(); + for (const std::pair nd : noteData) { + if (nd.first.js < vjoinsplit.size() && + nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) { + // Store the address and nullifier for the Note + mapNoteData[nd.first] = nd.second; + } else { + // If FindMyNotes() was used to obtain noteData, + // this should never happen + throw std::logic_error("CWalletTx::SetNoteData(): Invalid note"); + } + } +} + bool CWalletTx::InMempool() const { return fInMempool; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f33631941..0745fe1cf 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -456,6 +456,8 @@ class CWalletTx : public CMerkleTx MarkDirty(); } + void SetNoteData(mapNoteData_t ¬eData); + //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const; @@ -1051,6 +1053,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool IsChange(const CTxOut& txout) const; CAmount GetChange(const CTxOut& txout) const; bool IsMine(const CTransaction& tx) const; + mapNoteData_t FindMyNotes(const CTransaction& tx) const; /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; From 600ccd92185efd1e8bc2b4252531cb3f36fe9359 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 00:59:57 -0500 Subject: [PATCH 03/18] [wallet] [zk] Add mapNoteData and related --- src/wallet/wallet.cpp | 2 ++ src/wallet/wallet.h | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index c886f6aa8..0e949c730 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -562,6 +562,8 @@ void CWallet::SyncMetaData(std::pair ran assert(copyFrom && "Oldest wallet transaction in range assumed to have been found."); if (!copyFrom->IsEquivalentTo(*copyTo)) continue; copyTo->mapValue = copyFrom->mapValue; + // mapNoteData not copied on purpose + // (zcash: it is always set correctly for each CWalletTx) copyTo->vOrderForm = copyFrom->vOrderForm; // fTimeReceivedIsTxTime not copied on purpose // nTimeReceived not copied on purpose diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0745fe1cf..86cee51cd 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -319,6 +319,7 @@ class CWalletTx : public CMerkleTx * 2014 (removed in commit 93a18a3) */ mapValue_t mapValue; + mapNoteData_t mapNoteData; std::vector > vOrderForm; unsigned int fTimeReceivedIsTxTime; unsigned int nTimeReceived; //!< time received by this node @@ -371,6 +372,7 @@ class CWalletTx : public CMerkleTx { pwallet = pwalletIn; mapValue.clear(); + mapNoteData.clear(); vOrderForm.clear(); fTimeReceivedIsTxTime = false; nTimeReceived = 0; @@ -404,6 +406,7 @@ class CWalletTx : public CMerkleTx { char fSpent = false; mapValue_t mapValueCopy = mapValue; + mapNoteData_t mapNoteDataCopy = mapNoteData; mapValueCopy["fromaccount"] = strFromAccount; WriteOrderPos(nOrderPos, mapValueCopy); @@ -413,7 +416,7 @@ class CWalletTx : public CMerkleTx s << static_cast(*this); std::vector vUnused; //!< Used to be vtxPrev - s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; + s << vUnused << mapValueCopy << mapNoteDataCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; } template @@ -424,7 +427,7 @@ class CWalletTx : public CMerkleTx s >> static_cast(*this); std::vector vUnused; //!< Used to be vtxPrev - s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; + s >> vUnused >> mapValue >> mapNoteData >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; strFromAccount = std::move(mapValue["fromaccount"]); ReadOrderPos(nOrderPos, mapValue); @@ -578,6 +581,7 @@ class CAccountingEntry std::string strOtherAccount; std::string strComment; mapValue_t mapValue; + mapNoteData_t mapNoteData; int64_t nOrderPos; //!< position in ordered transaction list uint64_t nEntryNo; From f38e182df68723ff2074e2f76fbde18778b39358 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 01:04:45 -0500 Subject: [PATCH 04/18] [wallet] [zk] Add GetNoteNullifier --- src/wallet/wallet.cpp | 28 ++++++++++++++++++++++++++++ src/wallet/wallet.h | 6 ++++++ 2 files changed, 34 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0e949c730..1d88912f8 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1395,6 +1395,34 @@ bool CWallet::IsMine(const CTransaction& tx) const return false; } +/** + * Returns a nullifier if the SpendingKey is available + * Throws std::runtime_error if the decryptor doesn't match this note + */ +boost::optional CWallet::GetNoteNullifier(const JSDescription& jsdesc, + const libzcash::PaymentAddress& address, + const ZCNoteDecryption& dec, + const uint256& hSig, + uint8_t n) const +{ + boost::optional ret; + auto note_pt = libzcash::NotePlaintext::decrypt( + dec, + jsdesc.ciphertexts[n], + jsdesc.ephemeralKey, + hSig, + (unsigned char) n); + auto note = note_pt.note(address); + // SpendingKeys are only available if: + // - We have them (this isn't a viewing key) + // - The wallet is unlocked + libzcash::SpendingKey key; + if (GetSpendingKey(address, key)) { + ret = note.nullifier(key); + } + return ret; +} + /** * Finds all output notes in the given transaction that have been sent to * PaymentAddresses in this wallet. diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 86cee51cd..a17f51c0c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1057,6 +1057,12 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool IsChange(const CTxOut& txout) const; CAmount GetChange(const CTxOut& txout) const; bool IsMine(const CTransaction& tx) const; + boost::optional GetNoteNullifier( + const JSDescription& jsdesc, + const libzcash::PaymentAddress& address, + const ZCNoteDecryption& dec, + const uint256& hSig, + uint8_t n) const; mapNoteData_t FindMyNotes(const CTransaction& tx) const; /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; From ab6dd54620eed3b5484602f16928140a61cd0825 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 01:17:34 -0500 Subject: [PATCH 05/18] [wallet] [zk] Add UpdateNullifierNoteMap(WithTx), TODO properly introduce it to AddToWallet akin to zcash --- src/wallet/wallet.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 2 ++ 2 files changed, 52 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1d88912f8..51ad654f9 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -912,6 +912,56 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash) return success; } +/** + * Ensure that every note in the wallet (for which we possess a spending key) + * has a cached nullifier. + */ +bool CWallet::UpdateNullifierNoteMap() +{ + { + LOCK(cs_wallet); + + if (IsLocked()) + return false; + + ZCNoteDecryption dec; + for (std::pair& wtxItem : mapWallet) { + for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) { + if (!item.second.nullifier) { + if (GetNoteDecryptor(item.second.address, dec)) { + auto i = item.first.js; + auto hSig = wtxItem.second.vjoinsplit[i].h_sig( + *pzcashParams, wtxItem.second.joinSplitPubKey); + item.second.nullifier = GetNoteNullifier( + wtxItem.second.vjoinsplit[i], + item.second.address, + dec, + hSig, + item.first.n); + } + } + } + UpdateNullifierNoteMapWithTx(wtxItem.second); + } + } + return true; +} + +/** + * Update mapNullifiersToNotes with the cached nullifiers in this tx. + */ +void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx) +{ + { + LOCK(cs_wallet); + for (const mapNoteData_t::value_type& item : wtx.mapNoteData) { + if (item.second.nullifier) { + mapNullifiersToNotes[*item.second.nullifier] = item.first; + } + } + } +} + bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) { LOCK(cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a17f51c0c..1d574a9cc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -943,6 +943,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface bool GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew = false); void MarkDirty(); + bool UpdateNullifierNoteMap(); + void UpdateNullifierNoteMapWithTx(const CWalletTx& wtx); bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true); bool LoadToWallet(const CWalletTx& wtxIn); void TransactionAddedToMempool(const CTransactionRef& tx) override; From a16d32b5e6a14b56c778a67fb23c2a29a32c4c22 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 01:27:28 -0500 Subject: [PATCH 06/18] [wallet] [zk] Add GenerateNewZKey + AddZKey --- src/wallet/wallet.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.h | 5 +++++ 2 files changed, 50 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 51ad654f9..5a6c6ece7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -153,6 +153,51 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const return &(it->second); } +// Generate a new spending key and return its public payment address +libzcash::PaymentAddress CWallet::GenerateNewZKey() +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + + auto k = SpendingKey::random(); + auto addr = k.address(); + + // Check for collision, even though it is unlikely to ever occur + if (CCryptoKeyStore::HaveSpendingKey(addr)) + throw std::runtime_error("CWallet::GenerateNewZKey(): Collision detected"); + + // Create new metadata + int64_t nCreationTime = GetTime(); + mapZKeyMetadata[addr] = CKeyMetadata(nCreationTime); + + if (!AddZKey(k)) + throw std::runtime_error("CWallet::GenerateNewZKey(): AddZKey failed"); + return addr; +} + +// Add spending key to keystore and persist to disk +bool CWallet::AddZKey(const libzcash::SpendingKey &key) +{ + AssertLockHeld(cs_wallet); // mapZKeyMetadata + auto addr = key.address(); + + if (!CCryptoKeyStore::AddSpendingKey(key)) + return false; + + // check if we need to remove from viewing keys + if (HaveViewingKey(addr)) + RemoveViewingKey(key.viewing_key()); + + if (!fFileBacked) + return true; + + if (!IsCrypted()) { + return CWalletDB(strWalletFile).WriteZKey(addr, + key, + mapZKeyMetadata[addr]); + } + return true; +} + CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { AssertLockHeld(cs_wallet); // mapKeyMetadata diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 1d574a9cc..3bf62f4c0 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -933,6 +933,11 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface void GetKeyBirthTimes(std::map &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; + //! Generates a new zaddr + libzcash::PaymentAddress GenerateNewZKey(); + //! Adds spending key to the store, and saves it to disk + bool AddZKey(const libzcash::SpendingKey &key); + /** * Increment the next transaction order id * @return next transaction order id From 7f60d38e5c9e77bf959470a09a48e5431b47a8b6 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 02:35:54 -0500 Subject: [PATCH 07/18] [wallet] [zk] [keystore] Add SpendingKey and ViewingKey Add/Remove/Get/Have --- src/keystore.h | 77 +++++++++++++++++++++++++++++++++++++++++++ src/wallet/wallet.cpp | 2 +- src/wallet/wallet.h | 1 + 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/keystore.h b/src/keystore.h index cd5ded920..1b672e6d3 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -36,6 +36,14 @@ class CKeyStore : public SigningProvider virtual bool RemoveWatchOnly(const CScript &dest) =0; virtual bool HaveWatchOnly(const CScript &dest) const =0; virtual bool HaveWatchOnly() const =0; + + //! Add a spending key to the store. + virtual bool AddSpendingKey(const libzcash::SpendingKey &sk) =0; + + //! Check whether a spending key corresponding to a given payment address is present in the store. + virtual bool HaveSpendingKey(const libzcash::PaymentAddress &address) const =0; + virtual bool GetSpendingKey(const libzcash::PaymentAddress &address, libzcash::SpendingKey& skOut) const =0; + virtual void GetPaymentAddresses(std::set &setAddress) const =0; }; typedef std::map KeyMap; @@ -43,6 +51,10 @@ typedef std::map WatchKeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; +typedef std::map SpendingKeyMap; +typedef std::map ViewingKeyMap; +typedef std::map NoteDecryptorMap; + /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore { @@ -53,6 +65,9 @@ class CBasicKeyStore : public CKeyStore WatchKeyMap mapWatchKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; + SpendingKeyMap mapSpendingKeys; + ViewingKeyMap mapViewingKeys; + NoteDecryptorMap mapNoteDecryptors; void ImplicitlyLearnRelatedKeyScripts(const CPubKey& pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore); @@ -72,10 +87,72 @@ class CBasicKeyStore : public CKeyStore bool RemoveWatchOnly(const CScript &dest) override; bool HaveWatchOnly(const CScript &dest) const override; bool HaveWatchOnly() const override; + + bool AddSpendingKey(const libzcash::SpendingKey &sk); + bool HaveSpendingKey(const libzcash::PaymentAddress &address) const + { + bool result; + { + LOCK(cs_SpendingKeyStore); + result = (mapSpendingKeys.count(address) > 0); + } + return result; + } + bool GetSpendingKey(const libzcash::PaymentAddress &address, libzcash::SpendingKey &skOut) const + { + { + LOCK(cs_SpendingKeyStore); + SpendingKeyMap::const_iterator mi = mapSpendingKeys.find(address); + if (mi != mapSpendingKeys.end()) + { + skOut = mi->second; + return true; + } + } + return false; + } + bool GetNoteDecryptor(const libzcash::PaymentAddress &address, ZCNoteDecryption &decOut) const + { + { + LOCK(cs_SpendingKeyStore); + NoteDecryptorMap::const_iterator mi = mapNoteDecryptors.find(address); + if (mi != mapNoteDecryptors.end()) + { + decOut = mi->second; + return true; + } + } + return false; + } + void GetPaymentAddresses(std::set &setAddress) const + { + setAddress.clear(); + { + LOCK(cs_SpendingKeyStore); + SpendingKeyMap::const_iterator mi = mapSpendingKeys.begin(); + while (mi != mapSpendingKeys.end()) + { + setAddress.insert((*mi).first); + mi++; + } + ViewingKeyMap::const_iterator mvi = mapViewingKeys.begin(); + while (mvi != mapViewingKeys.end()) + { + setAddress.insert((*mvi).first); + mvi++; + } + } + } + + virtual bool AddViewingKey(const libzcash::ViewingKey &vk); + virtual bool RemoveViewingKey(const libzcash::ViewingKey &vk); + virtual bool HaveViewingKey(const libzcash::PaymentAddress &address) const; + virtual bool GetViewingKey(const libzcash::PaymentAddress &address, libzcash::ViewingKey& vkOut) const; }; typedef std::vector > CKeyingMaterial; typedef std::map > > CryptedKeyMap; +typedef std::map > CryptedSpendingKeyMap; /** Return the CKeyID of the key involved in a script (if there is a unique one). */ CKeyID GetKeyForDestination(const CKeyStore& store, const CTxDestination& dest); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 5a6c6ece7..a30c0e191 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -158,7 +158,7 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() { AssertLockHeld(cs_wallet); // mapZKeyMetadata - auto k = SpendingKey::random(); + auto k = libzcash::SpendingKey::random(); auto addr = k.address(); // Check for collision, even though it is unlikely to ever occur diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 3bf62f4c0..f457b3aa6 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include From e43dd4c1581d24a38bade5bfbb48c94fc843442d Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 02:54:34 -0500 Subject: [PATCH 08/18] [wallet] [zk] [fixup] Add mapNullifiersToNotes, mapZKeyMetadata, cs_SpendingKeyStore declarations --- src/keystore.h | 1 + src/wallet/wallet.h | 51 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/keystore.h b/src/keystore.h index 1b672e6d3..4a4d2f0ef 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -60,6 +60,7 @@ class CBasicKeyStore : public CKeyStore { protected: mutable CCriticalSection cs_KeyStore; + mutable CCriticalSection cs_SpendingKeyStore; KeyMap mapKeys; WatchKeyMap mapWatchKeys; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f457b3aa6..bacf4175e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -797,6 +797,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface // Map from Key ID to key metadata. std::map mapKeyMetadata; + std::map mapZKeyMetadata; // Map from Script ID to key metadata (for watch-only keys). std::map m_script_metadata; @@ -816,6 +817,56 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface encrypted_batch = nullptr; } +/** + * The reverse mapping of nullifiers to notes. + * + * The mapping cannot be updated while an encrypted wallet is locked, + * because we need the SpendingKey to create the nullifier (#1502). This has + * several implications for transactions added to the wallet while locked: + * + * - Parent transactions can't be marked dirty when a child transaction that + * spends their output notes is updated. + * + * - We currently don't cache any note values, so this is not a problem, + * yet. + * + * - GetFilteredNotes can't filter out spent notes. + * + * - Per the comment in CNoteData, we assume that if we don't have a + * cached nullifier, the note is not spent. + * + * Another more problematic implication is that the wallet can fail to + * detect transactions on the blockchain that spend our notes. There are two + * possible cases in which this could happen: + * + * - We receive a note when the wallet is locked, and then spend it using a + * different wallet client. + * + * - We spend from a PaymentAddress we control, then we export the + * SpendingKey and import it into a new wallet, and reindex/rescan to find + * the old transactions. + * + * The wallet will only miss "pure" spends - transactions that are only + * linked to us by the fact that they contain notes we spent. If it also + * sends notes to us, or interacts with our transparent addresses, we will + * detect the transaction and add it to the wallet (again without caching + * nullifiers for new notes). As by default JoinSplits send change back to + * the origin PaymentAddress, the wallet should rarely miss transactions. + * + * To work around these issues, whenever the wallet is unlocked, we scan all + * cached notes, and cache any missing nullifiers. Since the wallet must be + * unlocked in order to spend notes, this means that GetFilteredNotes will + * always behave correctly within that context (and any other uses will give + * correct responses afterwards), for the transactions that the wallet was + * able to detect. Any missing transactions can be rediscovered by: + * + * - Unlocking the wallet (to fill all nullifier caches). + * + * - Restarting the node with -reindex (which operates on a locked wallet + * but with the now-cached nullifiers). + */ + std::map mapNullifiersToNotes; + std::map mapWallet; std::list laccentries; From 2747c7363191d0001d93566ded295c8491f9b6d5 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 03:32:53 -0500 Subject: [PATCH 09/18] [wallet] Remove fFileBacked --- src/wallet/wallet.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a30c0e191..cd39fb98e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -187,9 +187,6 @@ bool CWallet::AddZKey(const libzcash::SpendingKey &key) if (HaveViewingKey(addr)) RemoveViewingKey(key.viewing_key()); - if (!fFileBacked) - return true; - if (!IsCrypted()) { return CWalletDB(strWalletFile).WriteZKey(addr, key, From c7138f23e35074ae4193e5d6aecd32f55f063e10 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 04:26:40 -0500 Subject: [PATCH 10/18] [wallet] [zk] Add AddZKey(WithDB), WriteZKey --- src/wallet/wallet.cpp | 18 ++++++++++++------ src/wallet/wallet.h | 5 +++-- src/wallet/walletdb.h | 2 ++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index cd39fb98e..4eed0d133 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -175,7 +175,7 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey() } // Add spending key to keystore and persist to disk -bool CWallet::AddZKey(const libzcash::SpendingKey &key) +bool CWallet::AddZKeyWithDB(WalletBatch &batch, const libzcash::SpendingKey &key) { AssertLockHeld(cs_wallet); // mapZKeyMetadata auto addr = key.address(); @@ -188,13 +188,19 @@ bool CWallet::AddZKey(const libzcash::SpendingKey &key) RemoveViewingKey(key.viewing_key()); if (!IsCrypted()) { - return CWalletDB(strWalletFile).WriteZKey(addr, - key, - mapZKeyMetadata[addr]); + return batch.WriteZKey(addr, + key, + mapZKeyMetadata[addr]); } return true; } +bool CWallet::AddZKey(const libzcash::SpendingKey &key) +{ + WalletBatch batch(*database); + return CWallet::AddZKeyWithDB(batch, key); +} + CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { AssertLockHeld(cs_wallet); // mapKeyMetadata @@ -306,8 +312,8 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C if (!IsCrypted()) { return batch.WriteKey(pubkey, - secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); + secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); } return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index bacf4175e..adeb5bbfb 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -986,9 +986,10 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface unsigned int ComputeTimeSmart(const CWalletTx& wtx) const; //! Generates a new zaddr - libzcash::PaymentAddress GenerateNewZKey(); + libzcash::PaymentAddress GenerateNewZKey() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds spending key to the store, and saves it to disk - bool AddZKey(const libzcash::SpendingKey &key); + bool AddZKey(const libzcash::SpendingKey &key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // no override + bool AddZKeyWithDB(WalletBatch &batch, const libzcash::SpendingKey &key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Increment the next transaction order id diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 3237376f6..09d15164a 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -185,6 +185,8 @@ class WalletBatch bool WriteCScript(const uint160& hash, const CScript& redeemScript); + bool WriteZKey(const libzcash::PaymentAddress& addr, const libzcash::SpendingKey& key, const CKeyMetadata& keyMeta); + bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta); bool EraseWatchOnly(const CScript &script); From ecfece697e0d6e1d9a70708fa017c919882b05bf Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 04:38:23 -0500 Subject: [PATCH 11/18] [wallet] [zk] Add mapTxNullifiers and related --- src/wallet/wallet.cpp | 15 +++++++++++++++ src/wallet/wallet.h | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 4eed0d133..11716bbfd 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -566,6 +566,21 @@ std::set CWallet::GetConflicts(const uint256& txid) const for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) result.insert(_it->second); } + + std::pair range_n; + + for (const JSDescription& jsdesc : wtx.vjoinsplit) { + for (const uint256& nullifier : jsdesc.nullifiers) { + if (mapTxNullifiers.count(nullifier) <= 1) { + continue; // No conflict if zero or one spends + } + range_n = mapTxNullifiers.equal_range(nullifier); + for (TxNullifiers::const_iterator it = range_n.first; it != range_n.second; ++it) { + result.insert(it->second); + } + } + } + return result; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index adeb5bbfb..337bf706a 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -706,7 +706,12 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface */ typedef std::multimap TxSpends; TxSpends mapTxSpends; + + typedef std::multimap TxNullifiers; + TxNullifiers mapTxNullifiers; + void AddToSpends(const COutPoint& outpoint, const uint256& wtxid); + void AddToSpends(const uint256& nullifier, const uint256& wtxid); void AddToSpends(const uint256& wtxid); /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */ From 193c8ebaac90247adb280878c9a02264db85f5bc Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sun, 15 Jul 2018 04:44:52 -0500 Subject: [PATCH 12/18] [zk] [fixup] Explicitly reference libzcash::note_decryption_failed --- src/wallet/wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 11716bbfd..e49c5ac0a 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1570,7 +1570,7 @@ mapNoteData_t CWallet::FindMyNotes(const CTransaction& tx) const noteData.insert(std::make_pair(jsoutpt, nd)); } break; - } catch (const note_decryption_failed &err) { + } catch (const libzcash::note_decryption_failed &err) { // Couldn't decrypt with this decryptor } catch (const std::exception &exc) { // Unexpected failure From 36d912bbe5d978827ad46a4cd4a6d24bed5b5823 Mon Sep 17 00:00:00 2001 From: Jon Layton Date: Sat, 21 Jul 2018 17:27:01 -0500 Subject: [PATCH 13/18] [consensus] Finish consensus/joinsplit.cpp & h --- src/consensus/joinsplit.cpp | 27 +++++++++++++++++++++------ src/consensus/joinsplit.h | 7 +++++-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/consensus/joinsplit.cpp b/src/consensus/joinsplit.cpp index b00d9dd71..4c7d355f4 100644 --- a/src/consensus/joinsplit.cpp +++ b/src/consensus/joinsplit.cpp @@ -1,18 +1,33 @@ -bool CheckTransactionJoinsplits(const CTransaction& tx, CValidationState &state) + +// Copyright (c) 2018 The Bitcoin Private Developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include