Add wallet method for finding spendable notes in a CTransaction
This commit is contained in:
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;
|
||||
|
||||
Reference in New Issue
Block a user