Merge branch 'danger' into sietch_dynamic

This commit is contained in:
Duke Leto
2020-07-22 09:39:48 -04:00
345 changed files with 7701 additions and 37338 deletions

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@@ -45,7 +46,6 @@
#include <string>
#include <thread>
#include "paymentdisclosuredb.h"
int32_t komodo_blockheight(uint256 hash);
using namespace libzcash;
@@ -74,19 +74,17 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee,
UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
{
if (fee < 0 || fee > MAX_MONEY) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
}
if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
if (utxoInputs.empty() && saplingNoteInputs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
}
@@ -94,14 +92,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
}
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
if (sproutNoteInputs.size() > 0 && builder) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
}
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
@@ -132,9 +122,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
// Lock UTXOs
lock_utxos();
lock_notes();
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
@@ -209,31 +196,16 @@ void AsyncRPCOperation_mergetoaddress::main()
unlock_utxos(); // clean up
unlock_notes(); // clean up
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size() > 0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
// Notes:
// 1. #1359 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
// 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid.
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
bool AsyncRPCOperation_mergetoaddress::main_impl()
{
assert(isToTaddr_ != isToZaddr_);
bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
bool isPureTaddrOnlyTx = (saplingNoteInputs_.empty() && isToTaddr_);
CAmount minersFee = fee_;
size_t numInputs = utxoInputs_.size();
@@ -258,9 +230,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
}
CAmount z_inputs_total = 0;
for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
z_inputs_total += std::get<2>(t);
@@ -311,7 +280,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
/**
* SCENARIO #0
*
* Sprout not involved, so we just use the TransactionBuilder and we're done.
* Only sapling involved, so we just use the TransactionBuilder and we're done.
*
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
*/
@@ -428,7 +397,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
* END SCENARIO #0
*/
/**
* SCENARIO #1
*
@@ -446,337 +414,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
* END SCENARIO #1
*/
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(tx_);
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
mtx.joinSplitPubKey = joinSplitPubKey_;
tx_ = CTransaction(mtx);
std::string hexMemo = std::get<1>(recipient_);
/**
* SCENARIO #2
*
* taddrs -> zaddr
*
* We only need a single JoinSplit.
*/
if (sproutNoteInputs_.empty() && isToZaddr_) {
// Create JoinSplit to target z-addr.
MergeToAddressJSInfo info;
info.vpub_old = sendAmount;
info.vpub_new = 0;
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_), sendAmount);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
UniValue obj(UniValue::VOBJ);
obj = perform_joinsplit(info);
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #2
*/
// Copy zinputs to more flexible containers
std::deque<MergeToAddressInputSproutNote> zInputsDeque;
for (const auto& o : sproutNoteInputs_) {
zInputsDeque.push_back(o);
}
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
{
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : sproutNoteInputs_) {
JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = {jso};
uint256 inputAnchor;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
jsopWitnessAnchorMap[jso.ToString()] = MergeToAddressWitnessAnchorData{vInputWitnesses[0], inputAnchor};
}
}
/**
* SCENARIO #3
*
* zaddrs -> zaddr
* taddrs ->
*
* zaddrs ->
* taddrs -> taddr
*
* Send to zaddr by chaining JoinSplits together and immediately consuming any change
* Send to taddr by creating dummy z outputs and accumulating value in a change note
* which is used to set vpub_new in the last chained joinsplit.
*/
UniValue obj(UniValue::VOBJ);
CAmount jsChange = 0; // this is updated after each joinsplit
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
bool vpubOldProcessed = false; // updated when vpub_old for taddr inputs is set in first joinsplit
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
// At this point, we are guaranteed to have at least one input note.
// Use address of first input note as the temporary change address.
SproutSpendingKey changeKey = std::get<3>(zInputsDeque.front());
SproutPaymentAddress changeAddress = changeKey.address();
CAmount vpubOldTarget = 0;
CAmount vpubNewTarget = 0;
if (isToTaddr_) {
vpubNewTarget = z_inputs_total;
} else {
if (utxoInputs_.empty()) {
vpubNewTarget = minersFee;
} else {
vpubOldTarget = t_inputs_total - minersFee;
}
}
// Keep track of treestate within this transaction
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
std::vector<uint256> previousCommitments;
while (!vpubNewProcessed) {
MergeToAddressJSInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
// Set vpub_old in the first joinsplit
if (!vpubOldProcessed) {
if (t_inputs_total < vpubOldTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient transparent funds for vpub_old %s (miners fee %s, taddr inputs %s)",
FormatMoney(vpubOldTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
}
info.vpub_old += vpubOldTarget; // funds flowing from public pool
vpubOldProcessed = true;
}
CAmount jsInputValue = 0;
uint256 jsAnchor;
std::vector<boost::optional<SproutWitness>> witnesses;
JSDescription prevJoinSplit;
// Keep track of previous JoinSplit and its commitments
if (tx_.vjoinsplit.size() > 0) {
prevJoinSplit = tx_.vjoinsplit.back();
}
// If there is no change, the chain has terminated so we can reset the tracked treestate.
if (jsChange == 0 && tx_.vjoinsplit.size() > 0) {
intermediates.clear();
previousCommitments.clear();
}
//
// Consume change as the first input of the JoinSplit.
//
if (jsChange > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
// Update tree state with previous joinsplit
SproutMerkleTree tree;
auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
}
assert(changeOutputIndex != -1);
boost::optional<SproutWitness> changeWitness;
int n = 0;
for (const uint256& commitment : prevJoinSplit.commitments) {
tree.append(commitment);
previousCommitments.push_back(commitment);
if (!changeWitness && changeOutputIndex == n++) {
changeWitness = tree.witness();
} else if (changeWitness) {
changeWitness.get().append(commitment);
}
}
if (changeWitness) {
witnesses.push_back(changeWitness);
}
jsAnchor = tree.root();
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
// Decrypt the change note's ciphertext to retrieve some data we need
ZCNoteDecryption decryptor(changeKey.receiving_key());
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor,
prevJoinSplit.ciphertexts[changeOutputIndex],
prevJoinSplit.ephemeralKey,
hSig,
(unsigned char)changeOutputIndex);
SproutNote note = plaintext.note(changeAddress);
info.notes.push_back(note);
info.zkeys.push_back(changeKey);
jsInputValue += plaintext.value();
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
getId(),
FormatMoney(plaintext.value()));
} catch (const std::exception& e) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
}
}
//
// Consume spendable non-change notes
//
std::vector<SproutNote> vInputNotes;
std::vector<SproutSpendingKey> vInputZKeys;
std::vector<JSOutPoint> vOutPoints;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
uint256 inputAnchor;
int numInputsNeeded = (jsChange > 0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
MergeToAddressInputSproutNote t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t);
SproutSpendingKey zkey = std::get<3>(t);
zInputsDeque.pop_front();
MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()];
vInputWitnesses.push_back(wad.witness);
if (inputAnchor.IsNull()) {
inputAnchor = wad.anchor;
} else if (inputAnchor != wad.anchor) {
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
}
vOutPoints.push_back(jso);
vInputNotes.push_back(note);
vInputZKeys.push_back(zkey);
jsInputValue += noteFunds;
int wtxHeight = -1;
int wtxDepth = -1;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
// Zero confirmation notes belong to transactions which have not yet been mined
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
}
wtxHeight = komodo_blockheight(wtx.hashBlock);
wtxDepth = wtx.GetDepthInMainChain();
}
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
getId(),
jso.hash.ToString().substr(0, 10),
jso.js,
int(jso.n), // uint8_t
FormatMoney(noteFunds),
wtxHeight,
wtxDepth);
}
// Add history of previous commitments to witness
if (vInputNotes.size() > 0) {
if (vInputWitnesses.size() == 0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
}
for (auto& optionalWitness : vInputWitnesses) {
if (!optionalWitness) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
}
SproutWitness w = *optionalWitness; // could use .get();
if (jsChange > 0) {
for (const uint256& commitment : previousCommitments) {
w.append(commitment);
}
if (jsAnchor != w.root()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
}
}
witnesses.push_back(w);
}
// The jsAnchor is null if this JoinSplit is at the start of a new chain
if (jsAnchor.IsNull()) {
jsAnchor = inputAnchor;
}
// Add spendable notes as inputs
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
std::copy(vInputZKeys.begin(), vInputZKeys.end(), std::back_inserter(info.zkeys));
}
// Accumulate change
jsChange = jsInputValue + info.vpub_old;
// Set vpub_new in the last joinsplit (when there are no more notes to spend)
if (zInputsDeque.empty()) {
assert(!vpubNewProcessed);
if (jsInputValue < vpubNewTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr inputs %s)",
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
}
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
vpubNewProcessed = true;
jsChange -= vpubNewTarget;
// If we are merging to a t-addr, there should be no change
if (isToTaddr_) assert(jsChange == 0);
}
// create dummy output
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
// create output for any change
if (jsChange > 0) {
std::string outputType = "change";
auto jso = JSOutput(changeAddress, jsChange);
// If this is the final output, set the target and memo
if (isToZaddr_ && vpubNewProcessed) {
outputType = "target";
jso.addr = boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_);
if (!hexMemo.empty()) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
}
info.vjsout.push_back(jso);
LogPrint("zrpcunsafe", "%s: generating note for %s (amount=%s)\n",
getId(),
outputType,
FormatMoney(jsChange));
}
obj = perform_joinsplit(info, witnesses, jsAnchor);
if (jsChange > 0) {
changeOutputIndex = mta_find_output(obj, 1);
}
}
// Sanity check in case changes to code block above exits loop by invoking 'break'
assert(zInputsDeque.size() == 0);
assert(vpubNewProcessed);
sign_send_raw_transaction(obj);
return true;
return false;
}
@@ -848,191 +486,6 @@ void AsyncRPCOperation_mergetoaddress::sign_send_raw_transaction(UniValue obj)
tx_ = tx;
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info)
{
std::vector<boost::optional<SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& outPoints)
{
std::vector<boost::optional<SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor)
{
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
if (witnesses.size() != info.notes.size()) {
throw runtime_error("number of notes and witnesses do not match");
}
if (info.notes.size() != info.zkeys.size()) {
throw runtime_error("number of notes and spending keys do not match");
}
for (size_t i = 0; i < witnesses.size(); i++) {
if (!witnesses[i]) {
throw runtime_error("joinsplit input could not be found in tree");
}
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], info.zkeys[i]));
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value));
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_) == 0)) {
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()) == 0)) {
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char)0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char)0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
std::array<unsigned char, ZC_MEMO_SIZE> AsyncRPCOperation_mergetoaddress::get_memo_from_hex_string(std::string s)
{
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0x00}};
@@ -1099,9 +552,6 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/
void AsyncRPCOperation_mergetoaddress::lock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : sproutNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
@@ -1112,9 +562,6 @@ void AsyncRPCOperation_mergetoaddress::lock_notes() {
*/
void AsyncRPCOperation_mergetoaddress::unlock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : sproutNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}

View File

@@ -1,4 +1,6 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -22,7 +24,6 @@
#include "amount.h"
#include "asyncrpcoperation.h"
#include "paymentdisclosure.h"
#include "primitives/transaction.h"
#include "transaction_builder.h"
#include "wallet.h"
@@ -43,29 +44,11 @@ using namespace libzcash;
// Input UTXO is a tuple of txid, vout, amount, script
typedef std::tuple<COutPoint, CAmount, CScript> MergeToAddressInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note, amount, spending key
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SproutSpendingKey> MergeToAddressInputSproutNote;
typedef std::tuple<SaplingOutPoint, SaplingNote, CAmount, SaplingExpandedSpendingKey> MergeToAddressInputSaplingNote;
// A recipient is a tuple of address, memo (optional if zaddr)
typedef std::tuple<std::string, std::string> MergeToAddressRecipient;
// Package of info which is passed to perform_joinsplit methods.
struct MergeToAddressJSInfo {
std::vector<JSInput> vjsin;
std::vector<JSOutput> vjsout;
std::vector<SproutNote> notes;
std::vector<SproutSpendingKey> zkeys;
CAmount vpub_old = 0;
CAmount vpub_new = 0;
};
// A struct to help us track the witness and anchor for a given JSOutPoint
struct MergeToAddressWitnessAnchorData {
boost::optional<SproutWitness> witness;
uint256 anchor;
};
class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
{
@@ -74,7 +57,6 @@ public:
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
@@ -93,8 +75,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
@@ -113,11 +93,7 @@ private:
uint256 joinSplitPubKey_;
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES];
// The key is the result string from calling JSOutPoint::ToString()
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
std::vector<MergeToAddressInputUTXO> utxoInputs_;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs_;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;
TransactionBuilder builder_;
@@ -126,18 +102,6 @@ private:
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
bool main_impl();
// JoinSplit without any input notes to spend
UniValue perform_joinsplit(MergeToAddressJSInfo&);
// JoinSplit with input notes to spend (JSOutPoints))
UniValue perform_joinsplit(MergeToAddressJSInfo&, std::vector<JSOutPoint>&);
// JoinSplit where you have the witnesses and anchor
UniValue perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor);
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
void lock_utxos();
@@ -148,8 +112,6 @@ private:
void unlock_notes();
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
@@ -183,24 +145,6 @@ public:
return delegate->main_impl();
}
UniValue perform_joinsplit(MergeToAddressJSInfo& info)
{
return delegate->perform_joinsplit(info);
}
UniValue perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& v)
{
return delegate->perform_joinsplit(info, v);
}
UniValue perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor)
{
return delegate->perform_joinsplit(info, witnesses, anchor);
}
void sign_send_raw_transaction(UniValue obj)
{
delegate->sign_send_raw_transaction(obj);

View File

@@ -0,0 +1,279 @@
// Copyright (c) 2019-2020 The Hush developers
// Copyright (c) 2019 CryptoForge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "assert.h"
#include "boost/variant/static_visitor.hpp"
#include "asyncrpcoperation_saplingconsolidation.h"
#include "init.h"
#include "key_io.h"
#include "rpc/protocol.h"
#include "random.h"
#include "sync.h"
#include "tinyformat.h"
#include "transaction_builder.h"
#include "util.h"
#include "utilmoneystr.h"
#include "wallet.h"
CAmount fConsolidationTxFee = DEFAULT_CONSOLIDATION_FEE;
bool fConsolidationMapUsed = false;
const int CONSOLIDATION_EXPIRY_DELTA = 15;
extern string randomSietchZaddr();
AsyncRPCOperation_saplingconsolidation::AsyncRPCOperation_saplingconsolidation(int targetHeight) : targetHeight_(targetHeight) {}
AsyncRPCOperation_saplingconsolidation::~AsyncRPCOperation_saplingconsolidation() {}
void AsyncRPCOperation_saplingconsolidation::main() {
if (isCancelled())
return;
set_state(OperationStatus::EXECUTING);
start_execution_clock();
bool success = false;
try {
success = main_impl();
} catch (const UniValue& objError) {
int code = find_value(objError, "code").get_int();
std::string message = find_value(objError, "message").get_str();
set_error_code(code);
set_error_message(message);
} catch (const runtime_error& e) {
set_error_code(-1);
set_error_message("runtime error: " + string(e.what()));
} catch (const logic_error& e) {
set_error_code(-1);
set_error_message("logic error: " + string(e.what()));
} catch (const exception& e) {
set_error_code(-1);
set_error_message("general exception: " + string(e.what()));
} catch (...) {
set_error_code(-2);
set_error_message("unknown error");
}
stop_execution_clock();
if (success) {
set_state(OperationStatus::SUCCESS);
} else {
set_state(OperationStatus::FAILED);
}
std::string s = strprintf("%s: Sapling Consolidation transaction created. (status=%s", getId(), getStateAsString());
if (success) {
s += strprintf(", success)\n");
} else {
s += strprintf(", error=%s)\n", getErrorMessage());
}
LogPrintf("%s", s);
}
bool AsyncRPCOperation_saplingconsolidation::main_impl() {
bool status=true;
auto opid=getId();
LogPrint("zrpcunsafe", "%s: Beginning AsyncRPCOperation_saplingconsolidation.\n", opid);
auto consensusParams = Params().GetConsensus();
auto nextActivationHeight = NextActivationHeight(targetHeight_, consensusParams);
if (nextActivationHeight && targetHeight_ + CONSOLIDATION_EXPIRY_DELTA >= nextActivationHeight.get()) {
LogPrint("zrpcunsafe", "%s: Consolidation txs would be created before a NU activation but may expire after. Skipping this round.\n",opid);
setConsolidationResult(0, 0, std::vector<std::string>());
return status;
}
std::vector<SaplingNoteEntry> saplingEntries;
std::set<libzcash::SaplingPaymentAddress> addresses;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
// an anchor at height N-10 for each SpendDescription
// Consider, should notes be sorted?
pwalletMain->GetFilteredNotes(saplingEntries, "", 11);
if(saplingEntries.size() == 0) {
LogPrint("zrpcunsafe", "%s: Nothing to consolidate, done.\n",opid);
return true;
}
if (fConsolidationMapUsed) {
const vector<string>& v = mapMultiArgs["-consolidatesaplingaddress"];
for(int i = 0; i < v.size(); i++) {
auto zAddress = DecodePaymentAddress(v[i]);
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
libzcash::SaplingPaymentAddress saplingAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
addresses.insert(saplingAddress);
} else {
LogPrint("zrpcunsafe", "%s: Invalid zaddr, exiting\n", opid);
return false;
}
}
} else {
pwalletMain->GetSaplingPaymentAddresses(addresses);
}
}
int numTxCreated = 0;
std::vector<std::string> consolidationTxIds;
CAmount amountConsolidated = 0;
CCoinsViewCache coinsView(pcoinsTip);
for (auto addr : addresses) {
libzcash::SaplingExtendedSpendingKey extsk;
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
std::vector<SaplingNoteEntry> fromNotes;
CAmount amountToSend = 0;
// max of 8 zins means the tx cannot reduce the anonset,
// since there will be 8 zins and 8 zouts at worst case
// This also helps reduce ztx creation time
int maxQuantity = rand() % 8 + 1;
for (const SaplingNoteEntry& saplingEntry : saplingEntries) {
libzcash::SaplingIncomingViewingKey ivk;
pwalletMain->GetSaplingIncomingViewingKey(boost::get<libzcash::SaplingPaymentAddress>(saplingEntry.address), ivk);
//Select Notes from that same address we will be sending to.
if (ivk == extsk.expsk.full_viewing_key().in_viewing_key()) {
amountToSend += CAmount(saplingEntry.note.value());
fromNotes.push_back(saplingEntry);
}
//Only use a randomly determined number of notes
if (fromNotes.size() >= maxQuantity)
break;
}
// minimum required
// We use 3 so that addresses can spent one zutxo and still have another zutxo to use while that
// tx is confirming
int minQuantity = 3;
if (fromNotes.size() < minQuantity)
continue;
amountConsolidated += amountToSend;
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain);
//builder.SetExpiryHeight(targetHeight_ + CONSOLIDATION_EXPIRY_DELTA);
auto actualAmountToSend = amountToSend < fConsolidationTxFee ? 0 : amountToSend - fConsolidationTxFee;
LogPrint("zrpcunsafe", "%s: Beginning to create transaction with Sapling output amount=%s\n", opid, FormatMoney(actualAmountToSend));
// Select Sapling notes
std::vector<SaplingOutPoint> ops;
std::vector<libzcash::SaplingNote> notes;
for (auto fromNote : fromNotes) {
ops.push_back(fromNote.op);
notes.push_back(fromNote.note);
}
// Fetch Sapling anchor and witnesses
uint256 anchor;
std::vector<boost::optional<SaplingWitness>> witnesses;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
LogPrint("zrpcunsafe", "%s: Fetching note witnesses\n", opid);
pwalletMain->GetSaplingNoteWitnesses(ops, witnesses, anchor);
}
// Add Sapling spends
for (size_t i = 0; i < notes.size(); i++) {
if (!witnesses[i]) {
LogPrint("zrpcunsafe", "%s: Missing Witnesses. Stopping.\n", opid);
status=false;
break;
}
builder.AddSaplingSpend(extsk.expsk, notes[i], anchor, witnesses[i].get());
LogPrint("zrpcunsafe", "%s: Added consolidation input %d\n", opid, i);
}
CAmount thisTxFee = amountToSend < fConsolidationTxFee ? 0 : fConsolidationTxFee;
LogPrint("zrpcunsafe", "%s: Using fee=%d\n", opid, thisTxFee);
builder.SetFee(thisTxFee);
// Add the actual consolidation tx
builder.AddSaplingOutput(extsk.expsk.ovk, addr, actualAmountToSend);
LogPrint("zrpcunsafe", "%s: Added consolidation output %s with amount=%li\n", opid, addr.GetHash().ToString().c_str(), actualAmountToSend);
// Add sietch zouts
int MIN_ZOUTS = 7;
for(size_t i = 0; i < MIN_ZOUTS; i++) {
// In Privacy Zdust We Trust -- Duke
string zdust = randomSietchZaddr();
auto zaddr = DecodePaymentAddress(zdust);
if (IsValidPaymentAddress(zaddr)) {
CAmount amount=0;
auto sietchZoutput = boost::get<libzcash::SaplingPaymentAddress>(zaddr);
LogPrint("zrpcunsafe", "%s: Adding Sietch zdust output %d %s amount=%li\n", opid, i, zdust, amount);
// actually add our sietch zoutput, the new way
builder.AddSaplingOutput(extsk.expsk.ovk, sietchZoutput, amount);
} else {
LogPrint("zrpcunsafe", "%s: Invalid payment address %s! Stopping.\n", opid, zdust);
status = false;
break;
}
}
LogPrint("zrpcunsafe", "%s: Done adding %d sietch zouts\n", opid, MIN_ZOUTS);
//CTransaction tx = builder.Build();
auto maybe_tx = builder.Build();
if (!maybe_tx) {
LogPrint("zrpcunsafe", "%s: Failed to build transaction.\n",opid);
status=false;
break;
}
CTransaction tx = maybe_tx.get();
if (isCancelled()) {
LogPrint("zrpcunsafe", "%s: Canceled. Stopping.\n", opid);
status=false;
break;
}
if(pwalletMain->CommitConsolidationTx(tx)) {
LogPrint("zrpcunsafe", "%s: Committed consolidation transaction with txid=%s\n",opid, tx.GetHash().ToString());
amountConsolidated += actualAmountToSend;
consolidationTxIds.push_back(tx.GetHash().ToString());
numTxCreated++;
} else {
LogPrint("zrpcunsafe", "%s: Consolidation transaction FAILED in CommitTransaction, txid=%s\n",opid , tx.GetHash().ToString());
setConsolidationResult(numTxCreated, amountConsolidated, consolidationTxIds);
status = false;
break;
}
}
}
LogPrint("zrpcunsafe", "%s: Created %d transactions with total Sapling output amount=%s,status=%d\n",opid , numTxCreated, FormatMoney(amountConsolidated), (int)status);
setConsolidationResult(numTxCreated, amountConsolidated, consolidationTxIds);
return status;
}
void AsyncRPCOperation_saplingconsolidation::setConsolidationResult(int numTxCreated, const CAmount& amountConsolidated, const std::vector<std::string>& consolidationTxIds) {
UniValue res(UniValue::VOBJ);
res.push_back(Pair("num_tx_created", numTxCreated));
res.push_back(Pair("amount_consolidated", FormatMoney(amountConsolidated)));
UniValue txIds(UniValue::VARR);
for (const std::string& txId : consolidationTxIds) {
txIds.push_back(txId);
}
res.push_back(Pair("consolidation_txids", txIds));
set_result(res);
}
void AsyncRPCOperation_saplingconsolidation::cancel() {
set_state(OperationStatus::CANCELLED);
}
UniValue AsyncRPCOperation_saplingconsolidation::getStatus() const {
UniValue v = AsyncRPCOperation::getStatus();
UniValue obj = v.get_obj();
obj.push_back(Pair("method", "saplingconsolidation"));
obj.push_back(Pair("target_height", targetHeight_));
return obj;
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2020 The Hush developers
// Copyright (c) 2019 CryptoForge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "amount.h"
#include "asyncrpcoperation.h"
#include "univalue.h"
#include "zcash/Address.hpp"
#include "zcash/zip32.h"
//Default fee used for consolidation transactions, in puposhis
static const CAmount DEFAULT_CONSOLIDATION_FEE = 10000;
extern CAmount fConsolidationTxFee;
extern bool fConsolidationMapUsed;
class AsyncRPCOperation_saplingconsolidation : public AsyncRPCOperation
{
public:
AsyncRPCOperation_saplingconsolidation(int targetHeight);
virtual ~AsyncRPCOperation_saplingconsolidation();
// We don't want to be copied or moved around
AsyncRPCOperation_saplingconsolidation(AsyncRPCOperation_saplingconsolidation const&) = delete; // Copy construct
AsyncRPCOperation_saplingconsolidation(AsyncRPCOperation_saplingconsolidation&&) = delete; // Move construct
AsyncRPCOperation_saplingconsolidation& operator=(AsyncRPCOperation_saplingconsolidation const&) = delete; // Copy assign
AsyncRPCOperation_saplingconsolidation& operator=(AsyncRPCOperation_saplingconsolidation&&) = delete; // Move assign
virtual void main();
virtual void cancel();
virtual UniValue getStatus() const;
private:
int targetHeight_;
bool main_impl();
void setConsolidationResult(int numTxCreated, const CAmount& amountConsolidated, const std::vector<std::string>& consolidationTxIds);
};

View File

@@ -1,7 +1,7 @@
// Copyright (c) 2016 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@@ -49,7 +49,6 @@
#include <thread>
#include <string>
#include "paymentdisclosuredb.h"
#include <boost/optional/optional_io.hpp>
using namespace libzcash;
@@ -141,10 +140,6 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
} else {
LogPrint("zrpc", "%s: z_sendmany initialized\n", getId());
}
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() {
@@ -211,25 +206,10 @@ void AsyncRPCOperation_sendmany::main() {
s += strprintf(", error=%s)\n", getErrorMessage());
}
LogPrintf("%s",s);
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
// Notes:
// 1. #1159 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
// 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid.
// 2. #1360 Note selection is not optimal
// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them
bool AsyncRPCOperation_sendmany::main_impl() {
@@ -265,18 +245,12 @@ bool AsyncRPCOperation_sendmany::main_impl() {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
}
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty());
CAmount t_inputs_total = 0;
for (SendManyInputUTXO & t : t_inputs_) {
t_inputs_total += std::get<2>(t);
}
CAmount z_inputs_total = 0;
for (SendManyInputJSOP & t : z_sprout_inputs_) {
z_inputs_total += std::get<2>(t);
}
for (auto t : z_sapling_inputs_) {
z_inputs_total += t.note.value();
}
@@ -566,430 +540,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
* END SCENARIO #0
*/
// Grab the current consensus branch ID
{
LOCK(cs_main);
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
}
/**
* SCENARIO #1
*
* taddr -> taddrs
*
* There are no zaddrs or joinsplits involved.
*/
if (isPureTaddrOnlyTx) {
add_taddr_outputs_to_tx();
CAmount funds = selectedUTXOAmount;
CAmount fundsSpent = t_outputs_total + minersFee;
CAmount change = funds - fundsSpent;
if (change > 0) {
add_taddr_change_output_to_tx(0,change);
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
getId(),
FormatMoney(change)
);
}
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("rawtxn", EncodeHexTx(tx_)));
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #1
*/
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(tx_);
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
mtx.joinSplitPubKey = joinSplitPubKey_;
//if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP)
if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) )
mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777
else
mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast();
tx_ = CTransaction(mtx);
// Copy zinputs and zoutputs to more flexible containers
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
CAmount tmp = 0;
for (auto o : z_sprout_inputs_) {
zInputsDeque.push_back(o);
tmp += std::get<2>(o);
if (tmp >= targetAmount) {
break;
}
}
std::deque<SendManyRecipient> zOutputsDeque;
for (auto o : z_outputs_) {
zOutputsDeque.push_back(o);
}
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
if (z_sprout_inputs_.size() > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : z_sprout_inputs_) {
JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = { jso };
uint256 inputAnchor;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor };
}
}
/**
* SCENARIO #2
*
* taddr -> taddrs
* -> zaddrs
*
* Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. TODO: Do they?
* Local wallet rule does not allow any change when sending coinbase utxos
* since there is currently no way to specify a change address and we don't
* want users accidentally sending excess funds to a recipient.
*/
if (isfromtaddr_) {
add_taddr_outputs_to_tx();
CAmount funds = selectedUTXOAmount;
CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total;
CAmount change = funds - fundsSpent;
if (change > 0) {
if (selectedUTXOCoinbase) {
assert(isSingleZaddrOutput);
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
"Change %s not allowed. When shielding coinbase funds, the wallet does not "
"allow any change as there is currently no way to specify a change address "
"in z_sendmany.", FormatMoney(change)));
} else {
CBitcoinAddress ba = CBitcoinAddress(fromtaddr_);
add_taddr_change_output_to_tx(&ba,change);
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
getId(),
FormatMoney(change)
);
}
}
// Create joinsplits, where each output represents a zaddr recipient.
UniValue obj(UniValue::VOBJ);
while (zOutputsDeque.size() > 0) {
AsyncJoinSplitInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
int n = 0;
while (n++<ZC_NUM_JS_OUTPUTS && zOutputsDeque.size() > 0) {
SendManyRecipient smr = zOutputsDeque.front();
std::string address = std::get<0>(smr);
CAmount value = std::get<1>(smr);
std::string hexMemo = std::get<2>(smr);
zOutputsDeque.pop_front();
PaymentAddress pa = DecodePaymentAddress(address);
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
// Funds are removed from the value pool and enter the private pool
info.vpub_old += value;
}
obj = perform_joinsplit(info);
}
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #2
*/
/**
* SCENARIO #3
*
* zaddr -> taddrs
* -> zaddrs
*
* Send to zaddrs by chaining JoinSplits together and immediately consuming any change
* Send to taddrs by creating dummy z outputs and accumulating value in a change note
* which is used to set vpub_new in the last chained joinsplit.
*/
UniValue obj(UniValue::VOBJ);
CAmount jsChange = 0; // this is updated after each joinsplit
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
CAmount vpubNewTarget = minersFee;
if (t_outputs_total > 0) {
add_taddr_outputs_to_tx();
vpubNewTarget += t_outputs_total;
}
// Keep track of treestate within this transaction
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
std::vector<uint256> previousCommitments;
while (!vpubNewProcessed) {
AsyncJoinSplitInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
CAmount jsInputValue = 0;
uint256 jsAnchor;
std::vector<boost::optional<SproutWitness>> witnesses;
JSDescription prevJoinSplit;
// Keep track of previous JoinSplit and its commitments
if (tx_.vjoinsplit.size() > 0) {
prevJoinSplit = tx_.vjoinsplit.back();
}
// If there is no change, the chain has terminated so we can reset the tracked treestate.
if (jsChange==0 && tx_.vjoinsplit.size() > 0) {
intermediates.clear();
previousCommitments.clear();
}
//
// Consume change as the first input of the JoinSplit.
//
if (jsChange > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
// Update tree state with previous joinsplit
SproutMerkleTree tree;
auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
}
assert(changeOutputIndex != -1);
boost::optional<SproutWitness> changeWitness;
int n = 0;
for (const uint256& commitment : prevJoinSplit.commitments) {
tree.append(commitment);
previousCommitments.push_back(commitment);
if (!changeWitness && changeOutputIndex == n++) {
changeWitness = tree.witness();
} else if (changeWitness) {
changeWitness.get().append(commitment);
}
}
if (changeWitness) {
witnesses.push_back(changeWitness);
}
jsAnchor = tree.root();
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
// Decrypt the change note's ciphertext to retrieve some data we need
ZCNoteDecryption decryptor(boost::get<libzcash::SproutSpendingKey>(spendingkey_).receiving_key());
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor,
prevJoinSplit.ciphertexts[changeOutputIndex],
prevJoinSplit.ephemeralKey,
hSig,
(unsigned char) changeOutputIndex);
SproutNote note = plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_));
info.notes.push_back(note);
jsInputValue += plaintext.value();
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
getId(),
FormatMoney(plaintext.value())
);
} catch (const std::exception& e) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
}
}
//
// Consume spendable non-change notes
//
std::vector<SproutNote> vInputNotes;
std::vector<JSOutPoint> vOutPoints;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
uint256 inputAnchor;
int numInputsNeeded = (jsChange>0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
SendManyInputJSOP t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t);
zInputsDeque.pop_front();
WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ];
vInputWitnesses.push_back(wad.witness);
if (inputAnchor.IsNull()) {
inputAnchor = wad.anchor;
} else if (inputAnchor != wad.anchor) {
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
}
vOutPoints.push_back(jso);
vInputNotes.push_back(note);
jsInputValue += noteFunds;
int wtxHeight = -1;
int wtxDepth = -1;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
// Zero-confirmation notes belong to transactions which have not yet been mined
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
}
wtxHeight = komodo_blockheight(wtx.hashBlock);
wtxDepth = wtx.GetDepthInMainChain();
}
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
getId(),
jso.hash.ToString().substr(0, 10),
jso.js,
int(jso.n), // uint8_t
FormatMoney(noteFunds),
wtxHeight,
wtxDepth
);
}
// Add history of previous commitments to witness
if (vInputNotes.size() > 0) {
if (vInputWitnesses.size()==0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
}
for (auto & optionalWitness : vInputWitnesses) {
if (!optionalWitness) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
}
SproutWitness w = *optionalWitness; // could use .get();
if (jsChange > 0) {
for (const uint256& commitment : previousCommitments) {
w.append(commitment);
}
if (jsAnchor != w.root()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
}
}
witnesses.push_back(w);
}
// The jsAnchor is null if this JoinSplit is at the start of a new chain
if (jsAnchor.IsNull()) {
jsAnchor = inputAnchor;
}
// Add spendable notes as inputs
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
}
// Find recipient to transfer funds to
std::string address, hexMemo;
CAmount value = 0;
if (zOutputsDeque.size() > 0) {
SendManyRecipient smr = zOutputsDeque.front();
address = std::get<0>(smr);
value = std::get<1>(smr);
hexMemo = std::get<2>(smr);
zOutputsDeque.pop_front();
}
// Reset change
jsChange = 0;
CAmount outAmount = value;
// Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy)
if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) {
assert(!vpubNewProcessed);
if (jsInputValue < vpubNewTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)",
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total)));
}
outAmount += vpubNewTarget;
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
vpubNewProcessed = true;
jsChange = jsInputValue - outAmount;
assert(jsChange >= 0);
}
else {
// This is not the last joinsplit, so compute change and any amount still due to the recipient
if (jsInputValue > outAmount) {
jsChange = jsInputValue - outAmount;
} else if (outAmount > jsInputValue) {
// Any amount due is owed to the recipient. Let the miners fee get paid first.
CAmount due = outAmount - jsInputValue;
SendManyRecipient r = SendManyRecipient(address, due, hexMemo);
zOutputsDeque.push_front(r);
// reduce the amount being sent right now to the value of all inputs
value = jsInputValue;
}
}
// create output for recipient
if (address.empty()) {
assert(value==0);
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
} else {
PaymentAddress pa = DecodePaymentAddress(address);
// If we are here, we know we have no Sapling outputs.
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
}
// create output for any change
if (jsChange>0) {
info.vjsout.push_back(JSOutput(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_), jsChange));
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
getId(),
FormatMoney(jsChange)
);
}
obj = perform_joinsplit(info, witnesses, jsAnchor);
if (jsChange > 0) {
changeOutputIndex = find_output(obj, 1);
}
}
// Sanity check in case changes to code block above exits loop by invoking 'break'
assert(zInputsDeque.size() == 0);
assert(zOutputsDeque.size() == 0);
assert(vpubNewProcessed);
sign_send_raw_transaction(obj);
return true;
return false;
}
@@ -1127,33 +678,10 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
bool AsyncRPCOperation_sendmany::find_unspent_notes() {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
}
// If using the TransactionBuilder, we only want Sapling notes.
// If not using it, we only want Sprout notes.
// TODO: Refactor `GetFilteredNotes()` so we only fetch what we need.
if (isUsingBuilder_) {
sproutEntries.clear();
} else {
saplingEntries.clear();
}
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end());
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
getId(),
entry.jsop.hash.ToString().substr(0, 10),
entry.jsop.js,
int(entry.jsop.n), // uint8_t
FormatMoney(entry.plaintext.value()),
HexStr(data).substr(0, 10)
);
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_);
}
for (auto entry : saplingEntries) {
@@ -1167,15 +695,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
HexStr(data).substr(0, 10));
}
if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) {
return false;
}
// sort in descending order, so big notes appear first
std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(),
[](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
return std::get<2>(i) > std::get<2>(j);
});
std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(),
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
return i.note.value() > j.note.value();
@@ -1184,190 +704,6 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
return true;
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info) {
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector<JSOutPoint> & outPoints) {
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor)
{
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
if (!(witnesses.size() == info.notes.size())) {
throw runtime_error("number of notes and witnesses do not match");
}
for (size_t i = 0; i < witnesses.size(); i++) {
if (!witnesses[i]) {
throw runtime_error("joinsplit input could not be found in tree");
}
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get<libzcash::SproutSpendingKey>(spendingkey_)));
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
);
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_
) == 0))
{
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()
) == 0))
{
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() {
CMutableTransaction rawTx(tx_);

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2016 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -27,7 +28,6 @@
#include "zcash/JoinSplit.hpp"
#include "zcash/Address.hpp"
#include "wallet.h"
#include "paymentdisclosure.h"
#include <array>
#include <unordered_map>
@@ -46,19 +46,6 @@ typedef std::tuple<std::string, CAmount, std::string> SendManyRecipient;
// Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase)
typedef std::tuple<uint256, int, CAmount, bool, CTxDestination> SendManyInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note and amount
typedef std::tuple<JSOutPoint, SproutNote, CAmount> SendManyInputJSOP;
// Package of info which is passed to perform_joinsplit methods.
struct AsyncJoinSplitInfo
{
std::vector<JSInput> vjsin;
std::vector<JSOutput> vjsout;
std::vector<SproutNote> notes;
CAmount vpub_old = 0;
CAmount vpub_new = 0;
};
// A struct to help us track the witness and anchor for a given JSOutPoint
struct WitnessAnchorData {
boost::optional<SproutWitness> witness;
@@ -90,8 +77,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
@@ -117,7 +102,7 @@ private:
std::vector<SendManyRecipient> t_outputs_;
std::vector<SendManyRecipient> z_outputs_;
std::vector<SendManyInputUTXO> t_inputs_;
std::vector<SendManyInputJSOP> z_sprout_inputs_;
//std::vector<SendManyInputJSOP> z_sprout_inputs_;
std::vector<SaplingNoteEntry> z_sapling_inputs_;
TransactionBuilder builder_;
@@ -130,22 +115,7 @@ private:
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
bool main_impl();
// JoinSplit without any input notes to spend
UniValue perform_joinsplit(AsyncJoinSplitInfo &);
// JoinSplit with input notes to spend (JSOutPoints))
UniValue perform_joinsplit(AsyncJoinSplitInfo &, std::vector<JSOutPoint> & );
// JoinSplit where you have the witnesses and anchor
UniValue perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor);
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
@@ -190,22 +160,6 @@ public:
return delegate->main_impl();
}
UniValue perform_joinsplit(AsyncJoinSplitInfo &info) {
return delegate->perform_joinsplit(info);
}
UniValue perform_joinsplit(AsyncJoinSplitInfo &info, std::vector<JSOutPoint> &v ) {
return delegate->perform_joinsplit(info, v);
}
UniValue perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor)
{
return delegate->perform_joinsplit(info, witnesses, anchor);
}
void sign_send_raw_transaction(UniValue obj) {
delegate->sign_send_raw_transaction(obj);
}

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -47,9 +48,6 @@
#include "asyncrpcoperation_shieldcoinbase.h"
#include "paymentdisclosure.h"
#include "paymentdisclosuredb.h"
using namespace libzcash;
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
@@ -107,8 +105,6 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
// Lock UTXOs
lock_utxos();
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
@@ -180,20 +176,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
unlock_utxos(); // clean up
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
@@ -234,37 +216,6 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
return boost::apply_visitor(ShieldToAddress(this, sendAmount), tozaddr_);
}
bool ShieldToAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const {
// update the transaction with these inputs
CMutableTransaction rawTx(m_op->tx_);
for (ShieldCoinbaseUTXO & t : m_op->inputs_) {
CTxIn in(COutPoint(t.txid, t.vout));
if (t.amount >= ASSETCHAINS_TIMELOCKGTE)
in.nSequence = 0xfffffffe;
rawTx.vin.push_back(in);
}
m_op->tx_ = CTransaction(rawTx);
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(m_op->tx_);
crypto_sign_keypair(m_op->joinSplitPubKey_.begin(), m_op->joinSplitPrivKey_);
mtx.joinSplitPubKey = m_op->joinSplitPubKey_;
m_op->tx_ = CTransaction(mtx);
// Create joinsplit
UniValue obj(UniValue::VOBJ);
ShieldCoinbaseJSInfo info;
info.vpub_old = sendAmount;
info.vpub_new = 0;
JSOutput jso = JSOutput(zaddr, sendAmount);
info.vjsout.push_back(jso);
obj = m_op->perform_joinsplit(info);
m_op->sign_send_raw_transaction(obj);
return true;
}
extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
@@ -406,164 +357,6 @@ void AsyncRPCOperation_shieldcoinbase::sign_send_raw_transaction(UniValue obj)
}
UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInfo & info) {
uint32_t consensusBranchId;
uint256 anchor;
{
LOCK(cs_main);
consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
anchor = pcoinsTip->GetBestAnchor(SPROUT);
}
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
);
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION),
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_
) == 0))
{
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()
) == 0))
{
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
/**
* Override getStatus() to append the operation's context object to the default status object.
*/

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -33,8 +34,6 @@
#include <univalue.h>
#include "paymentdisclosure.h"
// Default transaction fee if caller does not specify one.
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
@@ -80,8 +79,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool cheatSpend = false; // set when this is shielding a cheating coinbase
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class ShieldToAddress;
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
@@ -109,9 +106,6 @@ private:
void lock_utxos();
void unlock_utxos();
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
class ShieldToAddress : public boost::static_visitor<bool>
@@ -123,7 +117,6 @@ public:
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
m_op(op), sendAmount(sendAmount) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2009-2013 The Bitcoin Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@@ -168,22 +169,6 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsi
return key.VerifyPubKey(vchPubKey);
}
static bool DecryptSproutSpendingKey(const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const libzcash::SproutPaymentAddress& address,
libzcash::SproutSpendingKey& sk)
{
CKeyingMaterial vchSecret;
if (!DecryptSecret(vMasterKey, vchCryptedSecret, address.GetHash(), vchSecret))
return false;
if (vchSecret.size() != libzcash::SerializedSproutSpendingKeySize)
return false;
CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
ss >> sk;
return sk.address() == address;
}
static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
@@ -207,7 +192,7 @@ bool CCryptoKeyStore::SetCrypted()
LOCK2(cs_KeyStore, cs_SpendingKeyStore);
if (fUseCrypto)
return true;
if (!(mapKeys.empty() && mapSproutSpendingKeys.empty() && mapSaplingSpendingKeys.empty()))
if (!(mapKeys.empty() && mapSaplingSpendingKeys.empty()))
return false;
fUseCrypto = true;
return true;
@@ -260,21 +245,6 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
if (fDecryptionThoroughlyChecked)
break;
}
CryptedSproutSpendingKeyMap::const_iterator miSprout = mapCryptedSproutSpendingKeys.begin();
for (; miSprout != mapCryptedSproutSpendingKeys.end(); ++miSprout)
{
const libzcash::SproutPaymentAddress &address = (*miSprout).first;
const std::vector<unsigned char> &vchCryptedSecret = (*miSprout).second;
libzcash::SproutSpendingKey sk;
if (!DecryptSproutSpendingKey(vMasterKeyIn, vchCryptedSecret, address, sk))
{
keyFail = true;
break;
}
keyPass = true;
if (fDecryptionThoroughlyChecked)
break;
}
CryptedSaplingSpendingKeyMap::const_iterator miSapling = mapCryptedSaplingSpendingKeys.begin();
for (; miSapling != mapCryptedSaplingSpendingKeys.end(); ++miSapling)
{
@@ -292,7 +262,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
}
if (keyPass && keyFail)
{
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
LogPrintf("Oh shit! The wallet is probably corrupted: Some keys decrypt but not all.\n");
assert(false);
}
if (keyFail || !keyPass)
@@ -440,30 +410,6 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
return false;
}
bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::AddSproutSpendingKey(sk);
if (IsLocked())
return false;
std::vector<unsigned char> vchCryptedSecret;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
auto address = sk.address();
if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret))
return false;
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk,
const libzcash::SaplingPaymentAddress &defaultAddr)
@@ -494,22 +440,6 @@ bool CCryptoKeyStore::AddSaplingSpendingKey(
return true;
}
bool CCryptoKeyStore::AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret)
{
{
LOCK(cs_SpendingKeyStore);
if (!SetCrypted())
return false;
mapCryptedSproutSpendingKeys[address] = vchCryptedSecret;
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(rk)));
}
return true;
}
bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret,
@@ -531,23 +461,6 @@ bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
return true;
}
bool CCryptoKeyStore::GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::GetSproutSpendingKey(address, skOut);
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.find(address);
if (mi != mapCryptedSproutSpendingKeys.end())
{
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second;
return DecryptSproutSpendingKey(vMasterKey, vchCryptedSecret, address, skOut);
}
}
return false;
}
bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
{
{
@@ -603,22 +516,6 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
}
}
mapKeys.clear();
BOOST_FOREACH(SproutSpendingKeyMap::value_type& mSproutSpendingKey, mapSproutSpendingKeys)
{
const libzcash::SproutSpendingKey &sk = mSproutSpendingKey.second;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
libzcash::SproutPaymentAddress address = sk.address();
std::vector<unsigned char> vchCryptedSecret;
if (!EncryptSecret(vMasterKeyIn, vchSecret, address.GetHash(), vchCryptedSecret)) {
return false;
}
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret)) {
return false;
}
}
mapSproutSpendingKeys.clear();
//! Sapling key support
BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys)
{

View File

@@ -1,4 +1,5 @@
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -145,7 +146,7 @@ class CCryptoKeyStore : public CBasicKeyStore
private:
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
CryptedKeyMap mapCryptedKeys;
CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
//CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
CKeyingMaterial vMasterKey;
@@ -223,37 +224,6 @@ public:
mi++;
}
}
virtual bool AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret);
bool AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk);
bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::HaveSproutSpendingKey(address);
return mapCryptedSproutSpendingKeys.count(address) > 0;
}
return false;
}
bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const;
void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const
{
if (!IsCrypted())
{
CBasicKeyStore::GetSproutPaymentAddresses(setAddress);
return;
}
setAddress.clear();
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.begin();
while (mi != mapCryptedSproutSpendingKeys.end())
{
setAddress.insert((*mi).first);
mi++;
}
}
//! Sapling
virtual bool AddCryptedSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,

View File

@@ -105,7 +105,7 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
nEnvFlags |= DB_PRIVATE;
dbenv->set_lg_dir(pathLogDir.string().c_str());
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
dbenv->set_cachesize(1, 0x100000, 1); // 1 MiB should be enough for just the wallet, Increased by 1 GB
dbenv->set_lg_bsize(0x10000);
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
@@ -181,6 +181,55 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}
bool CDBEnv::Compact(const std::string& strFile)
{
LOCK(cs_db);
DB_COMPACT dbcompact;
dbcompact.compact_fillpercent = 80;
dbcompact.compact_pages = DB_MAX_PAGES;
dbcompact.compact_timeout = 0;
DB_COMPACT *pdbcompact;
pdbcompact = &dbcompact;
int result = 1;
if (mapDb[strFile] != NULL) {
Db* pdb = mapDb[strFile];
result = pdb->compact(NULL, NULL, NULL, pdbcompact, DB_FREE_SPACE, NULL);
delete pdb;
mapDb[strFile] = NULL;
switch (result)
{
case DB_LOCK_DEADLOCK:
LogPrint("db","Deadlock %i\n", result);
break;
case DB_LOCK_NOTGRANTED:
LogPrint("db","Lock Not Granted %i\n", result);
break;
case DB_REP_HANDLE_DEAD:
LogPrint("db","Handle Dead %i\n", result);
break;
case DB_REP_LOCKOUT:
LogPrint("db","Rep Lockout %i\n", result);
break;
case EACCES:
LogPrint("db","Eacces %i\n", result);
break;
case EINVAL:
LogPrint("db","Error Invalid %i\n", result);
break;
case 0:
LogPrint("db","Wallet Compact Sucessful\n");
break;
default:
LogPrint("db","Compact result int %i\n", result);
}
}
return (result == 0);
}
bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
{
LOCK(cs_db);

View File

@@ -88,6 +88,7 @@ public:
* NOTE: reads the entire database into memory, so cannot be used
* for huge databases.
*/
bool Compact(const std::string& strFile);
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);

View File

@@ -53,12 +53,10 @@ public:
return CCryptoKeyStore::Unlock(vMasterKeyIn);
}
void IncrementNoteWitnesses(const CBlockIndex* pindex,
const CBlock* pblock,
SproutMerkleTree& sproutTree,
SaplingMerkleTree& saplingTree) {
CWallet::IncrementNoteWitnesses(pindex, pblock, sproutTree, saplingTree);
void BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly) {
CWallet::BuildWitnessCache(pindex, witnessOnly);
}
void DecrementNoteWitnesses(const CBlockIndex* pindex) {
CWallet::DecrementNoteWitnesses(pindex);
}
@@ -77,11 +75,6 @@ CWalletTx GetValidReceive(const libzcash::SproutSpendingKey& sk, CAmount value,
return GetValidReceive(*params, sk, value, randomInputs, version);
}
libzcash::SproutNote GetNote(const libzcash::SproutSpendingKey& sk,
const CTransaction& tx, size_t js, size_t n) {
return GetNote(*params, sk, tx, js, n);
}
CWalletTx GetValidSpend(const libzcash::SproutSpendingKey& sk,
const libzcash::SproutNote& note, CAmount value) {
return GetValidSpend(*params, sk, note, value);
@@ -116,7 +109,7 @@ std::pair<JSOutPoint, SaplingOutPoint> CreateValidBlock(TestWallet& wallet,
wallet.AddToWallet(wtx, true, NULL);
block.vtx.push_back(wtx);
wallet.IncrementNoteWitnesses(&index, &block, sproutTree, saplingTree);
wallet.BuildWitnessCache(&index, false);
return std::make_pair(jsoutpt, saplingNotes[0]);
}
@@ -141,341 +134,8 @@ TEST(WalletTests, SetupDatadirLocationRunAsFirstTest) {
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
}
/*
TEST(WalletTests, SproutNoteDataSerialisation) {
auto sk = libzcash::SproutSpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
SproutMerkleTree tree;
nd.witnesses.push_front(tree.witness());
noteData[jsoutpt] = nd;
CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << noteData;
mapSproutNoteData_t noteData2;
ss >> noteData2;
EXPECT_EQ(noteData, noteData2);
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
}
TEST(WalletTests, FindUnspentSproutNotes) {
SelectParams(CBaseChainParams::TESTNET);
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", -1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Fake-mine the transaction
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
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());
wtx.SetMerkleBranch(block);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// We now have an unspent and confirmed note in the wallet (depth of 1)
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's spend the note.
auto wtx2 = GetValidSpend(sk, note, 5);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// Fake-mine a spend transaction
EXPECT_EQ(0, chainActive.Height());
CBlock block2;
block2.vtx.push_back(wtx2);
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashPrevBlock = blockHash;
auto blockHash2 = block2.GetHash();
CBlockIndex fakeIndex2 {block2};
mapBlockIndex.insert(std::make_pair(blockHash2, &fakeIndex2));
fakeIndex2.SetHeight(1);
chainActive.SetTip(&fakeIndex2);
EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
EXPECT_EQ(1, chainActive.Height());
wtx2.SetMerkleBranch(block2);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's include spent notes to retrieve it.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// The spent note has two confirmations.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// It does not have 3 confirmations.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 3, false);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's receive a new note
CWalletTx wtx3;
{
auto wtx = GetValidReceive(sk, 20, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
wtx3 = wtx;
}
// Fake-mine the new transaction
EXPECT_EQ(1, chainActive.Height());
CBlock block3;
block3.vtx.push_back(wtx3);
block3.hashMerkleRoot = block3.BuildMerkleTree();
block3.hashPrevBlock = blockHash2;
auto blockHash3 = block3.GetHash();
CBlockIndex fakeIndex3 {block3};
mapBlockIndex.insert(std::make_pair(blockHash3, &fakeIndex3));
fakeIndex3.SetHeight(2);
chainActive.SetTip(&fakeIndex3);
EXPECT_TRUE(chainActive.Contains(&fakeIndex3));
EXPECT_EQ(2, chainActive.Height());
wtx3.SetMerkleBranch(block3);
wallet.AddToWallet(wtx3, true, NULL);
// We now have an unspent note which has one confirmation, in addition to our spent note.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's return the spent note too.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1, false);
EXPECT_EQ(2, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Increasing number of confirmations will exclude our new unspent note.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// If we also ignore spent notes at this depth, we won't find any notes.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, true);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2);
mapBlockIndex.erase(blockHash3);
}
TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
auto sk = libzcash::SproutSpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
EXPECT_EQ(noteData, wtx.mapSproutNoteData);
}
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto consensusParams = Params().GetConsensus();
TestWallet wallet;
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
libzcash::SaplingNote note(pk, 50000);
auto cm = note.cm().get();
SaplingMerkleTree tree;
tree.append(cm);
auto anchor = tree.root();
auto witness = tree.witness();
auto nf = note.nullifier(fvk, witness.position());
ASSERT_TRUE(nf);
uint256 nullifier = nf.get();
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
CWalletTx wtx {&wallet, tx};
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
mapSaplingNoteData_t noteData;
SaplingOutPoint op {wtx.GetHash(), 0};
SaplingNoteData nd;
nd.nullifier = nullifier;
nd.ivk = ivk;
nd.witnesses.push_front(witness);
nd.witnessHeight = 123;
noteData.insert(std::make_pair(op, nd));
wtx.SetSaplingNoteData(noteData);
EXPECT_EQ(noteData, wtx.mapSaplingNoteData);
// Test individual fields in case equality operator is defined/changed.
EXPECT_EQ(ivk, wtx.mapSaplingNoteData[op].ivk);
EXPECT_EQ(nullifier, wtx.mapSaplingNoteData[op].nullifier);
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight);
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front());
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, SetSproutInvalidNoteAddrsInCWalletTx) {
CWalletTx wtx;
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
mapSproutNoteData_t noteData;
auto sk = libzcash::SproutSpendingKey::random();
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), uint256()};
noteData[jsoutpt] = nd;
EXPECT_THROW(wtx.SetSproutNoteData(noteData), std::logic_error);
}
// The following test is the same as SetInvalidSaplingNoteDataInCWalletTx
// TEST(WalletTests, SetSaplingInvalidNoteAddrsInCWalletTx)
// Cannot add note data for an index which does not exist in tx.vShieldedOutput
TEST(WalletTests, SetInvalidSaplingNoteDataInCWalletTx) {
CWalletTx wtx;
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
mapSaplingNoteData_t noteData;
SaplingOutPoint op {uint256(), 1};
SaplingNoteData nd;
noteData.insert(std::make_pair(op, nd));
EXPECT_THROW(wtx.SetSaplingNoteData(noteData), std::logic_error);
}
TEST(WalletTests, GetSproutNoteNullifier) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
auto address = sk.address();
auto dec = ZCNoteDecryption(sk.receiving_key());
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto hSig = wtx.vjoinsplit[0].h_sig(
*params, wtx.joinSplitPubKey);
auto ret = wallet.GetSproutNoteNullifier(
wtx.vjoinsplit[0],
address,
dec,
hSig, 1);
EXPECT_NE(nullifier, ret);
wallet.AddSproutSpendingKey(sk);
ret = wallet.GetSproutNoteNullifier(
wtx.vjoinsplit[0],
address,
dec,
hSig, 1);
EXPECT_EQ(nullifier, ret);
}
TEST(WalletTests, FindMySaplingNotes) {
SelectParams(CBaseChainParams::REGTEST);
@@ -579,92 +239,6 @@ TEST(WalletTests, FindMySaplingNotesWithIvkOnly) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, FindMySproutNotes) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
auto sk2 = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk2);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(0, noteMap.size());
wallet.AddSproutSpendingKey(sk);
noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
TestWallet wallet;
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_NE(nd, noteMap[jsoutpt]);
ASSERT_TRUE(wallet.Unlock(vMasterKey));
noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(WalletTests, GetConflictedSproutNotes) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(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);
}
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions
TEST(WalletTests, GetConflictedSaplingNotes) {
SelectParams(CBaseChainParams::REGTEST);
@@ -724,8 +298,8 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
wallet.AddToWallet(wtx, true, NULL);
// Simulate receiving new block and ChainTip signal
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
wallet.BuildWitnessCache(&fakeIndex, false);
wallet.UpdateNullifierNoteMapForBlock(&block);
// Retrieve the updated wtx from wallet
uint256 hash = wtx.GetHash();
@@ -1008,8 +582,8 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
}
// Simulate receiving new block and ChainTip signal
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
wallet.BuildWitnessCache(&fakeIndex, false);
wallet.UpdateNullifierNoteMapForBlock(&block);
// Retrieve the updated wtx from wallet
uint256 hash = wtx.GetHash();
@@ -1126,8 +700,8 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// Simulate receiving new block and ChainTip signal.
// This triggers calculation of nullifiers for notes belonging to this wallet
// in the output descriptions of wtx.
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
wallet.BuildWitnessCache(&fakeIndex, false);
wallet.UpdateNullifierNoteMapForBlock(&block);
// Retrieve the updated wtx from wallet
wtx = wallet.mapWallet[wtx.GetHash()];
@@ -1263,7 +837,7 @@ TEST(WalletTests, CachedWitnessesEmptyChain) {
CBlockIndex index(block);
SproutMerkleTree sproutTree;
SaplingMerkleTree saplingTree;
wallet.IncrementNoteWitnesses(&index, &block, sproutTree, saplingTree);
wallet.BuildWitnessCache(&index, false);
::GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
@@ -1354,7 +928,7 @@ TEST(WalletTests, CachedWitnessesChainTip) {
EXPECT_NE(anchors1.second, anchors3.second);
// Re-incrementing with the same block should give the same result
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
wallet.BuildWitnessCache(&index2, false);
auto anchors4 = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
EXPECT_NE(anchors4.first, anchors4.second);
@@ -1364,7 +938,7 @@ TEST(WalletTests, CachedWitnessesChainTip) {
EXPECT_EQ(anchors2.second, anchors4.second);
// Incrementing with the same block again should not change the cache
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
wallet.BuildWitnessCache(&index2, false);
std::vector<boost::optional<SproutWitness>> sproutWitnesses5;
std::vector<boost::optional<SaplingWitness>> saplingWitnesses5;
@@ -1447,7 +1021,7 @@ TEST(WalletTests, CachedWitnessesDecrementFirst) {
EXPECT_NE(anchors2.second, anchors4.second);
// Re-incrementing with the same block should give the same result
wallet.IncrementNoteWitnesses(&index2, &block2, sproutTree, saplingTree);
wallet.BuildWitnessCache(&index2, false);
auto anchors5 = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
@@ -1504,7 +1078,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
for (size_t i = 0; i < numBlocks; i++) {
SproutMerkleTree sproutRiPrevTree {sproutRiTree};
SaplingMerkleTree saplingRiPrevTree {saplingRiTree};
wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), sproutRiTree, saplingRiTree);
wallet.BuildWitnessCache(&indices[i], false);
auto anchors = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
for (size_t j = 0; j < numBlocks; j++) {
@@ -1531,7 +1105,7 @@ TEST(WalletTests, CachedWitnessesCleanIndex) {
}
{
wallet.IncrementNoteWitnesses(&(indices[i]), &(blocks[i]), sproutRiPrevTree, saplingRiPrevTree);
wallet.BuildWitnessCache(&indices[i], false);
auto anchors = GetWitnessesAndAnchors(wallet, sproutNotes, saplingNotes, sproutWitnesses, saplingWitnesses);
for (size_t j = 0; j < numBlocks; j++) {
EXPECT_TRUE((bool) sproutWitnesses[j]);
@@ -1737,88 +1311,6 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
wallet.SetBestChain(walletdb, loc);
}
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
TestWallet wallet;
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
// Pretend that we called FindMySproutNotes while the wallet was locked
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address()};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes.count(nullifier));
EXPECT_FALSE(wallet.UpdateNullifierNoteMap());
ASSERT_TRUE(wallet.Unlock(vMasterKey));
EXPECT_TRUE(wallet.UpdateNullifierNoteMap());
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes.count(nullifier));
EXPECT_EQ(wtx.GetHash(), wallet.mapSproutNullifiersToNotes[nullifier].hash);
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes[nullifier].js);
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes[nullifier].n);
}
TEST(WalletTests, UpdatedSproutNoteData) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(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);
auto wtx2 = wtx;
// First pretend we added the tx to the wallet and
// we don't have the key for the second note
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
// Pretend we mined the tx by adding a fake witness
SproutMerkleTree tree;
wtx.mapSproutNoteData[jsoutpt].witnesses.push_front(tree.witness());
wtx.mapSproutNoteData[jsoutpt].witnessHeight = 100;
// Now pretend we added the key for the second note, and
// the tx was "added" to the wallet again to update it.
// This happens via the 'z_importkey' RPC method.
JSOutPoint jsoutpt2 {wtx2.GetHash(), 0, 1};
SproutNoteData nd2 {sk.address(), nullifier2};
noteData[jsoutpt2] = nd2;
wtx2.SetSproutNoteData(noteData);
// The txs should initially be different
EXPECT_NE(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
// After updating, they should be the same
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
EXPECT_EQ(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
// TODO: The new note should get witnessed (but maybe not here) (#1350)
}
TEST(WalletTests, UpdatedSaplingNoteData) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
@@ -1886,8 +1378,8 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
wallet.AddToWallet(wtx, true, NULL);
// Simulate receiving new block and ChainTip signal
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
wallet.BuildWitnessCache(&fakeIndex, false);
wallet.UpdateNullifierNoteMapForBlock(&block);
// Retrieve the updated wtx from wallet
uint256 hash = wtx.GetHash();
@@ -1915,7 +1407,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
EXPECT_EQ(2, wtx2.mapSaplingNoteData.size());
EXPECT_EQ(1, wtx2.mapSaplingNoteData[sop0].witnesses.size()); // wtx2 has fake witness for payment output
EXPECT_EQ(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had incrementnotewitness called
EXPECT_EQ(0, wtx2.mapSaplingNoteData[sop1].witnesses.size()); // wtx2 never had BuildWitnessCache called
// After updating, they should be the same
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
@@ -1943,37 +1435,6 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto hash = wtx.GetHash();
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {hash, 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
wallet.MarkAffectedTransactionsDirty(wtx);
// After getting a cached value, the first tx should be clean
wallet.mapWallet[hash].GetDebit(ISMINE_ALL);
EXPECT_TRUE(wallet.mapWallet[hash].fDebitCached);
// After adding the note spend, the first tx should be dirty
wallet.AddToWallet(wtx2, true, NULL);
wallet.MarkAffectedTransactionsDirty(wtx2);
EXPECT_FALSE(wallet.mapWallet[hash].fDebitCached);
}
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
@@ -2001,7 +1462,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
// Generate shielding tx from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
// 0.0005 t-HUSH in, 0.0004 z-HUSH out, 0.0001 t-HUSH fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
@@ -2040,8 +1501,8 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
wallet.AddToWallet(wtx, true, NULL);
// Simulate receiving new block and ChainTip signal
wallet.IncrementNoteWitnesses(&fakeIndex, &block, sproutTree, saplingTree);
wallet.UpdateSaplingNullifierNoteMapForBlock(&block);
wallet.BuildWitnessCache(&fakeIndex, false);
wallet.UpdateNullifierNoteMapForBlock(&block);
// Retrieve the updated wtx from wallet
uint256 hash = wtx.GetHash();
@@ -2095,39 +1556,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, SproutNoteLocking) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto wtx2 = GetValidReceive(sk, 10, true);
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
JSOutPoint jsoutpt2 {wtx2.GetHash(),0, 0};
// Test selective locking
wallet.LockNote(jsoutpt);
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
// Test selective unlocking
wallet.UnlockNote(jsoutpt);
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
// Test multiple locking
wallet.LockNote(jsoutpt);
wallet.LockNote(jsoutpt2);
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt2));
// Test unlock all
wallet.UnlockAllSproutNotes();
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
}
*/
TEST(WalletTests, SaplingNoteLocking) {
TestWallet wallet;

View File

@@ -106,303 +106,6 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) {
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
}
/**
* This test covers methods on CWallet
* GenerateNewSproutZKey()
* AddSproutZKey()
* LoadZKey()
* LoadZKeyMetadata()
*/
TEST(wallet_zkeys_tests, store_and_load_zkeys) {
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// wallet should have one key
auto addr = wallet.GenerateNewSproutZKey();
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// verify wallet has spending key for the address
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// manually add new spending key to wallet
auto sk = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.AddSproutZKey(sk));
// verify wallet did add it
addr = sk.address();
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// verify spending key stored correctly
libzcash::SproutSpendingKey keyOut;
wallet.GetSproutSpendingKey(addr, keyOut);
ASSERT_EQ(sk, keyOut);
// verify there are two keys
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
ASSERT_EQ(1, addrs.count(addr));
// Load a third key into the wallet
sk = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.LoadZKey(sk));
// attach metadata to this third key
addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
// check metadata is the same
CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}
/**
* This test covers methods on CWallet
* AddSproutViewingKey()
* RemoveSproutViewingKey()
* LoadSproutViewingKey()
*/
TEST(wallet_zkeys_tests, StoreAndLoadViewingKeys) {
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// manually add new viewing key to wallet
auto sk = libzcash::SproutSpendingKey::random();
auto vk = sk.viewing_key();
ASSERT_TRUE(wallet.AddSproutViewingKey(vk));
// verify wallet did add it
auto addr = sk.address();
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
// and that we don't have the corresponding spending key
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
// verify viewing key stored correctly
libzcash::SproutViewingKey vkOut;
wallet.GetSproutViewingKey(addr, vkOut);
ASSERT_EQ(vk, vkOut);
// Load a second viewing key into the wallet
auto sk2 = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.LoadSproutViewingKey(sk2.viewing_key()));
// verify wallet did add it
auto addr2 = sk2.address();
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr2));
// Remove the first viewing key
ASSERT_TRUE(wallet.RemoveSproutViewingKey(vk));
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
}
/**
* This test covers methods on CWalletDB
* WriteZKey()
*/
TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// Add random key to the wallet
auto paymentAddress = wallet.GenerateNewSproutZKey();
// wallet should have one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// create random key and add it to database directly, bypassing wallet
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
CWalletDB db("wallet.dat");
db.WriteZKey(addr, sk, meta);
// wallet should not be aware of key
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
// wallet sees one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// wallet should have default metadata for addr with null createtime
CKeyMetadata m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, 0);
ASSERT_NE(m.nCreateTime, now);
// load the wallet again
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// wallet can now see the spending key
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// check key is the same
libzcash::SproutSpendingKey keyOut;
wallet.GetSproutSpendingKey(addr, keyOut);
ASSERT_EQ(sk, keyOut);
// wallet should have two keys
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
// check metadata is now the same
m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}
/**
* This test covers methods on CWalletDB
* WriteSproutViewingKey()
*/
TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet-vkey.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// create random viewing key and add it to database directly, bypassing wallet
auto sk = libzcash::SproutSpendingKey::random();
auto vk = sk.viewing_key();
auto addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
CWalletDB db("wallet-vkey.dat");
db.WriteSproutViewingKey(vk);
// wallet should not be aware of viewing key
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
// load the wallet again
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// wallet can now see the viewing key
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
// check key is the same
libzcash::SproutViewingKey vkOut;
wallet.GetSproutViewingKey(addr, vkOut);
ASSERT_EQ(vk, vkOut);
}
/**
* This test covers methods on CWalletDB to load/save crypted z keys.
*/
TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet_crypted.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// Add random key to the wallet
auto paymentAddress = wallet.GenerateNewSproutZKey();
// wallet should have one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// encrypt wallet
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = "hello";
ASSERT_TRUE(wallet.EncryptWallet(strWalletPass));
// adding a new key will fail as the wallet is locked
EXPECT_ANY_THROW(wallet.GenerateNewSproutZKey());
// unlock wallet and then add
wallet.Unlock(strWalletPass);
auto paymentAddress2 = wallet.GenerateNewSproutZKey();
// Create a new wallet from the existing wallet path
CWallet wallet2("wallet_crypted.dat");
ASSERT_EQ(DB_LOAD_OK, wallet2.LoadWallet(fFirstRun));
// Confirm it's not the same as the other wallet
ASSERT_TRUE(&wallet != &wallet2);
// wallet should have two keys
wallet2.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
// check we have entries for our payment addresses
ASSERT_TRUE(addrs.count(paymentAddress));
ASSERT_TRUE(addrs.count(paymentAddress2));
// spending key is crypted, so we can't extract valid payment address
libzcash::SproutSpendingKey keyOut;
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
ASSERT_FALSE(paymentAddress == keyOut.address());
// unlock wallet to get spending keys and verify payment addresses
wallet2.Unlock(strWalletPass);
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
ASSERT_EQ(paymentAddress, keyOut.address());
wallet2.GetSproutSpendingKey(paymentAddress2, keyOut);
ASSERT_EQ(paymentAddress2, keyOut.address());
}
/**
* This test covers methods on CWalletDB to load/save crypted sapling z keys.
*/

View File

@@ -1,319 +0,0 @@
// Copyright (c) 2017 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
* the top-level directory of this distribution for the individual copyright *
* holder information and the developer policies on copyright and licensing. *
* *
* Unless otherwise agreed in a custom licensing agreement, no part of the *
* SuperNET software, including this file may be copied, modified, propagated *
* or distributed except according to the terms contained in the LICENSE file *
* *
* Removal or modification of this copyright notice is prohibited. *
* *
******************************************************************************/
#include "rpc/server.h"
#include "init.h"
#include "key_io.h"
#include "main.h"
#include "script/script.h"
#include "script/standard.h"
#include "sync.h"
#include "util.h"
#include "utiltime.h"
#include "wallet.h"
#include <fstream>
#include <stdint.h>
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <univalue.h>
#include "paymentdisclosure.h"
#include "paymentdisclosuredb.h"
#include "zcash/Note.hpp"
#include "zcash/NoteEncryption.hpp"
using namespace std;
using namespace libzcash;
// Function declaration for function implemented in wallet/rpcwallet.cpp
bool EnsureWalletIsAvailable(bool avoidException);
/**
* RPC call to generate a payment disclosure
*/
UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
string enableArg = "paymentdisclosure";
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
string strPaymentDisclosureDisabledMsg = "";
if (!fEnablePaymentDisclosure) {
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_getpaymentdisclosure", enableArg);
}
if (fHelp || params.size() < 3 || params.size() > 4 )
throw runtime_error(
"z_getpaymentdisclosure \"txid\" \"js_index\" \"output_index\" (\"message\") \n"
"\nGenerate a payment disclosure for a given joinsplit output.\n"
"\nEXPERIMENTAL FEATURE\n"
+ strPaymentDisclosureDisabledMsg +
"\nArguments:\n"
"1. \"txid\" (string, required) \n"
"2. \"js_index\" (string, required) \n"
"3. \"output_index\" (string, required) \n"
"4. \"message\" (string, optional) \n"
"\nResult:\n"
"\"paymentdisclosure\" (string) Hex data string, with \"zpd:\" prefix.\n"
"\nExamples:\n"
+ HelpExampleCli("z_getpaymentdisclosure", "96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2 0 0 \"refund\"")
+ HelpExampleRpc("z_getpaymentdisclosure", "\"96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2\", 0, 0, \"refund\"")
);
if (!fEnablePaymentDisclosure) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
}
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
// Check wallet knows about txid
string txid = params[0].get_str();
uint256 hash;
hash.SetHex(txid);
CTransaction tx;
uint256 hashBlock;
// Check txid has been seen
if (!GetTransaction(hash, tx, hashBlock, true)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
}
// Check tx has been confirmed
if (hashBlock.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
}
// Check is mine
if (!pwalletMain->mapWallet.count(hash)) {
throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not belong to the wallet");
}
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
// Check if shielded tx
if (wtx.vjoinsplit.empty()) {
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
}
// Check js_index
int js_index = params[1].get_int();
if (js_index < 0 || js_index >= wtx.vjoinsplit.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid js_index");
}
// Check output_index
int output_index = params[2].get_int();
if (output_index < 0 || output_index >= ZC_NUM_JS_OUTPUTS) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid output_index");
}
// Get message if it exists
string msg;
if (params.size() == 4) {
msg = params[3].get_str();
}
// Create PaymentDisclosureKey
PaymentDisclosureKey key = {hash, (size_t)js_index, (uint8_t)output_index };
// TODO: In future, perhaps init the DB in init.cpp
shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
PaymentDisclosureInfo info;
if (!db->Get(key, info)) {
throw JSONRPCError(RPC_DATABASE_ERROR, "Could not find payment disclosure info for the given joinsplit output");
}
PaymentDisclosure pd( wtx.joinSplitPubKey, key, info, msg );
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << pd;
string strHex = HexStr(ss.begin(), ss.end());
return PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX + strHex;
}
/**
* RPC call to validate a payment disclosure data blob.
*/
UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
string enableArg = "paymentdisclosure";
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
string strPaymentDisclosureDisabledMsg = "";
if (!fEnablePaymentDisclosure) {
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_validatepaymentdisclosure", enableArg);
}
if (fHelp || params.size() != 1)
throw runtime_error(
"z_validatepaymentdisclosure \"paymentdisclosure\"\n"
"\nValidates a payment disclosure.\n"
"\nEXPERIMENTAL FEATURE\n"
+ strPaymentDisclosureDisabledMsg +
"\nArguments:\n"
"1. \"paymentdisclosure\" (string, required) Hex data string, with \"zpd:\" prefix.\n"
"\nExamples:\n"
+ HelpExampleCli("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
+ HelpExampleRpc("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
);
if (!fEnablePaymentDisclosure) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
}
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
// Verify the payment disclosure input begins with "zpd:" prefix.
string strInput = params[0].get_str();
size_t pos = strInput.find(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX);
if (pos != 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure prefix not found.");
}
string hexInput = strInput.substr(strlen(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX));
if (!IsHex(hexInput))
{
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected payment disclosure data in hexadecimal format.");
}
// Unserialize the payment disclosure data into an object
PaymentDisclosure pd;
CDataStream ss(ParseHex(hexInput), SER_NETWORK, PROTOCOL_VERSION);
try {
ss >> pd;
// too much data is ignored, but if not enough data, exception of type ios_base::failure is thrown,
// CBaseDataStream::read(): end of data: iostream error
} catch (const std::exception &e) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure data is malformed.");
}
if (pd.payload.marker != PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure marker not found.");
}
if (pd.payload.version != PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Payment disclosure version is unsupported.");
}
uint256 hash = pd.payload.txid;
CTransaction tx;
uint256 hashBlock;
// Check if we have seen the transaction
if (!GetTransaction(hash, tx, hashBlock, true)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
}
// Check if the transaction has been confirmed
if (hashBlock.IsNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
}
// Check if shielded tx
if (tx.vjoinsplit.empty()) {
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
}
UniValue errs(UniValue::VARR);
UniValue o(UniValue::VOBJ);
o.push_back(Pair("txid", pd.payload.txid.ToString()));
// Check js_index
if (pd.payload.js >= tx.vjoinsplit.size()) {
errs.push_back("Payment disclosure refers to an invalid joinsplit index");
}
o.push_back(Pair("jsIndex", pd.payload.js));
if (pd.payload.n < 0 || pd.payload.n >= ZC_NUM_JS_OUTPUTS) {
errs.push_back("Payment disclosure refers to an invalid output index");
}
o.push_back(Pair("outputIndex", pd.payload.n));
o.push_back(Pair("version", pd.payload.version));
o.push_back(Pair("onetimePrivKey", pd.payload.esk.ToString()));
o.push_back(Pair("message", pd.payload.message));
o.push_back(Pair("joinSplitPubKey", tx.joinSplitPubKey.ToString()));
// Verify the payment disclosure was signed using the same key as the transaction i.e. the joinSplitPrivKey.
uint256 dataToBeSigned = SerializeHash(pd.payload, SER_GETHASH, 0);
bool sigVerified = (crypto_sign_verify_detached(pd.payloadSig.data(),
dataToBeSigned.begin(), 32,
tx.joinSplitPubKey.begin()) == 0);
o.push_back(Pair("signatureVerified", sigVerified));
if (!sigVerified) {
errs.push_back("Payment disclosure signature does not match transaction signature");
}
// Check the payment address is valid
SproutPaymentAddress zaddr = pd.payload.zaddr;
{
o.push_back(Pair("paymentAddress", EncodePaymentAddress(zaddr)));
try {
// Decrypt the note to get value and memo field
JSDescription jsdesc = tx.vjoinsplit[pd.payload.js];
uint256 h_sig = jsdesc.h_sig(*pzcashParams, tx.joinSplitPubKey);
ZCPaymentDisclosureNoteDecryption decrypter;
ZCNoteEncryption::Ciphertext ciphertext = jsdesc.ciphertexts[pd.payload.n];
uint256 pk_enc = zaddr.pk_enc;
auto plaintext = decrypter.decryptWithEsk(ciphertext, pk_enc, pd.payload.esk, h_sig, pd.payload.n);
CDataStream ssPlain(SER_NETWORK, PROTOCOL_VERSION);
ssPlain << plaintext;
SproutNotePlaintext npt;
ssPlain >> npt;
string memoHexString = HexStr(npt.memo().data(), npt.memo().data() + npt.memo().size());
o.push_back(Pair("memo", memoHexString));
o.push_back(Pair("value", ValueFromAmount(npt.value())));
// Check the blockchain commitment matches decrypted note commitment
uint256 cm_blockchain = jsdesc.commitments[pd.payload.n];
SproutNote note = npt.note(zaddr);
uint256 cm_decrypted = note.cm();
bool cm_match = (cm_decrypted == cm_blockchain);
o.push_back(Pair("commitmentMatch", cm_match));
if (!cm_match) {
errs.push_back("Commitment derived from payment disclosure does not match blockchain commitment");
}
} catch (const std::exception &e) {
errs.push_back(string("Error while decrypting payment disclosure note: ") + string(e.what()) );
}
}
bool isValid = errs.empty();
o.push_back(Pair("valid", isValid));
if (!isValid) {
o.push_back(Pair("errors", errs));
}
return o;
}

View File

@@ -149,6 +149,43 @@ UniValue convertpassphrase(const UniValue& params, bool fHelp, const CPubKey& my
return ret;
}
UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
//LOCK2(cs_main, pwalletMain->cs_wallet);
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"rescan \"height\"\n"
"\nRescan all transactions from genesis or given block height.\n"
"\nArguments:\n"
"1. \"height\" (integer, optional) The block height to rescan from\n"
"\nExamples:\n"
"\nRescan from block height 555\n"
+ HelpExampleCli("rescan", "\"555\"") +
"\nRescan from genesis block\n"
+ HelpExampleCli("rescan","")
);
// Height to rescan from
int nRescanHeight = 0;
if (params.size() > 0)
nRescanHeight = params[0].get_int();
if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}
LogPrintf("Rescanning from height=%d\n", nRescanHeight);
//pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight],true);
bool update = false;
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(),update);
//TODO: can we return something more useful?
return NullUniValue;
}
UniValue importprivkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
@@ -666,18 +703,10 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
file << "\n";
if (fDumpZKeys) {
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
pwalletMain->GetSproutPaymentAddresses(sproutAddresses);
file << "\n";
file << "# Zkeys\n";
file << "\n";
for (auto addr : sproutAddresses) {
libzcash::SproutSpendingKey key;
if (pwalletMain->GetSproutSpendingKey(addr, key)) {
std::string strTime = EncodeDumpTime(pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime);
file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr));
}
}
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
pwalletMain->GetSaplingPaymentAddresses(saplingAddresses);
file << "\n";
@@ -864,48 +893,27 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
}
if (boost::get<libzcash::SproutViewingKey>(&viewingkey) == nullptr) {
if (params.size() < 4) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
}
string strAddress = params[3].get_str();
auto address = DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (params.size() < 4) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
}
string strAddress = params[3].get_str();
auto address = DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
pwalletMain->MarkDirty();
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
auto vkey = boost::get<libzcash::SproutViewingKey>(viewingkey);
auto addr = vkey.address();
if (pwalletMain->HaveSproutSpendingKey(addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key");
}
pwalletMain->MarkDirty();
// Don't throw error in case a viewing key is already there
if (pwalletMain->HaveSproutViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
pwalletMain->MarkDirty();
if (!pwalletMain->AddSproutViewingKey(vkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
}
@@ -985,26 +993,12 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (boost::get<libzcash::SproutPaymentAddress>(&address) == nullptr) {
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
libzcash::SaplingIncomingViewingKey ivk;
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
}
return EncodeViewingKey(ivk);
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
libzcash::SaplingIncomingViewingKey ivk;
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
}
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
libzcash::SproutViewingKey vk;
if (!pwalletMain->GetSproutViewingKey(addr, vk)) {
libzcash::SproutSpendingKey k;
if (!pwalletMain->GetSproutSpendingKey(addr, k)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this zaddr");
}
vk = k.viewing_key();
}
return EncodeViewingKey(vk);
return EncodeViewingKey(ivk);
}
extern int32_t KOMODO_NSPV;

View File

@@ -0,0 +1,478 @@
// Copyright (c) 2019-2020 The Hush developers
// Copyright (c) 2019 Cryptoforge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "init.h"
#include "key_io.h"
#include "rpc/server.h"
#include "wallet.h"
#include "rpchushwallet.h"
#include "utilmoneystr.h"
#include <utf8.h>
using namespace std;
using namespace libzcash;
bool EnsureWalletIsAvailable(bool avoidException);
void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress) {
LOCK2(cs_main, pwalletMain->cs_wallet);
//Used to identify incomplete key sets
int vinCount = 0;
int vinSpendCount = 0;
int shieldedSpendCount = 0;
int shieldedSpendsSpentCount = 0;
//Check address
bool isTAddress = false;
bool isZsAddress = false;
CTxDestination tAddress = DecodeDestination(strAddress);
auto zAddress = DecodePaymentAddress(strAddress);
SaplingPaymentAddress zsAddress;
if (filterByAddress) {
if (IsValidDestination(tAddress))
isTAddress = true;
if (IsValidPaymentAddress(zAddress)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
isZsAddress = true;
}
}
}
// Transparent Inputs belonging to the wallet
UniValue tSpends(UniValue::VARR);
if (isTAddress || !filterByAddress) {
for (int i = 0; i < wtx.vin.size(); i++) {
vinCount++;
const CTxIn& txin = wtx.vin[i];
UniValue obj(UniValue::VOBJ);
CTxDestination address;
const CWalletTx* parent = pwalletMain->GetWalletTx(txin.prevout.hash);
if (parent != NULL) {
const CTxOut& parentOut = parent->vout[txin.prevout.n];
ExtractDestination(parentOut.scriptPubKey, address);
if(IsMine(*pwalletMain, address)){
vinSpendCount++;
totalSpends += CAmount(-parentOut.nValue);
obj.push_back(Pair("address",EncodeDestination(address)));
obj.push_back(Pair("scriptPubKey",HexStr(parentOut.scriptPubKey.begin(), parentOut.scriptPubKey.end())));
obj.push_back(Pair("amount",ValueFromAmount(-parentOut.nValue)));
obj.push_back(Pair("spendTxid",parent->GetHash().ToString()));
obj.push_back(Pair("spendVout",(int)txin.prevout.n));
CTxDestination filterAddress = DecodeDestination(strAddress);
if (address == tAddress || !filterByAddress) {
filteredSpends += CAmount(-parentOut.nValue);
tSpends.push_back(obj);
}
}
}
}
}
spends.push_back(Pair("transparentSpends",tSpends));
// Sapling Inputs belonging to the wallet
UniValue zsSpends(UniValue::VARR);
if (isZsAddress || !filterByAddress) {
for (int i = 0; i < wtx.vShieldedSpend.size(); i++) {
shieldedSpendCount++;
const SpendDescription& spendDesc = wtx.vShieldedSpend[i];
UniValue obj(UniValue::VOBJ);
SaplingOutPoint op = pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier];
if (pwalletMain->IsSaplingNullifierFromMe(spendDesc.nullifier)) {
const CWalletTx* parent = pwalletMain->GetWalletTx(pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier].hash);
const OutputDescription& output = parent->vShieldedOutput[op.n];
auto nd = pwalletMain->mapWallet[pwalletMain->mapSaplingNullifiersToNotes[spendDesc.nullifier].hash].mapSaplingNoteData[op];
auto pt = libzcash::SaplingNotePlaintext::decrypt(output.encCiphertext,nd.ivk,output.ephemeralKey,output.cm);
if (pt) {
auto note = pt.get();
auto pa = nd.ivk.address(note.d);
auto address = pa.get();
shieldedSpendsSpentCount++;
totalSpends += CAmount(-note.value());
obj.push_back(Pair("address",EncodePaymentAddress(address)));
obj.push_back(Pair("amount", ValueFromAmount(CAmount(-note.value()))));
obj.push_back(Pair("spendTxid",parent->GetHash().ToString()));
obj.push_back(Pair("spendshieldedOutputIndex",(int)op.n));
if (address == zsAddress || !filterByAddress) {
filteredSpends += CAmount(-note.value());
zsSpends.push_back(obj);
}
}
}
}
}
spends.push_back(Pair("saplingSpends",zsSpends));
spends.push_back(Pair("totalSpends",ValueFromAmount(filteredSpends)));
if (!filterByAddress) {
if (filteredSpends != 0 && (vinCount != vinSpendCount || shieldedSpendCount != shieldedSpendsSpentCount)) {
spends.push_back(Pair("missingSpendingKeys", true));
spends.push_back(Pair("vinCount", vinCount));
spends.push_back(Pair("vinSpendCount", vinSpendCount));
spends.push_back(Pair("shieldedSpendCount", shieldedSpendCount));
spends.push_back(Pair("shieldedSpendsSpentCount", shieldedSpendsSpentCount));
} else {
spends.push_back(Pair("missingSpendingKeys", false));
}
}
}
void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress) {
LOCK2(cs_main, pwalletMain->cs_wallet);
//Check address
bool isTAddress = false;
bool isZsAddress = false;
CTxDestination tAddress = DecodeDestination(strAddress);
auto zAddress = DecodePaymentAddress(strAddress);
SaplingPaymentAddress zsAddress;
if (filterByAddress) {
if (IsValidDestination(tAddress))
isTAddress = true;
if (IsValidPaymentAddress(zAddress)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
isZsAddress = true;
}
}
}
//Transparent Received txos belonging to the wallet
UniValue tReceived(UniValue::VARR);
if (isTAddress || !filterByAddress) {
for (int i = 0; i < wtx.vout.size(); i++) {
const CTxOut& txout = wtx.vout[i];
UniValue obj(UniValue::VOBJ);
CTxDestination address;
ExtractDestination(txout.scriptPubKey, address);
if(IsMine(*pwalletMain, address)){
obj.push_back(Pair("address",EncodeDestination(address)));
obj.push_back(Pair("scriptPubKey",HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end())));
obj.push_back(Pair("amount",ValueFromAmount(txout.nValue)));
obj.push_back(Pair("vout", i));
if (address == tAddress || !filterByAddress) {
totalReceived += CAmount(txout.nValue);
tReceived.push_back(obj);
}
}
}
}
received.push_back(Pair("transparentReceived",tReceived));
//Sapling Sends belonging to the wallet
UniValue zsReceived(UniValue::VARR);
if (isZsAddress || !filterByAddress) {
for (int i = 0; i < wtx.vShieldedOutput.size(); i++) {
const OutputDescription& outputDesc = wtx.vShieldedOutput[i];
UniValue obj(UniValue::VOBJ);
bool changeTx = false;
//Decrypt sapling incoming commitments using IVK
std::set<libzcash::SaplingPaymentAddress> addresses;
pwalletMain->GetSaplingPaymentAddresses(addresses);
for (auto addr : addresses) {
libzcash::SaplingExtendedSpendingKey extsk;
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
auto pt = libzcash::SaplingNotePlaintext::decrypt(
outputDesc.encCiphertext, extsk.expsk.full_viewing_key().in_viewing_key(), outputDesc.ephemeralKey, outputDesc.cm);
if (pt) {
auto note = pt.get();
obj.push_back(Pair("address",EncodePaymentAddress(addr)));
obj.push_back(Pair("amount", ValueFromAmount(CAmount(note.value()))));
obj.push_back(Pair("shieldedOutputIndex",i));
//Check Change Status
if (wtx.vShieldedSpend.size()!=0) {
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
nullifierSet = pwalletMain->GetNullifiersForAddresses({addr});
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
if (nullifierSet.count(std::make_pair(addr, spendDesc.nullifier))) {
changeTx = true;
}
}
}
obj.push_back(Pair("change",changeTx));
if (addr == zsAddress || !filterByAddress) {
totalReceived += CAmount(note.value());
zsReceived.push_back(obj);
}
}
}
}
}
}
received.push_back(Pair("saplingReceived",zsReceived));
received.push_back(Pair("totalReceived",ValueFromAmount(totalReceived)));
}
void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress) {
LOCK2(cs_main, pwalletMain->cs_wallet);
//Used to identify incomplete key sets
int shieldedOutputCount = 0;
int shieldedOutputDecryptedCount = 0;
//Check address
bool isTAddress = false;
bool isZsAddress = false;
CTxDestination tAddress = DecodeDestination(strAddress);
auto zAddress = DecodePaymentAddress(strAddress);
SaplingPaymentAddress zsAddress;
if (filterByAddress) {
if (IsValidDestination(tAddress))
isTAddress = true;
if (IsValidPaymentAddress(zAddress)) {
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
zsAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
isZsAddress = true;
}
}
}
//All Transparent Sends in the transaction
UniValue tSends(UniValue::VARR);
if (isTAddress || !filterByAddress) {
for (int i = 0; i < wtx.vout.size(); i++) {
const CTxOut& txout = wtx.vout[i];
UniValue obj(UniValue::VOBJ);
CTxDestination address;
ExtractDestination(txout.scriptPubKey, address);
obj.push_back(Pair("address",EncodeDestination(address)));
obj.push_back(Pair("scriptPubKey",HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end())));
obj.push_back(Pair("amount",ValueFromAmount(-txout.nValue)));
obj.push_back(Pair("vout", i));
if (address == tAddress || !filterByAddress) {
totalSends += CAmount(-txout.nValue);
tSends.push_back(obj);
}
}
}
sends.push_back(Pair("transparentSends",tSends));
//All Shielded Sends in the transaction
UniValue zsSends(UniValue::VARR);
if (isZsAddress || !filterByAddress) {
for (int i = 0; i < wtx.vShieldedOutput.size(); i++) {
const OutputDescription& outputDesc = wtx.vShieldedOutput[i];
shieldedOutputCount++;
UniValue obj(UniValue::VOBJ);
bool changeTx = false;
//Decrypt sapling outgoing t to z transaction using HDseed
if (wtx.vShieldedSpend.size()==0) {
HDSeed seed;
if (pwalletMain->GetHDSeed(seed)) {
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
outputDesc.outCiphertext,ovkForShieldingFromTaddr(seed),outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
if (opt) {
auto opt_unwrapped = opt.get();
auto pt = libzcash::SaplingNotePlaintext::decrypt(
outputDesc.encCiphertext,outputDesc.ephemeralKey,opt_unwrapped.esk,opt_unwrapped.pk_d,outputDesc.cm);
if (pt) {
shieldedOutputDecryptedCount++;
auto pt_unwrapped = pt.get();
libzcash::SaplingPaymentAddress sentAddr(pt_unwrapped.d, opt_unwrapped.pk_d);
obj.push_back(Pair("address",EncodePaymentAddress(sentAddr)));
obj.push_back(Pair("amount", ValueFromAmount(CAmount(pt_unwrapped.value()))));
obj.push_back(Pair("shieldedOutputIndex",i));
obj.push_back(Pair("change",false));
if (sentAddr == zsAddress || !filterByAddress) {
totalSends += CAmount(-pt_unwrapped.value());
zsSends.push_back(obj);
}
}
}
}
//attempt Decryption of Outgoing Sapling using wallet extended spending keys
} else {
std::set<libzcash::SaplingPaymentAddress> addresses;
pwalletMain->GetSaplingPaymentAddresses(addresses);
for (auto addr : addresses) {
libzcash::SaplingExtendedSpendingKey extsk;
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
auto opt = libzcash::SaplingOutgoingPlaintext::decrypt(
outputDesc.outCiphertext,extsk.expsk.full_viewing_key().ovk,outputDesc.cv,outputDesc.cm,outputDesc.ephemeralKey);
if (opt) {
auto opt_unwrapped = opt.get();
auto pt = libzcash::SaplingNotePlaintext::decrypt(
outputDesc.encCiphertext,outputDesc.ephemeralKey,opt_unwrapped.esk,opt_unwrapped.pk_d,outputDesc.cm);
if (pt) {
auto pt_unwrapped = pt.get();
auto memo = pt_unwrapped.memo();
shieldedOutputDecryptedCount++;
libzcash::SaplingPaymentAddress sentAddr(pt_unwrapped.d, opt_unwrapped.pk_d);
obj.push_back(Pair("address",EncodePaymentAddress(sentAddr)));
obj.push_back(Pair("amount", ValueFromAmount(CAmount(-pt_unwrapped.value()))));
obj.push_back(Pair("memo", HexStr(memo)));
if (memo[0] <= 0xf4) {
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; });
std::string memoStr(memo.begin(), end.base());
if (utf8::is_valid(memoStr)) {
obj.push_back(Pair("memoStr", memoStr));
}
}
obj.push_back(Pair("shieldedOutputIndex",i));
//Check Change Status
if (wtx.vShieldedSpend.size()!=0) {
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
nullifierSet = pwalletMain->GetNullifiersForAddresses({sentAddr});
BOOST_FOREACH(const SpendDescription& spendDesc, wtx.vShieldedSpend) {
if (nullifierSet.count(std::make_pair(sentAddr, spendDesc.nullifier))) {
changeTx = true;
}
}
}
obj.push_back(Pair("change",changeTx));
if (sentAddr == zsAddress || !filterByAddress) {
totalSends += CAmount(-pt_unwrapped.value());
zsSends.push_back(obj);
}
}
}
}
}
}
}
}
sends.push_back(Pair("saplingSends",zsSends));
if (shieldedOutputCount != shieldedOutputDecryptedCount) {
sends.push_back(Pair("missingSaplingOVK", true));
} else {
sends.push_back(Pair("missingSaplingOVK", false));
}
sends.push_back(Pair("totalSends",ValueFromAmount(totalSends)));
}
void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType) {
LOCK2(cs_main, pwalletMain->cs_wallet);
//Track total wallet spend and received
CAmount totalSpends = 0;
CAmount filteredSpends = 0;
CAmount totalReceived = 0;
CAmount totalSends = 0;
//Various Univalue to be added to the final transaction
UniValue spends(UniValue::VOBJ);
UniValue received(UniValue::VOBJ);
UniValue sends(UniValue::VOBJ);
UniValue tx(UniValue::VOBJ);
//Begin Compiling the Decrypted Transaction
tx.push_back(Pair("txid", wtx.GetHash().ToString()));
if (wtx.IsCoinBase())
{
tx.push_back(Pair("coinbase", true));
if (wtx.GetDepthInMainChain() < 1)
tx.push_back(Pair("category", "orphan"));
else if (wtx.GetBlocksToMaturity() > 0)
tx.push_back(Pair("category", "immature"));
else
tx.push_back(Pair("category", "generate"));
} else {
tx.push_back(Pair("coinbase", false));
tx.push_back(Pair("category", "standard"));
}
tx.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
tx.push_back(Pair("blockindex", wtx.nIndex));
int confirms = wtx.GetDepthInMainChain();
if(confirms > 0)
{
tx.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
} else {
tx.push_back(Pair("blocktime", 0));
}
tx.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight));
tx.push_back(Pair("confirmations", confirms));
tx.push_back(Pair("time", wtx.GetTxTime()));
tx.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION))));
//Wallet Conflicts
UniValue conflicts(UniValue::VARR);
BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts())
conflicts.push_back(conflict.GetHex());
tx.push_back(Pair("walletconflicts", conflicts));
// Return Type used to determine what is included in the transaction
// 0 Spends, Received and spends
// 1 Sends
// 2 Received
// 3 Spends
// TODO - Setup Enum, maybe...
// Add Spends retrieved earlier
if (returnType != 2) {
zsTxSpendsToJSON(wtx, spends, totalSpends, filteredSpends, strAddress, fBool);
if ((!fBool || filteredSpends != 0) && (returnType == 0 || returnType == 1)) {
tx.push_back(Pair("spends",spends));
}
}
// Get Received
if (returnType == 0 || returnType == 2) {
zsTxReceivedToJSON(wtx, received, totalReceived, strAddress, fBool);
if (!fBool || totalReceived != 0) {
tx.push_back(Pair("received",received));
}
}
// Get Sends
if (returnType == 0 || returnType == 3) {
//Only include sends if there are spends that belong to the wallet.
if (totalSpends != 0 || fBool) {
zsTxSendsToJSON(wtx, sends, totalSends, strAddress, fBool);
}
if (!fBool || totalSends != 0) {
tx.push_back(Pair("sends",sends));
}
}
if ((returnType == 0 && (!fBool || filteredSpends != 0 || totalReceived != 0 || totalSends != 0))
|| (returnType == 1 && (!fBool || filteredSpends != 0))
|| (returnType == 2 && (!fBool || totalReceived != 0))
|| (returnType == 3 && (!fBool || totalSends != 0))) {
ret.push_back(tx);
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2020 The Hush developers
// Copyright (c) 2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_RPCHUSHWALLET_H
#define BITCOIN_WALLET_RPCHUSHWALLET_H
struct balancestruct {
CAmount confirmed;
CAmount unconfirmed;
CAmount locked;
CAmount immature;
};
void zsTxSpendsToJSON(const CWalletTx& wtx, UniValue& spends, CAmount& totalSpends, CAmount& filteredSpends, const std::string& strAddress, bool filterByAddress);
void zsTxReceivedToJSON(const CWalletTx& wtx, UniValue& received, CAmount& totalReceived, const std::string& strAddress, bool filterByAddress);
void zsTxSendsToJSON(const CWalletTx& wtx, UniValue& sends, CAmount& totalSends, const std::string& strAddress, bool filterByAddress);
void zsWalletTxJSON(const CWalletTx& wtx, UniValue& ret, const std::string strAddress, bool fBool, const int returnType);
#endif //BITCOIN_WALLET_RPCWALLET_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,7 @@
#define BITCOIN_WALLET_WALLET_H
#include "amount.h"
#include "asyncrpcoperation.h"
#include "coins.h"
#include "key.h"
#include "keystore.h"
@@ -60,6 +61,11 @@ extern unsigned int nTxConfirmTarget;
extern bool bSpendZeroConfChange;
extern bool fSendFreeTransactions;
extern bool fPayAtLeastCustomFee;
extern bool fTxDeleteEnabled;
extern bool fTxConflictDeleteEnabled;
extern int fDeleteInterval;
extern unsigned int fDeleteTransactionsAfterNBlocks;
extern unsigned int fKeepLastNTransactions;
//! -paytxfee default
@@ -82,6 +88,18 @@ extern unsigned int WITNESS_CACHE_SIZE;
//! Size of HD seed in bytes
static const size_t HD_WALLET_SEED_LENGTH = 32;
//Default Transaction Rentention N-BLOCKS
static const int DEFAULT_TX_DELETE_INTERVAL = 1000;
//Default Transaction Rentention N-BLOCKS
static const unsigned int DEFAULT_TX_RETENTION_BLOCKS = 10000;
//Default Retention Last N-Transactions
static const unsigned int DEFAULT_TX_RETENTION_LASTTX = 200;
//Amount of transactions to delete per run while syncing
static const int MAX_DELETE_TX_SIZE = 50000;
class CBlockIndex;
class CCoinControl;
class COutput;
@@ -218,9 +236,10 @@ public:
std::string ToString() const;
};
// NOTE: wallet.dat format depends on this data structure :(
class SproutNoteData
{
public:
{
public:
libzcash::SproutPaymentAddress address;
/**
@@ -246,20 +265,14 @@ public:
/**
* Block height corresponding to the most current witness.
*
* When we first create a SproutNoteData in CWallet::FindMySproutNotes, this is set to
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
* determine what height the witness cache for this note is valid for (even
* if no witnesses were cached), and so can set the correct value in
* CWallet::IncrementNoteWitnesses and CWallet::DecrementNoteWitnesses.
* CWallet::BuildWitnessCache and CWallet::DecrementNoteWitnesses.
*/
int witnessHeight;
SproutNoteData() : address(), nullifier(), witnessHeight {-1} { }
SproutNoteData(libzcash::SproutPaymentAddress a) :
address {a}, nullifier(), witnessHeight {-1} { }
SproutNoteData(libzcash::SproutPaymentAddress a, uint256 n) :
address {a}, nullifier {n}, witnessHeight {-1} { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
@@ -270,27 +283,22 @@ public:
READWRITE(witnessHeight);
}
friend bool operator<(const SproutNoteData& a, const SproutNoteData& b) {
return (a.address < b.address ||
(a.address == b.address && a.nullifier < b.nullifier));
}
friend bool operator==(const SproutNoteData& a, const SproutNoteData& b) {
return (a.address == b.address && a.nullifier == b.nullifier);
}
friend bool operator!=(const SproutNoteData& a, const SproutNoteData& b) {
return !(a == b);
}
};
class SaplingNoteData
{
public:
/**
* We initialize the height to -1 for the same reason as we do in SproutNoteData.
* See the comment in that class for a full description.
* Block height corresponding to the most current witness.
*
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
* determine what height the witness cache for this note is valid for (even
* if no witnesses were cached), and so can set the correct value in
* CWallet::BuildWitnessCache and CWallet::DecrementNoteWitnesses.
*/
SaplingNoteData() : witnessHeight {-1}, nullifier() { }
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk) : ivk {ivk}, witnessHeight {-1}, nullifier() { }
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk, uint256 n) : ivk {ivk}, witnessHeight {-1}, nullifier(n) { }
@@ -300,6 +308,9 @@ public:
libzcash::SaplingIncomingViewingKey ivk;
boost::optional<uint256> nullifier;
//In Memory Only
bool witnessRootValidated;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
@@ -323,18 +334,11 @@ public:
}
};
// NOTE: this sprout structure is serialized into wallet.dat, removing it would change wallet.dat format on disk :(
typedef std::map<JSOutPoint, SproutNoteData> mapSproutNoteData_t;
typedef std::map<SaplingOutPoint, SaplingNoteData> mapSaplingNoteData_t;
/** Decrypted note, its location in a transaction, and number of confirmations. */
struct CSproutNotePlaintextEntry
{
JSOutPoint jsop;
libzcash::SproutPaymentAddress address;
libzcash::SproutNotePlaintext plaintext;
int confirmations;
};
/** Sapling note, its location in a transaction, and number of confirmations. */
struct SaplingNoteEntry
{
@@ -565,11 +569,8 @@ public:
MarkDirty();
}
void SetSproutNoteData(mapSproutNoteData_t &noteData);
void SetSaplingNoteData(mapSaplingNoteData_t &noteData);
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
JSOutPoint jsop) const;
boost::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const;
@@ -777,11 +778,12 @@ private:
* detect and report conflicts (double-spends).
*/
typedef TxSpendMap<uint256> TxNullifiers;
TxNullifiers mapTxSproutNullifiers;
TxNullifiers mapTxSaplingNullifiers;
std::vector<CTransaction> pendingSaplingConsolidationTxs;
AsyncRPCOperationId saplingConsolidationOperationId;
void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSproutSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);
@@ -793,6 +795,7 @@ public:
*/
int64_t nWitnessCacheSize;
bool needsRescan = false;
bool fSaplingConsolidationEnabled = false;
void ClearNoteWitnessCache();
@@ -800,13 +803,15 @@ public:
std::set<uint256> GetNullifiers();
protected:
int SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight);
/**
* pindex is the new tip being connected.
*/
void IncrementNoteWitnesses(const CBlockIndex* pindex,
const CBlock* pblock,
SproutMerkleTree& sproutTree,
SaplingMerkleTree& saplingTree);
int VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessOnly);
void BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly);
/**
* pindex is the old tip being disconnected.
*/
@@ -822,11 +827,11 @@ protected:
try {
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
auto wtx = wtxItem.second;
// We skip transactions for which mapSproutNoteData and mapSaplingNoteData
// are empty. This covers transactions that have no Sprout or Sapling data
// We skip transactions for which mapSaplingNoteData
// is empty. This covers transactions that have no Sapling data
// (i.e. are purely transparent), as well as shielding and unshielding
// transactions in which we only have transparent addresses involved.
if (!(wtx.mapSproutNoteData.empty() && wtx.mapSaplingNoteData.empty())) {
if (!(wtx.mapSaplingNoteData.empty())) {
if (!walletdb.WriteTx(wtxItem.first, wtx)) {
LogPrintf("SetBestChain(): Failed to write CWalletTx, aborting atomic write\n");
walletdb.TxnAbort();
@@ -884,7 +889,6 @@ public:
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
@@ -1001,8 +1005,9 @@ 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 IsSproutSpent(const uint256& nullifier) const;
unsigned int GetSpendDepth(const uint256& hash, unsigned int n) const;
bool IsSaplingSpent(const uint256& nullifier) const;
unsigned int GetSaplingSpendDepth(const uint256& nullifier) const;
bool IsLockedCoin(uint256 hash, unsigned int n) const;
void LockCoin(COutPoint& output);
@@ -1013,8 +1018,6 @@ public:
bool IsLockedNote(const JSOutPoint& outpt) const;
void LockNote(const JSOutPoint& output);
void UnlockNote(const JSOutPoint& output);
void UnlockAllSproutNotes();
std::vector<JSOutPoint> ListLockedSproutNotes();
bool IsLockedNote(const SaplingOutPoint& output) const;
void LockNote(const SaplingOutPoint& output);
@@ -1064,31 +1067,6 @@ public:
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const;
/**
* Sprout ZKeys
*/
//! Generates a new Sprout zaddr
libzcash::SproutPaymentAddress GenerateNewSproutZKey();
//! Adds spending key to the store, and saves it to disk
bool AddSproutZKey(const libzcash::SproutSpendingKey &key);
//! Adds spending key to the store, without saving it to disk (used by LoadWallet)
bool LoadZKey(const libzcash::SproutSpendingKey &key);
//! Load spending key metadata (used by LoadWallet)
bool LoadZKeyMetadata(const libzcash::SproutPaymentAddress &addr, const CKeyMetadata &meta);
//! Adds an encrypted spending key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedZKey(const libzcash::SproutPaymentAddress &addr, const libzcash::ReceivingKey &rk, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted spending key to the store, and saves it to disk (virtual method, declared in crypter.h)
bool AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret);
//! Adds a Sprout viewing key to the store, and saves it to disk.
bool AddSproutViewingKey(const libzcash::SproutViewingKey &vk);
bool RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk);
//! Adds a Sprout viewing key to the store, without saving it to disk (used by LoadWallet)
bool LoadSproutViewingKey(const libzcash::SproutViewingKey &dest);
/**
* Sapling ZKeys
*/
@@ -1139,7 +1117,7 @@ public:
bool UpdateNullifierNoteMap();
void UpdateNullifierNoteMapWithTx(const CWalletTx& wtx);
void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx);
void UpdateSaplingNullifierNoteMapForBlock(const CBlock* pblock);
void UpdateNullifierNoteMapForBlock(const CBlock* pblock);
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
void EraseFromWallet(const uint256 &hash);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
@@ -1149,6 +1127,10 @@ public:
std::vector<uint256> commitments,
std::vector<boost::optional<SproutWitness>>& witnesses,
uint256 &final_anchor);
void ReorderWalletTransactions(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, int64_t &maxOrderPos);
void UpdateWalletTransactionOrder(std::map<std::pair<int,int>, CWalletTx*> &mapSorted, bool resetOrder);
void DeleteTransactions(std::vector<uint256> &removeTxs);
void DeleteWalletTransactions(const CBlockIndex* pindex);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime);
@@ -1181,21 +1163,9 @@ public:
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
boost::optional<uint256> GetSproutNoteNullifier(
const JSDescription& jsdesc,
const libzcash::SproutPaymentAddress& address,
const ZCNoteDecryption& dec,
const uint256& hSig,
uint8_t n) const;
mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const;
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx) const;
bool IsSproutNullifierFromMe(const uint256& nullifier) const;
bool IsSaplingNullifierFromMe(const uint256& nullifier) const;
void GetSproutNoteWitnesses(
std::vector<JSOutPoint> notes,
std::vector<boost::optional<SproutWitness>>& witnesses,
uint256 &final_anchor);
void GetSaplingNoteWitnesses(
std::vector<SaplingOutPoint> notes,
std::vector<boost::optional<SaplingWitness>>& witnesses,
@@ -1215,7 +1185,12 @@ public:
CAmount GetCredit(const CTransaction& tx, int32_t voutNum, 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, SproutMerkleTree sproutTree, SaplingMerkleTree saplingTree, bool added);
void ChainTip(
const CBlockIndex *pindex,
const CBlock *pblock,
boost::optional<std::pair<SproutMerkleTree, SaplingMerkleTree>> added);
void RunSaplingConsolidation(int blockHeight);
bool CommitConsolidationTx(const CTransaction& tx);
/** Saves witness caches and best block locator to disk. */
void SetBestChain(const CBlockLocator& loc);
std::set<std::pair<libzcash::PaymentAddress, uint256>> GetNullifiersForAddresses(const std::set<libzcash::PaymentAddress> & addresses);
@@ -1241,6 +1216,13 @@ public:
}
}
//void GetScriptForMining(boost::shared_ptr<CReserveScript> &script);
void ResetRequestCount(const uint256 &hash)
{
LOCK(cs_wallet);
mapRequestCount[hash] = 0;
};
unsigned int GetKeyPoolSize()
{
AssertLockHeld(cs_wallet); // setKeyPool
@@ -1317,8 +1299,7 @@ public:
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Find notes filtered by payment address, min depth, ability to spend */
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
std::string address,
int minDepth=1,
bool ignoreSpent=true,
@@ -1326,8 +1307,7 @@ public:
/* Find notes filtered by payment addresses, min depth, max depth, if they are spent,
if a spending key is required, and if they are locked */
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
std::set<libzcash::PaymentAddress>& filterAddresses,
int minDepth=1,
int maxDepth=INT_MAX,
@@ -1406,7 +1386,6 @@ private:
public:
PaymentAddressBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@@ -1419,7 +1398,6 @@ private:
public:
IncomingViewingKeyBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@@ -1431,7 +1409,6 @@ private:
public:
HaveSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@@ -1443,7 +1420,6 @@ private:
public:
GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
};
@@ -1515,7 +1491,6 @@ public:
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {}
SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
};

View File

@@ -1,6 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -124,26 +124,6 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
return true;
}
bool CWalletDB::WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta)
{
const bool fEraseUnencryptedKey = true;
nWalletDBUpdated++;
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
return false;
if (!Write(std::make_pair(std::string("czkey"), addr), std::make_pair(rk, vchCryptedSecret), false))
return false;
if (fEraseUnencryptedKey)
{
Erase(std::make_pair(std::string("zkey"), addr));
}
return true;
}
bool CWalletDB::WriteCryptedSaplingZKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char>& vchCryptedSecret,
@@ -172,16 +152,6 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
}
bool CWalletDB::WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta)
{
nWalletDBUpdated++;
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
return false;
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
return Write(std::make_pair(std::string("zkey"), addr), key, false);
}
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingExtendedSpendingKey &key,
const CKeyMetadata &keyMeta)
@@ -203,18 +173,6 @@ bool CWalletDB::WriteSaplingPaymentAddress(
return Write(std::make_pair(std::string("sapzaddr"), addr), ivk, false);
}
bool CWalletDB::WriteSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("vkey"), vk), '1');
}
bool CWalletDB::EraseSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
nWalletDBUpdated++;
return Erase(std::make_pair(std::string("vkey"), vk));
}
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
nWalletDBUpdated++;
@@ -552,6 +510,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "vkey")
{
/*
libzcash::SproutViewingKey vk;
ssKey >> vk;
char fYes;
@@ -559,24 +518,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
if (fYes == '1')
pwallet->LoadSproutViewingKey(vk);
// Viewing keys have no birthday information for now,
// so set the wallet birthday to the beginning of time.
Viewing keys have no birthday information for now,
so set the wallet birthday to the beginning of time.
pwallet->nTimeFirstKey = 1;
*/
}
else if (strType == "zkey")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
libzcash::SproutSpendingKey key;
ssValue >> key;
if (!pwallet->LoadZKey(key))
{
strErr = "Error reading wallet database: LoadZKey failed";
return false;
}
//if (!pwallet->LoadZKey(key))
//{
// strErr = "Error reading wallet database: LoadZKey failed";
// return false;
//}
wss.nZKeys++;
*/
}
else if (strType == "sapzkey")
{
@@ -691,6 +653,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "czkey")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
// Deserialization of a pair is just one item after another
@@ -707,6 +670,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
wss.fIsEncrypted = true;
*/
}
else if (strType == "csapzkey")
{
@@ -742,13 +706,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "zkeymeta")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nZKeyMeta++;
*/
pwallet->LoadZKeyMetadata(addr, keyMeta);
// pwallet->LoadZKeyMetadata(addr, keyMeta);
// ignore earliest key creation time as taddr will exist before any zaddr
}
@@ -1208,6 +1174,12 @@ bool BackupWallet(const CWallet& wallet, const string& strDest)
return false;
}
bool CWalletDB::Compact(CDBEnv& dbenv, const std::string& strFile)
{
bool fSuccess = dbenv.Compact(strFile);
return fSuccess;
}
//
// Try to (very carefully!) recover wallet.dat if there is a problem.
//

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin Core developers
// Copyright (c) 2009-2013 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@@ -189,6 +190,7 @@ public:
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
static bool Compact(CDBEnv& dbenv, const std::string& strFile);
static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, const std::string& filename);
@@ -198,23 +200,15 @@ public:
bool WriteHDChain(const CHDChain& chain);
/// Write spending key to wallet database, where key is payment address and value is spending key.
bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta);
bool WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingExtendedSpendingKey &key,
const CKeyMetadata &keyMeta);
bool WriteSaplingPaymentAddress(const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingIncomingViewingKey &ivk);
bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
const libzcash::ReceivingKey & rk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta);
bool WriteCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta);
bool WriteSproutViewingKey(const libzcash::SproutViewingKey &vk);
bool EraseSproutViewingKey(const libzcash::SproutViewingKey &vk);
private:
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);