From ad1e90dd34981e93abe526173e1f2b97d4ed5fd7 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 1 Aug 2018 15:28:27 -0700 Subject: [PATCH] Add caching and updating of Sapling note nullifier. --- src/wallet/wallet.cpp | 74 ++++++++++++++++++++++++++++++++++++++++++- src/wallet/wallet.h | 8 +++-- 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 2aa589dae..cb6d6f0d2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -474,8 +474,10 @@ void CWallet::ChainTip(const CBlockIndex *pindex, { if (added) { IncrementNoteWitnesses(pindex, pblock, sproutTree, saplingTree); + UpdateSaplingNullifierNoteMapForBlock(pblock); } else { DecrementNoteWitnesses(pindex); + UpdateSaplingNullifierNoteMapForBlock(pblock); } } @@ -1169,6 +1171,10 @@ bool CWallet::UpdateNullifierNoteMap() } } } + + // TODO: Sapling. This method is only called from RPC walletpassphrase, which is currently unsupported + // as RPC encryptwallet is hidden behind two flags: -developerencryptwallet -experimentalfeatures + UpdateNullifierNoteMapWithTx(wtxItem.second); } } @@ -1176,7 +1182,8 @@ bool CWallet::UpdateNullifierNoteMap() } /** - * Update mapSproutNullifiersToNotes with the cached nullifiers in this tx. + * Update mapSproutNullifiersToNotes and mapSaplingNullifiersToNotes + * with the cached nullifiers in this tx. */ void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx) { @@ -1187,6 +1194,71 @@ void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx) mapSproutNullifiersToNotes[*item.second.nullifier] = item.first; } } + + for (const mapSaplingNoteData_t::value_type& item : wtx.mapSaplingNoteData) { + if (item.second.nullifier) { + mapSaplingNullifiersToNotes[*item.second.nullifier] = item.first; + } + } + } +} + +/** + * Update mapSaplingNullifiersToNotes, computing the nullifier from a cached witness if necessary. + */ +void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) { + LOCK(cs_wallet); + + for (mapSaplingNoteData_t::value_type &item : wtx.mapSaplingNoteData) { + SaplingOutPoint op = item.first; + SaplingNoteData nd = item.second; + + if (nd.witnesses.size() == 0) { + // If there are no witnesses, erase the nullifier and associated mapping. + if (item.second.nullifier) { + mapSaplingNullifiersToNotes.erase(item.second.nullifier.get()); + } + item.second.nullifier = boost::none; + } + else if (nd.witnesses.size() > 0) { + uint64_t position = nd.witnesses.front().position(); + SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk); + OutputDescription output = wtx.vShieldedOutput[op.n]; + auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm); + if (!optPlaintext) { + // An item in mapSaplingNoteData must have already been successfully decrypted, + // otherwise the item would not exist in the first place. + assert(false); + } + auto optNote = optPlaintext.get().note(nd.ivk); + if (!optNote) { + assert(false); + } + auto optNullifier = optNote.get().nullifier(fvk, position); + if (!optNullifier) { + // This should not happen. If it does, maybe the position has been corrupted or miscalculated? + assert(false); + } + uint256 nullifier = optNullifier.get(); + mapSaplingNullifiersToNotes[nullifier] = op; + item.second.nullifier = nullifier; + } + } +} + +/** + * Iterate over transactions in a block and update the cached Sapling nullifiers + * for transactions which belong to the wallet. + */ +void CWallet::UpdateSaplingNullifierNoteMapForBlock(const CBlock *pblock) { + LOCK(cs_wallet); + + for (const CTransaction& tx : pblock->vtx) { + auto hash = tx.GetHash(); + bool txIsOurs = mapWallet.count(hash); + if (txIsOurs) { + UpdateSaplingNullifierNoteMapWithTx(mapWallet[hash]); + } } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index d020357a2..00e0af7d9 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -270,12 +270,14 @@ public: * We initialize the height to -1 for the same reason as we do in SproutNoteData. * See the comment in that class for a full description. */ - SaplingNoteData() : witnessHeight {-1} { } - SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk) : ivk {ivk}, witnessHeight {-1} { } + SaplingNoteData() : witnessHeight {-1}, nullifier() { } + SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk) : ivk {ivk}, witnessHeight {-1}, nullifier() { } + SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk, uint256 n) : ivk {ivk}, witnessHeight {-1}, nullifier(n) { } std::list witnesses; int witnessHeight; libzcash::SaplingIncomingViewingKey ivk; + boost::optional nullifier; friend bool operator==(const SaplingNoteData& a, const SaplingNoteData& b) { return (a.ivk == b.ivk && a.nullifier == b.nullifier && a.witnessHeight == b.witnessHeight); @@ -1050,6 +1052,8 @@ public: void MarkDirty(); bool UpdateNullifierNoteMap(); void UpdateNullifierNoteMapWithTx(const CWalletTx& wtx); + void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& tx); + void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock); bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb); void SyncTransaction(const CTransaction& tx, const CBlock* pblock); bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate);