Add wallet method for finding spendable notes in a CTransaction
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
98
src/wallet/gtest/test_wallet.cpp
Normal file
98
src/wallet/gtest/test_wallet.cpp
Normal 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]);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user