Add wallet method for finding spendable notes in a CTransaction

This commit is contained in:
Jack Grigg
2016-08-24 15:50:45 +12:00
parent 5db5e42ec3
commit 02e674555e
8 changed files with 264 additions and 2 deletions

View File

@@ -16,7 +16,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

@@ -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[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,32 @@ public:
}
return false;
}
bool GetNoteDecryptor(const libzcash::PaymentAddress &address, ZCNoteDecryption &decOut) const
{
{
LOCK(cs_KeyStore);
NoteDecryptorMap::const_iterator mi = mapNoteDecryptors.find(address);
if (mi != mapNoteDecryptors.end())
{
decOut = mi->second;
return true;
}
}
return false;
}
void GetNoteDecryptors(std::set<NoteDecryptorMap::value_type> &setDec) const
{
setDec.clear();
{
LOCK(cs_SpendingKeyStore);
NoteDecryptorMap::const_iterator mi = mapNoteDecryptors.begin();
while (mi != mapNoteDecryptors.end())
{
setDec.insert(*mi);
mi++;
}
}
}
void GetPaymentAddresses(std::set<libzcash::PaymentAddress> &setAddress) const
{
setAddress.clear();

View File

@@ -0,0 +1,98 @@
#include <gtest/gtest.h>
#include <sodium.h>
#include "base58.h"
#include "chainparams.h"
#include "random.h"
#include "wallet/wallet.h"
#include "zcash/JoinSplit.hpp"
#include "zcash/Note.hpp"
#include "zcash/NoteEncryption.hpp"
ZCJoinSplit* params = ZCJoinSplit::Unopened();
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(), // dummy output
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());
}
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(1, noteMap.size());
JSOutPoint jsoutpt {wtx.GetTxid(), 0, 1};
CNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}

View File

@@ -10,6 +10,7 @@
#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 +18,7 @@
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
#include "zcash/Note.hpp"
#include <assert.h>
@@ -57,6 +59,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));
@@ -843,6 +850,42 @@ void CWallet::EraseFromWallet(const uint256 &hash)
}
mapNoteData_t CWallet::FindMyNotes(const CTransaction& tx) const
{
uint256 hash = tx.GetTxid();
mapNoteData_t noteData;
std::set<NoteDecryptorMap::value_type> decryptors;
GetNoteDecryptors(decryptors);
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 : decryptors) {
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::exception &) {
// Couldn't decrypt with this spending key
}
}
}
}
return noteData;
}
isminetype CWallet::IsMine(const CTxIn &txin) const
{
{

View File

@@ -146,6 +146,88 @@ 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;
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);
}
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
{
@@ -667,6 +749,8 @@ public:
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
mapNoteData_t FindMyNotes(const CTransaction& tx) const;
isminetype IsMine(const CTxIn& txin) const;
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const;

View File

@@ -23,6 +23,7 @@ public:
READWRITE(pk_enc);
}
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; }
};

View File

@@ -61,6 +61,8 @@ public:
typedef boost::array<unsigned char, CLEN> Ciphertext;
typedef boost::array<unsigned char, MLEN> Plaintext;
// Unused default constructor to make allocators happy
NoteDecryption() { }
NoteDecryption(uint256 sk_enc);
Plaintext decrypt(const Ciphertext &ciphertext,
@@ -68,6 +70,8 @@ public:
const uint256 &hSig,
unsigned char nonce
) const;
friend inline bool operator<(const NoteDecryption& a, const NoteDecryption& b) { return a.pk_enc < b.pk_enc; }
};
uint256 random_uint256();