Hush Full Node software. We were censored from Github, this is where all development happens now. https://hush.is
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

475 lines
15 KiB

#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.GetHash(), 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.GetHash(), 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.GetHash(), 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.GetHash(), 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.GetHash();
auto hash3 = wtx3.GetHash();
// 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.GetHash(), 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.GetHash(), 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.GetHash(), 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.GetHash(), 0, 0};
JSOutPoint jsoutpt2 {wtx.GetHash(), 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]);
8 years ago
// 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.GetHash(), 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.GetHash(), 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);
}
}