Auto merge of #1233 - str4d:1199-note-tracking, r=str4d

Add Note tracking

This PR extends the existing transaction tracking in the wallet to track spendable Notes.

Closes #1199
This commit is contained in:
zkbot 2016-09-01 08:17:07 +00:00
commit a723794f81
20 changed files with 1103 additions and 20 deletions

View File

@ -18,7 +18,8 @@ zcash_gtest_SOURCES = \
gtest/test_txid.cpp \
gtest/test_wallet_zkeys.cpp \
gtest/test_libzcash_utils.cpp \
gtest/test_proofs.cpp
gtest/test_proofs.cpp \
wallet/gtest/test_wallet.cpp
zcash_gtest_CPPFLAGS = -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC

View File

@ -27,3 +27,17 @@ TEST(keystore_tests, store_and_retrieve_spending_key) {
EXPECT_EQ(1, addrs.size());
EXPECT_EQ(1, addrs.count(addr));
}
TEST(keystore_tests, store_and_retrieve_note_decryptor) {
CBasicKeyStore keyStore;
ZCNoteDecryption decOut;
auto sk = libzcash::SpendingKey::random();
auto addr = sk.address();
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut));
keyStore.AddSpendingKey(sk);
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.viewing_key()), decOut);
}

View File

@ -87,6 +87,8 @@ bool CBasicKeyStore::HaveWatchOnly() const
bool CBasicKeyStore::AddSpendingKey(const libzcash::SpendingKey &sk)
{
LOCK(cs_SpendingKeyStore);
mapSpendingKeys[sk.address()] = sk;
auto address = sk.address();
mapSpendingKeys[address] = sk;
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(sk.viewing_key())));
return true;
}

View File

@ -12,6 +12,7 @@
#include "script/standard.h"
#include "sync.h"
#include "zcash/Address.hpp"
#include "zcash/NoteEncryption.hpp"
#include <boost/signals2/signal.hpp>
#include <boost/variant.hpp>
@ -60,6 +61,7 @@ typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet;
typedef std::map<libzcash::PaymentAddress, libzcash::SpendingKey> SpendingKeyMap;
typedef std::map<libzcash::PaymentAddress, ZCNoteDecryption> NoteDecryptorMap;
/** Basic key store, that keeps keys in an address->secret map */
class CBasicKeyStore : public CKeyStore
@ -69,6 +71,7 @@ protected:
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;
SpendingKeyMap mapSpendingKeys;
NoteDecryptorMap mapNoteDecryptors;
public:
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
@ -139,6 +142,19 @@ public:
}
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<libzcash::PaymentAddress> &setAddress) const
{
setAddress.clear();

View File

@ -2398,11 +2398,16 @@ bool static DisconnectTip(CValidationState &state) {
mempool.check(pcoinsTip);
// Update chainActive and related variables.
UpdateTip(pindexDelete->pprev);
// Get the current commitment tree
ZCIncrementalMerkleTree newTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), newTree));
// Let wallets know transactions went from 1-confirmed to
// 0-confirmed or conflicted:
BOOST_FOREACH(const CTransaction &tx, block.vtx) {
SyncWithWallets(tx, NULL);
}
// Update cached incremental witnesses
GetMainSignals().ChainTip(pindexDelete, &block, newTree, false);
return true;
}
@ -2427,6 +2432,9 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
return AbortNode(state, "Failed to read block");
pblock = &block;
}
// Get the current commitment tree
ZCIncrementalMerkleTree oldTree;
assert(pcoinsTip->GetAnchorAt(pcoinsTip->GetBestAnchor(), oldTree));
// Apply the block atomically to the chain state.
int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1;
int64_t nTime3;
@ -2468,6 +2476,8 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
BOOST_FOREACH(const CTransaction &tx, pblock->vtx) {
SyncWithWallets(tx, pblock);
}
// Update cached incremental witnesses
GetMainSignals().ChainTip(pindexNew, pblock, oldTree, true);
int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1;
LogPrint("bench", " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001);

View File

@ -15,11 +15,14 @@ JSDescription::JSDescription(ZCJoinSplit& params,
const boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
const boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new) : vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor)
CAmount vpub_new,
bool computeProof) : vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor)
{
boost::array<libzcash::Note, ZC_NUM_JS_OUTPUTS> notes;
params.loadProvingKey();
if (computeProof) {
params.loadProvingKey();
}
proof = params.prove(
inputs,
outputs,
@ -33,7 +36,8 @@ JSDescription::JSDescription(ZCJoinSplit& params,
commitments,
vpub_old,
vpub_new,
anchor
anchor,
computeProof
);
}

View File

@ -74,7 +74,8 @@ public:
const boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
const boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new
CAmount vpub_new,
bool computeProof = true // Set to false in some tests
);
// Verifies that the JoinSplit proof is correct.

View File

@ -12,6 +12,7 @@
#include <assert.h>
#include <ios>
#include <limits>
#include <list>
#include <map>
#include <set>
#include <stdint.h>
@ -544,6 +545,13 @@ template<typename K, typename Pred, typename A> unsigned int GetSerializeSize(co
template<typename Stream, typename K, typename Pred, typename A> void Serialize(Stream& os, const std::set<K, Pred, A>& m, int nType, int nVersion);
template<typename Stream, typename K, typename Pred, typename A> void Unserialize(Stream& is, std::set<K, Pred, A>& m, int nType, int nVersion);
/**
* list
*/
template<typename T, typename A> unsigned int GetSerializeSize(const std::list<T, A>& m, int nType, int nVersion);
template<typename Stream, typename T, typename A> void Serialize(Stream& os, const std::list<T, A>& m, int nType, int nVersion);
template<typename Stream, typename T, typename A> void Unserialize(Stream& is, std::list<T, A>& m, int nType, int nVersion);
@ -890,6 +898,42 @@ void Unserialize(Stream& is, std::set<K, Pred, A>& m, int nType, int nVersion)
/**
* list
*/
template<typename T, typename A>
unsigned int GetSerializeSize(const std::list<T, A>& l, int nType, int nVersion)
{
unsigned int nSize = GetSizeOfCompactSize(l.size());
for (typename std::list<T, A>::const_iterator it = l.begin(); it != l.end(); ++it)
nSize += GetSerializeSize((*it), nType, nVersion);
return nSize;
}
template<typename Stream, typename T, typename A>
void Serialize(Stream& os, const std::list<T, A>& l, int nType, int nVersion)
{
WriteCompactSize(os, l.size());
for (typename std::list<T, A>::const_iterator it = l.begin(); it != l.end(); ++it)
Serialize(os, (*it), nType, nVersion);
}
template<typename Stream, typename T, typename A>
void Unserialize(Stream& is, std::list<T, A>& l, int nType, int nVersion)
{
l.clear();
unsigned int nSize = ReadCompactSize(is);
typename std::list<T, A>::iterator it = l.begin();
for (unsigned int i = 0; i < nSize; i++)
{
T item;
Unserialize(is, item, nType, nVersion);
l.push_back(item);
}
}
/**
* Support for ADD_SERIALIZE_METHODS and READWRITE macro
*/

View File

@ -16,6 +16,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.SyncTransaction.connect(boost::bind(&CValidationInterface::SyncTransaction, pwalletIn, _1, _2));
g_signals.EraseTransaction.connect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.ChainTip.connect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3, _4));
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
@ -26,6 +27,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.ChainTip.disconnect(boost::bind(&CValidationInterface::ChainTip, pwalletIn, _1, _2, _3, _4));
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.EraseTransaction.disconnect(boost::bind(&CValidationInterface::EraseFromWallet, pwalletIn, _1));
@ -36,6 +38,7 @@ void UnregisterAllValidationInterfaces() {
g_signals.BlockChecked.disconnect_all_slots();
g_signals.Broadcast.disconnect_all_slots();
g_signals.Inventory.disconnect_all_slots();
g_signals.ChainTip.disconnect_all_slots();
g_signals.SetBestChain.disconnect_all_slots();
g_signals.UpdatedTransaction.disconnect_all_slots();
g_signals.EraseTransaction.disconnect_all_slots();

View File

@ -8,7 +8,10 @@
#include <boost/signals2/signal.hpp>
#include "zcash/IncrementalMerkleTree.hpp"
class CBlock;
class CBlockIndex;
struct CBlockLocator;
class CTransaction;
class CValidationInterface;
@ -30,6 +33,7 @@ class CValidationInterface {
protected:
virtual void SyncTransaction(const CTransaction &tx, const CBlock *pblock) {}
virtual void EraseFromWallet(const uint256 &hash) {}
virtual void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, ZCIncrementalMerkleTree tree, bool added) {}
virtual void SetBestChain(const CBlockLocator &locator) {}
virtual void UpdatedTransaction(const uint256 &hash) {}
virtual void Inventory(const uint256 &hash) {}
@ -47,6 +51,8 @@ struct CMainSignals {
boost::signals2::signal<void (const uint256 &)> EraseTransaction;
/** Notifies listeners of an updated transaction without new data (for now: a coinbase potentially becoming visible). */
boost::signals2::signal<void (const uint256 &)> UpdatedTransaction;
/** Notifies listeners of a change to the tip of the active block chain. */
boost::signals2::signal<void (const CBlockIndex *, const CBlock *, ZCIncrementalMerkleTree, bool)> ChainTip;
/** Notifies listeners of a new active block chain. */
boost::signals2::signal<void (const CBlockLocator &)> SetBestChain;
/** Notifies listeners about an inventory item being seen on the network. */

View File

@ -0,0 +1,474 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <sodium.h>
#include "base58.h"
#include "chainparams.h"
#include "main.h"
#include "random.h"
#include "wallet/wallet.h"
#include "zcash/JoinSplit.hpp"
#include "zcash/Note.hpp"
#include "zcash/NoteEncryption.hpp"
using ::testing::_;
using ::testing::Return;
ZCJoinSplit* params = ZCJoinSplit::Unopened();
class TestWallet : public CWallet {
public:
TestWallet() : CWallet() { }
void IncrementNoteWitnesses(const CBlockIndex* pindex,
const CBlock* pblock,
ZCIncrementalMerkleTree tree) {
CWallet::IncrementNoteWitnesses(pindex, pblock, tree);
}
void DecrementNoteWitnesses() {
CWallet::DecrementNoteWitnesses();
}
};
CWalletTx GetValidReceive(const libzcash::SpendingKey& sk, CAmount value, bool randomInputs) {
CMutableTransaction mtx;
mtx.nVersion = 2; // Enable JoinSplits
mtx.vin.resize(2);
if (randomInputs) {
mtx.vin[0].prevout.hash = GetRandHash();
mtx.vin[1].prevout.hash = GetRandHash();
} else {
mtx.vin[0].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000001");
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002");
}
mtx.vin[0].prevout.n = 0;
mtx.vin[1].prevout.n = 0;
// Generate an ephemeral keypair.
uint256 joinSplitPubKey;
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
mtx.joinSplitPubKey = joinSplitPubKey;
boost::array<libzcash::JSInput, 2> inputs = {
libzcash::JSInput(), // dummy input
libzcash::JSInput() // dummy input
};
boost::array<libzcash::JSOutput, 2> outputs = {
libzcash::JSOutput(sk.address(), value),
libzcash::JSOutput(sk.address(), value)
};
boost::array<libzcash::Note, 2> output_notes;
// Prepare JoinSplits
uint256 rt;
JSDescription jsdesc {*params, mtx.joinSplitPubKey, rt,
inputs, outputs, value, 0, false};
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
// Add the signature
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
CTransaction tx {mtx};
CWalletTx wtx {NULL, tx};
return wtx;
}
libzcash::Note GetNote(const libzcash::SpendingKey& sk,
const CTransaction& tx, size_t js, size_t n) {
ZCNoteDecryption decryptor {sk.viewing_key()};
auto hSig = tx.vjoinsplit[js].h_sig(*params, tx.joinSplitPubKey);
auto note_pt = libzcash::NotePlaintext::decrypt(
decryptor,
tx.vjoinsplit[js].ciphertexts[n],
tx.vjoinsplit[js].ephemeralKey,
hSig,
(unsigned char) n);
return note_pt.note(sk.address());
}
CWalletTx GetValidSpend(const libzcash::SpendingKey& sk,
const libzcash::Note& note, CAmount value) {
CMutableTransaction mtx;
mtx.vout.resize(2);
mtx.vout[0].nValue = value;
mtx.vout[1].nValue = 0;
// Generate an ephemeral keypair.
uint256 joinSplitPubKey;
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey);
mtx.joinSplitPubKey = joinSplitPubKey;
// Fake tree for the unused witness
ZCIncrementalMerkleTree tree;
boost::array<libzcash::JSInput, 2> inputs = {
libzcash::JSInput(tree.witness(), note, sk),
libzcash::JSInput() // dummy input
};
boost::array<libzcash::JSOutput, 2> outputs = {
libzcash::JSOutput(), // dummy output
libzcash::JSOutput() // dummy output
};
boost::array<libzcash::Note, 2> output_notes;
// Prepare JoinSplits
uint256 rt;
JSDescription jsdesc {*params, mtx.joinSplitPubKey, rt,
inputs, outputs, 0, value, false};
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL);
// Add the signature
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey
) == 0);
CTransaction tx {mtx};
CWalletTx wtx {NULL, tx};
return wtx;
}
TEST(wallet_tests, note_data_serialisation) {
auto sk = libzcash::SpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
ZCIncrementalMerkleTree tree;
nd.witnesses.push_front(tree.witness());
noteData[jsoutpt] = nd;
CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << noteData;
mapNoteData_t noteData2;
ss >> noteData2;
EXPECT_EQ(noteData, noteData2);
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
}
TEST(wallet_tests, set_note_addrs_in_cwallettx) {
auto sk = libzcash::SpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
EXPECT_EQ(0, wtx.mapNoteData.size());
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetNoteData(noteData);
EXPECT_EQ(noteData, wtx.mapNoteData);
}
TEST(wallet_tests, set_invalid_note_addrs_in_cwallettx) {
CWalletTx wtx;
EXPECT_EQ(0, wtx.mapNoteData.size());
mapNoteData_t noteData;
auto sk = libzcash::SpendingKey::random();
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), uint256()};
noteData[jsoutpt] = nd;
EXPECT_THROW(wtx.SetNoteData(noteData), std::logic_error);
}
TEST(wallet_tests, find_note_in_tx) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto noteMap = wallet.FindMyNotes(wtx);
EXPECT_EQ(2, noteMap.size());
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(wallet_tests, get_conflicted_notes) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
auto wtx3 = GetValidSpend(sk, note, 10);
auto hash2 = wtx2.GetTxid();
auto hash3 = wtx3.GetTxid();
// No conflicts for no spends
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// No conflicts for one spend
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// Conflicts for two spends
wallet.AddToWallet(wtx3, true, NULL);
auto c3 = wallet.GetConflicts(hash2);
EXPECT_EQ(2, c3.size());
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
}
TEST(wallet_tests, nullifier_is_spent) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
EXPECT_FALSE(wallet.IsSpent(nullifier));
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSpent(nullifier));
auto wtx2 = GetValidSpend(sk, note, 5);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_FALSE(wallet.IsSpent(nullifier));
// Fake-mine the transaction
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx2);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
wtx2.SetMerkleBranch(block);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_TRUE(wallet.IsSpent(nullifier));
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
}
TEST(wallet_tests, navigate_from_nullifier_to_note) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetNoteData(noteData);
EXPECT_EQ(0, wallet.mapNullifiersToNotes.count(nullifier));
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(1, wallet.mapNullifiersToNotes.count(nullifier));
EXPECT_EQ(wtx.GetTxid(), wallet.mapNullifiersToNotes[nullifier].hash);
EXPECT_EQ(0, wallet.mapNullifiersToNotes[nullifier].js);
EXPECT_EQ(1, wallet.mapNullifiersToNotes[nullifier].n);
}
TEST(wallet_tests, spent_note_is_from_me) {
CWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
EXPECT_FALSE(wallet.IsFromMe(wtx));
EXPECT_FALSE(wallet.IsFromMe(wtx2));
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetNoteData(noteData);
EXPECT_FALSE(wallet.IsFromMe(wtx));
EXPECT_FALSE(wallet.IsFromMe(wtx2));
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsFromMe(wtx));
EXPECT_TRUE(wallet.IsFromMe(wtx2));
}
TEST(wallet_tests, cached_witnesses_empty_chain) {
TestWallet wallet;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 0);
auto note2 = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto nullifier2 = note2.nullifier(sk);
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 0};
JSOutPoint jsoutpt2 {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
CNoteData nd2 {sk.address(), nullifier2};
noteData[jsoutpt] = nd;
noteData[jsoutpt2] = nd2;
wtx.SetNoteData(noteData);
std::vector<JSOutPoint> notes {jsoutpt, jsoutpt2};
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
uint256 anchor;
wallet.GetNoteWitnesses(notes, witnesses, anchor);
EXPECT_FALSE((bool) witnesses[0]);
EXPECT_FALSE((bool) witnesses[1]);
wallet.AddToWallet(wtx, true, NULL);
witnesses.clear();
wallet.GetNoteWitnesses(notes, witnesses, anchor);
EXPECT_FALSE((bool) witnesses[0]);
EXPECT_FALSE((bool) witnesses[1]);
CBlock block;
block.vtx.push_back(wtx);
ZCIncrementalMerkleTree tree;
wallet.IncrementNoteWitnesses(NULL, &block, tree);
witnesses.clear();
wallet.GetNoteWitnesses(notes, witnesses, anchor);
EXPECT_TRUE((bool) witnesses[0]);
EXPECT_TRUE((bool) witnesses[1]);
// Until #1302 is implemented, this should triggger an assertion
EXPECT_DEATH(wallet.DecrementNoteWitnesses(),
"Assertion `nWitnessCacheSize > 0' failed.");
}
TEST(wallet_tests, cached_witnesses_chain_tip) {
TestWallet wallet;
uint256 anchor1;
CBlock block1;
ZCIncrementalMerkleTree tree;
auto sk = libzcash::SpendingKey::random();
wallet.AddSpendingKey(sk);
{
// First transaction (case tested in _empty_chain)
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
std::vector<JSOutPoint> notes {jsoutpt};
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
// First block (case tested in _empty_chain)
block1.vtx.push_back(wtx);
wallet.IncrementNoteWitnesses(NULL, &block1, tree);
// Called to fetch anchor
wallet.GetNoteWitnesses(notes, witnesses, anchor1);
}
{
// Second transaction
auto wtx = GetValidReceive(sk, 50, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
std::vector<JSOutPoint> notes {jsoutpt};
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
uint256 anchor2;
wallet.GetNoteWitnesses(notes, witnesses, anchor2);
EXPECT_FALSE((bool) witnesses[0]);
// Second block
CBlock block2;
block2.hashPrevBlock = block1.GetHash();
block2.vtx.push_back(wtx);
ZCIncrementalMerkleTree tree2 {tree};
wallet.IncrementNoteWitnesses(NULL, &block2, tree2);
witnesses.clear();
wallet.GetNoteWitnesses(notes, witnesses, anchor2);
EXPECT_TRUE((bool) witnesses[0]);
EXPECT_NE(anchor1, anchor2);
// Decrementing should give us the previous anchor
uint256 anchor3;
wallet.DecrementNoteWitnesses();
witnesses.clear();
wallet.GetNoteWitnesses(notes, witnesses, anchor3);
EXPECT_FALSE((bool) witnesses[0]);
// Should not equal first anchor because none of these notes had witnesses
EXPECT_NE(anchor1, anchor3);
// Re-incrementing with the same block should give the same result
uint256 anchor4;
wallet.IncrementNoteWitnesses(NULL, &block2, tree);
witnesses.clear();
wallet.GetNoteWitnesses(notes, witnesses, anchor4);
EXPECT_TRUE((bool) witnesses[0]);
EXPECT_EQ(anchor2, anchor4);
}
}

View File

@ -8,8 +8,8 @@
#include "base58.h"
#include "checkpoints.h"
#include "coincontrol.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "init.h"
#include "main.h"
#include "net.h"
#include "script/script.h"
@ -17,6 +17,7 @@
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include <assert.h>
@ -57,6 +58,11 @@ struct CompareValueOnly
}
};
std::string JSOutPoint::ToString() const
{
return strprintf("JSOutPoint(%s, %d, %d)", hash.ToString().substr(0,10), js, n);
}
std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetTxid().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue));
@ -329,6 +335,16 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
return false;
}
void CWallet::ChainTip(const CBlockIndex *pindex, const CBlock *pblock,
ZCIncrementalMerkleTree tree, bool added)
{
if (added) {
IncrementNoteWitnesses(pindex, pblock, tree);
} else {
DecrementNoteWitnesses();
}
}
void CWallet::SetBestChain(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile);
@ -394,6 +410,20 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
result.insert(it->second);
}
std::pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> 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;
}
@ -449,7 +479,8 @@ bool CWallet::Verify(const string& walletFile, string& warningString, string& er
return true;
}
void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
template <class T>
void CWallet::SyncMetaData(pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator> range)
{
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
@ -457,7 +488,7 @@ void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx* copyFrom = NULL;
for (TxSpends::iterator it = range.first; it != range.second; ++it)
for (typename TxSpendMap<T>::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
int n = mapWallet[hash].nOrderPos;
@ -468,12 +499,14 @@ void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
}
}
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it)
for (typename TxSpendMap<T>::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
copyTo->mapValue = copyFrom->mapValue;
// mapNoteData not copied on purpose
// (it is always set correctly for each CWalletTx)
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose
// nTimeReceived not copied on purpose
@ -505,15 +538,42 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
return false;
}
/**
* Note is spent if any non-conflicted transaction
* spends it:
*/
bool CWallet::IsSpent(const uint256& nullifier) const
{
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxNullifiers.equal_range(nullifier);
for (TxNullifiers::const_iterator it = range.first; it != range.second; ++it) {
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
return true; // Spent
}
}
return false;
}
void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
{
mapTxSpends.insert(make_pair(outpoint, wtxid));
pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
SyncMetaData<COutPoint>(range);
}
void CWallet::AddToSpends(const uint256& nullifier, const uint256& wtxid)
{
mapTxNullifiers.insert(make_pair(nullifier, wtxid));
pair<TxNullifiers::iterator, TxNullifiers::iterator> range;
range = mapTxNullifiers.equal_range(nullifier);
SyncMetaData<uint256>(range);
}
void CWallet::AddToSpends(const uint256& wtxid)
{
@ -522,8 +582,102 @@ void CWallet::AddToSpends(const uint256& wtxid)
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
BOOST_FOREACH(const CTxIn& txin, thisTx.vin)
for (const CTxIn& txin : thisTx.vin) {
AddToSpends(txin.prevout, wtxid);
}
for (const JSDescription& jsdesc : thisTx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
AddToSpends(nullifier, wtxid);
}
}
}
void CWallet::IncrementNoteWitnesses(const CBlockIndex* pindex,
const CBlock* pblockIn,
ZCIncrementalMerkleTree tree)
{
{
LOCK(cs_wallet);
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) {
CNoteData* nd = &(item.second);
// Check the validity of the cache
assert(nWitnessCacheSize >= nd->witnesses.size());
// Copy the witness for the previous block if we have one
if (nd->witnesses.size() > 0) {
nd->witnesses.push_front(nd->witnesses.front());
}
if (nd->witnesses.size() > WITNESS_CACHE_SIZE) {
nd->witnesses.pop_back();
}
}
}
const CBlock* pblock {pblockIn};
CBlock block;
if (!pblock) {
ReadBlockFromDisk(block, pindex);
pblock = &block;
}
for (const CTransaction& tx : pblock->vtx) {
auto hash = tx.GetTxid();
bool txIsOurs = mapWallet.count(hash);
for (size_t i = 0; i < tx.vjoinsplit.size(); i++) {
const JSDescription& jsdesc = tx.vjoinsplit[i];
for (uint8_t j = 0; j < jsdesc.commitments.size(); j++) {
const uint256& note_commitment = jsdesc.commitments[j];
tree.append(note_commitment);
// Increment existing witnesses
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) {
CNoteData* nd = &(item.second);
if (nd->witnesses.size() > 0) {
nd->witnesses.front().append(note_commitment);
}
}
}
// If this is our note, witness it
if (txIsOurs) {
JSOutPoint jsoutpt {hash, i, j};
if (mapWallet[hash].mapNoteData.count(jsoutpt)) {
mapWallet[hash].mapNoteData[jsoutpt].witnesses.push_front(
tree.witness());
}
}
}
}
}
if (nWitnessCacheSize < WITNESS_CACHE_SIZE) {
nWitnessCacheSize += 1;
}
if (fFileBacked) {
CWalletDB(strWalletFile).WriteWitnessCacheSize(nWitnessCacheSize);
}
}
}
void CWallet::DecrementNoteWitnesses()
{
{
LOCK(cs_wallet);
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
for (mapNoteData_t::value_type& item : wtxItem.second.mapNoteData) {
CNoteData* nd = &(item.second);
if (nd->witnesses.size() > 0) {
nd->witnesses.pop_front();
}
}
}
nWitnessCacheSize -= 1;
// TODO: If nWitnessCache is zero, we need to regenerate the caches (#1302)
assert(nWitnessCacheSize > 0);
if (fFileBacked) {
CWalletDB(strWalletFile).WriteWitnessCacheSize(nWitnessCacheSize);
}
}
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
@ -665,6 +819,16 @@ void CWallet::MarkDirty()
}
}
void CWallet::UpdateNullifierNoteMap(const CWalletTx& wtx)
{
{
LOCK(cs_wallet);
for (const mapNoteData_t::value_type& item : wtx.mapNoteData) {
mapNullifiersToNotes[item.second.nullifier] = item.first;
}
}
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb)
{
uint256 hash = wtxIn.GetTxid();
@ -673,6 +837,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
{
mapWallet[hash] = wtxIn;
mapWallet[hash].BindWallet(this);
UpdateNullifierNoteMap(mapWallet[hash]);
AddToSpends(hash);
}
else
@ -682,6 +847,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
UpdateNullifierNoteMap(wtx);
bool fInsertedNew = ret.second;
if (fInsertedNew)
{
@ -751,6 +917,20 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (!wtxIn.mapNoteData.empty() && wtxIn.mapNoteData != wtx.mapNoteData)
{
auto tmp = wtxIn.mapNoteData;
// Ensure we keep any cached witnesses we may already have
for (const std::pair<JSOutPoint, CNoteData> nd : wtx.mapNoteData) {
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
tmp.at(nd.first).witnesses.assign(
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
}
}
// Now copy over the updated note data
wtx.mapNoteData = tmp;
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
wtx.fFromMe = wtxIn.fFromMe;
@ -796,10 +976,15 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
AssertLockHeld(cs_wallet);
bool fExisted = mapWallet.count(tx.GetTxid()) != 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)
{
CWalletTx wtx(this,tx);
if (noteData.size() > 0) {
wtx.SetNoteData(noteData);
}
// Get merkle branch if transaction was found in a block
if (pblock)
wtx.SetMerkleBranch(*pblock);
@ -828,6 +1013,14 @@ void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
if (mapWallet.count(txin.prevout.hash))
mapWallet[txin.prevout.hash].MarkDirty();
}
for (const JSDescription& jsdesc : tx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (mapNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapNullifiersToNotes[nullifier].hash)) {
mapWallet[mapNullifiersToNotes[nullifier].hash].MarkDirty();
}
}
}
}
void CWallet::EraseFromWallet(const uint256 &hash)
@ -843,6 +1036,86 @@ void CWallet::EraseFromWallet(const uint256 &hash)
}
mapNoteData_t CWallet::FindMyNotes(const CTransaction& tx) const
{
LOCK(cs_SpendingKeyStore);
uint256 hash = tx.GetTxid();
mapNoteData_t noteData;
libzcash::SpendingKey key;
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 note_pt = libzcash::NotePlaintext::decrypt(
item.second,
tx.vjoinsplit[i].ciphertexts[j],
tx.vjoinsplit[i].ephemeralKey,
hSig,
(unsigned char) j);
auto address = item.first;
// Decryptors are only cached when SpendingKeys are added
assert(GetSpendingKey(address, key));
auto note = note_pt.note(address);
JSOutPoint jsoutpt {hash, i, j};
CNoteData nd {address, note.nullifier(key)};
noteData.insert(std::make_pair(jsoutpt, nd));
break;
} catch (const std::runtime_error &) {
// Couldn't decrypt with this spending key
} 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 uint256& nullifier) const
{
{
LOCK(cs_wallet);
if (mapNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapNullifiersToNotes.at(nullifier).hash)) {
return true;
}
}
return false;
}
void CWallet::GetNoteWitnesses(std::vector<JSOutPoint> notes,
std::vector<boost::optional<ZCIncrementalWitness>>& witnesses,
uint256 &final_anchor)
{
{
LOCK(cs_wallet);
witnesses.resize(notes.size());
boost::optional<uint256> rt;
int i = 0;
for (JSOutPoint note : notes) {
if (mapWallet.count(note.hash) &&
mapWallet[note.hash].mapNoteData.count(note) &&
mapWallet[note.hash].mapNoteData[note].witnesses.size() > 0) {
witnesses[i] = mapWallet[note.hash].mapNoteData[note].witnesses.front();
if (!rt) {
rt = witnesses[i]->root();
} else {
assert(*rt == witnesses[i]->root());
}
}
i++;
}
// All returned witnesses have the same anchor
if (rt) {
final_anchor = *rt;
}
}
}
isminetype CWallet::IsMine(const CTxIn &txin) const
{
{
@ -925,7 +1198,17 @@ bool CWallet::IsMine(const CTransaction& tx) const
bool CWallet::IsFromMe(const CTransaction& tx) const
{
return (GetDebit(tx, ISMINE_ALL) > 0);
if (GetDebit(tx, ISMINE_ALL) > 0) {
return true;
}
for (const JSDescription& jsdesc : tx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (IsFromMe(nullifier)) {
return true;
}
}
}
return false;
}
CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) const
@ -964,6 +1247,22 @@ CAmount CWallet::GetChange(const CTransaction& tx) const
return nChange;
}
void CWalletTx::SetNoteData(mapNoteData_t &noteData)
{
mapNoteData.clear();
for (const std::pair<JSOutPoint, CNoteData> 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");
}
}
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;

View File

@ -7,6 +7,8 @@
#define BITCOIN_WALLET_WALLET_H
#include "amount.h"
#include "coins.h"
#include "consensus/consensus.h"
#include "key.h"
#include "keystore.h"
#include "primitives/block.h"
@ -52,6 +54,10 @@ static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWarning;
//! Largest (in bytes) free transaction we're willing to create
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
//! Size of witness cache
// Should be large enough that we can expect not to reorg beyond our cache
// unless there is some exceptional network disruption.
static const unsigned int WITNESS_CACHE_SIZE = COINBASE_MATURITY;
class CAccountingEntry;
class CBlockIndex;
@ -146,6 +152,95 @@ struct COutputEntry
int vout;
};
/** An note outpoint */
class JSOutPoint
{
public:
// Transaction hash
uint256 hash;
// Index into CTransaction.vjoinsplit
size_t js;
// Index into JSDescription fields of length ZC_NUM_JS_OUTPUTS
uint8_t n;
JSOutPoint() { SetNull(); }
JSOutPoint(uint256 h, size_t js, uint8_t n) : hash {h}, js {js}, n {n} { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(hash);
READWRITE(js);
READWRITE(n);
}
void SetNull() { hash.SetNull(); }
bool IsNull() const { return hash.IsNull(); }
friend bool operator<(const JSOutPoint& a, const JSOutPoint& b) {
return (a.hash < b.hash ||
(a.hash == b.hash && a.js < b.js) ||
(a.hash == b.hash && a.js == b.js && a.n < b.n));
}
friend bool operator==(const JSOutPoint& a, const JSOutPoint& b) {
return (a.hash == b.hash && a.js == b.js && a.n == b.n);
}
friend bool operator!=(const JSOutPoint& a, const JSOutPoint& b) {
return !(a == b);
}
std::string ToString() const;
};
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 incremental witnesses for spendable Notes.
* Beginning of the list is the most recent witness.
*/
std::list<ZCIncrementalWitness> witnesses;
CNoteData() : address(), nullifier() { }
CNoteData(libzcash::PaymentAddress a, uint256 n) : address {a}, nullifier {n} { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(address);
READWRITE(nullifier);
READWRITE(witnesses);
}
friend bool operator<(const CNoteData& a, const CNoteData& b) {
return (a.address < b.address ||
(a.address == b.address && a.nullifier < b.nullifier));
}
friend bool operator==(const CNoteData& a, const CNoteData& b) {
return (a.address == b.address && a.nullifier == b.nullifier);
}
friend bool operator!=(const CNoteData& a, const CNoteData& b) {
return !(a == b);
}
};
typedef std::map<JSOutPoint, CNoteData> mapNoteData_t;
/** A transaction with a merkle branch linking it to the block chain. */
class CMerkleTx : public CTransaction
{
@ -216,6 +311,7 @@ private:
public:
mapValue_t mapValue;
mapNoteData_t mapNoteData;
std::vector<std::pair<std::string, std::string> > vOrderForm;
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; //! time received by this node
@ -268,6 +364,7 @@ public:
{
pwallet = pwalletIn;
mapValue.clear();
mapNoteData.clear();
vOrderForm.clear();
fTimeReceivedIsTxTime = false;
nTimeReceived = 0;
@ -317,6 +414,7 @@ public:
std::vector<CMerkleTx> vUnused; //! Used to be vtxPrev
READWRITE(vUnused);
READWRITE(mapValue);
READWRITE(mapNoteData);
READWRITE(vOrderForm);
READWRITE(fTimeReceivedIsTxTime);
READWRITE(nTimeReceived);
@ -358,6 +456,8 @@ public:
MarkDirty();
}
void SetNoteData(mapNoteData_t &noteData);
//! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const;
CAmount GetCredit(const isminefilter& filter) const;
@ -461,17 +561,43 @@ private:
int64_t nLastResend;
bool fBroadcastTransactions;
template <class T>
using TxSpendMap = std::multimap<T, uint256>;
/**
* Used to keep track of spent outpoints, and
* detect and report conflicts (double-spends or
* mutated transactions where the mutant gets mined).
*/
typedef std::multimap<COutPoint, uint256> TxSpends;
typedef TxSpendMap<COutPoint> TxSpends;
TxSpends mapTxSpends;
/**
* Used to keep track of spent Notes, and
* detect and report conflicts (double-spends).
*/
typedef TxSpendMap<uint256> TxNullifiers;
TxNullifiers mapTxNullifiers;
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
public:
/*
* Size of the incremental witness cache for the notes in our wallet.
* This will always be greater than or equal to the size of the largest
* incremental witness cache in any transaction in mapWallet.
*/
int64_t nWitnessCacheSize;
protected:
void IncrementNoteWitnesses(const CBlockIndex* pindex,
const CBlock* pblock,
ZCIncrementalMerkleTree tree);
void DecrementNoteWitnesses();
private:
template <class T>
void SyncMetaData(std::pair<typename TxSpendMap<T>::iterator, typename TxSpendMap<T>::iterator>);
public:
/*
@ -525,8 +651,10 @@ public:
nLastResend = 0;
nTimeFirstKey = 0;
fBroadcastTransactions = false;
nWitnessCacheSize = 0;
}
std::map<uint256, JSOutPoint> mapNullifiersToNotes;
std::map<uint256, CWalletTx> mapWallet;
int64_t nOrderPosNext;
@ -549,6 +677,7 @@ public:
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
bool IsSpent(const uint256& hash, unsigned int n) const;
bool IsSpent(const uint256& nullifier) const;
bool IsLockedCoin(uint256 hash, unsigned int n) const;
void LockCoin(COutPoint& output);
@ -628,6 +757,7 @@ public:
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
void MarkDirty();
void UpdateNullifierNoteMap(const CWalletTx& wtx);
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);
@ -667,6 +797,13 @@ public:
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
mapNoteData_t FindMyNotes(const CTransaction& tx) const;
bool IsFromMe(const uint256& nullifier) const;
void GetNoteWitnesses(
std::vector<JSOutPoint> notes,
std::vector<boost::optional<ZCIncrementalWitness>>& witnesses,
uint256 &final_anchor);
isminetype IsMine(const CTxIn& txin) const;
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const;
@ -679,6 +816,7 @@ public:
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
void ChainTip(const CBlockIndex *pindex, const CBlock *pblock, ZCIncrementalMerkleTree tree, bool added);
void SetBestChain(const CBlockLocator& loc);
DBErrors LoadWallet(bool& fFirstRunRet);

View File

@ -162,6 +162,12 @@ bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
return Write(std::string("defaultkey"), vchPubKey);
}
bool CWalletDB::WriteWitnessCacheSize(int64_t nWitnessCacheSize)
{
nWalletDBUpdated++;
return Write(std::string("witnesscachesize"), nWitnessCacheSize);
}
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
{
return Read(std::make_pair(std::string("pool"), nPool), keypool);
@ -631,6 +637,10 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
}
else if (strType == "witnesscachesize")
{
ssValue >> pwallet->nWitnessCacheSize;
}
} catch (...)
{
return false;

View File

@ -106,6 +106,8 @@ public:
bool WriteDefaultKey(const CPubKey& vchPubKey);
bool WriteWitnessCacheSize(int64_t nWitnessCacheSize);
bool ReadPool(int64_t nPool, CKeyPool& keypool);
bool WritePool(int64_t nPool, const CKeyPool& keypool);
bool ErasePool(int64_t nPool);

View File

@ -23,7 +23,13 @@ public:
READWRITE(pk_enc);
}
friend inline bool operator<(const PaymentAddress& a, const PaymentAddress& b) { return a.a_pk < b.a_pk; }
friend inline bool operator==(const PaymentAddress& a, const PaymentAddress& b) {
return a.a_pk == b.a_pk && a.pk_enc == b.pk_enc;
}
friend inline bool operator<(const PaymentAddress& a, const PaymentAddress& b) {
return (a.a_pk < b.a_pk ||
(a.a_pk == b.a_pk && a.pk_enc < b.pk_enc));
}
};
class ViewingKey : public uint256 {

View File

@ -43,10 +43,19 @@ public:
Hash empty_root(size_t depth) {
return empty_roots.at(depth);
}
template <size_t D, typename H>
friend bool operator==(const EmptyMerkleRoots<D, H>& a,
const EmptyMerkleRoots<D, H>& b);
private:
boost::array<Hash, Depth+1> empty_roots;
};
template<size_t Depth, typename Hash>
bool operator==(const EmptyMerkleRoots<Depth, Hash>& a,
const EmptyMerkleRoots<Depth, Hash>& b) {
return a.empty_roots == b.empty_roots;
}
template<size_t Depth, typename Hash>
class IncrementalWitness;
@ -90,6 +99,10 @@ public:
return emptyroots.empty_root(Depth);
}
template <size_t D, typename H>
friend bool operator==(const IncrementalMerkleTree<D, H>& a,
const IncrementalMerkleTree<D, H>& b);
private:
static EmptyMerkleRoots<Depth, Hash> emptyroots;
boost::optional<Hash> left;
@ -104,11 +117,23 @@ private:
void wfcheck() const;
};
template<size_t Depth, typename Hash>
bool operator==(const IncrementalMerkleTree<Depth, Hash>& a,
const IncrementalMerkleTree<Depth, Hash>& b) {
return (a.emptyroots == b.emptyroots &&
a.left == b.left &&
a.right == b.right &&
a.parents == b.parents);
}
template <size_t Depth, typename Hash>
class IncrementalWitness {
friend class IncrementalMerkleTree<Depth, Hash>;
public:
// Required for Unserialize()
IncrementalWitness() {}
MerklePath path() const {
return tree.path(partial_path());
}
@ -130,6 +155,10 @@ public:
cursor_depth = tree.next_depth(filled.size());
}
template <size_t D, typename H>
friend bool operator==(const IncrementalWitness<D, H>& a,
const IncrementalWitness<D, H>& b);
private:
IncrementalMerkleTree<Depth, Hash> tree;
std::vector<Hash> filled;
@ -139,6 +168,15 @@ private:
IncrementalWitness(IncrementalMerkleTree<Depth, Hash> tree) : tree(tree) {}
};
template<size_t Depth, typename Hash>
bool operator==(const IncrementalWitness<Depth, Hash>& a,
const IncrementalWitness<Depth, Hash>& b) {
return (a.tree == b.tree &&
a.filled == b.filled &&
a.cursor == b.cursor &&
a.cursor_depth == b.cursor_depth);
}
class SHA256Compress : public uint256 {
public:
SHA256Compress() : uint256() {}

View File

@ -173,9 +173,10 @@ public:
boost::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
const uint256& rt,
bool computeProof
) {
if (!pk) {
if (computeProof && !pk) {
throw std::runtime_error("JoinSplit proving key not loaded");
}
@ -231,6 +232,10 @@ public:
out_macs[i] = PRF_pk(inputs[i].key, i, h_sig);
}
if (!computeProof) {
return ZCProof();
}
protoboard<FieldT> pb;
{
joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);

View File

@ -73,7 +73,8 @@ public:
boost::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
const uint256& rt,
bool computeProof = true
) = 0;
virtual bool verify(

View File

@ -61,6 +61,7 @@ public:
typedef boost::array<unsigned char, CLEN> Ciphertext;
typedef boost::array<unsigned char, MLEN> Plaintext;
NoteDecryption() { }
NoteDecryption(uint256 sk_enc);
Plaintext decrypt(const Ciphertext &ciphertext,
@ -68,6 +69,14 @@ public:
const uint256 &hSig,
unsigned char nonce
) const;
friend inline bool operator==(const NoteDecryption& a, const NoteDecryption& b) {
return a.sk_enc == b.sk_enc && a.pk_enc == b.pk_enc;
}
friend inline bool operator<(const NoteDecryption& a, const NoteDecryption& b) {
return (a.sk_enc < b.sk_enc ||
(a.sk_enc == b.sk_enc && a.pk_enc < b.pk_enc));
}
};
uint256 random_uint256();