Delay caching of nullifiers when wallet is locked

Closes #1502
This commit is contained in:
Jack Grigg
2016-10-11 21:49:14 -05:00
parent 8f445ee774
commit 1a62587e9a
5 changed files with 226 additions and 23 deletions

View File

@@ -201,12 +201,18 @@ class CNoteData
public:
libzcash::PaymentAddress address;
// It's okay to cache the nullifier in the wallet, because we are storing
// the spending key there too, which could be used to derive this.
// If PR #1210 is merged, we need to revisit the threat model and decide
// whether it is okay to store this unencrypted while the spending key is
// encrypted.
uint256 nullifier;
/**
* Cached note nullifier. May not be set if the wallet was not unlocked when
* this was CNoteData was created. If not set, we always assume that the
* note has not been spent.
*
* It's okay to cache the nullifier in the wallet, because we are storing
* the spending key there too, which could be used to derive this.
* If PR #1210 is merged, we need to revisit the threat model and decide
* whether it is okay to store this unencrypted while the spending key is
* encrypted.
*/
boost::optional<uint256> nullifier;
/**
* Cached incremental witnesses for spendable Notes.
@@ -215,6 +221,7 @@ public:
std::list<ZCIncrementalWitness> witnesses;
CNoteData() : address(), nullifier() { }
CNoteData(libzcash::PaymentAddress a) : address {a}, nullifier() { }
CNoteData(libzcash::PaymentAddress a, uint256 n) : address {a}, nullifier {n} { }
ADD_SERIALIZE_METHODS;
@@ -704,7 +711,59 @@ public:
nWitnessCacheSize = 0;
}
/**
* 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).
*
* Several rounds of this may be required to incrementally fill the
* nullifier caches of discovered notes.
*/
std::map<uint256, JSOutPoint> mapNullifiersToNotes;
std::map<uint256, CWalletTx> mapWallet;
int64_t nOrderPosNext;
@@ -810,6 +869,7 @@ public:
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
void MarkDirty();
bool UpdateNullifierNoteMap();
void UpdateNullifierNoteMap(const CWalletTx& wtx);
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
@@ -850,6 +910,12 @@ public:
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
boost::optional<uint256> 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;
bool IsFromMe(const uint256& nullifier) const;
void GetNoteWitnesses(