Auto merge of #2440 - bitcartel:1360_reduce_info_leakage_vpub_new, r=str4d
Fix intermediate vpub_new leakage in multi joinsplit tx Resolves information leakage discussed here https://github.com/zcash/zcash/issues/1360#issuecomment-284561331 Example output in debug.log showing how intermediate vpub_new is now 0 https://gist.github.com/bitcartel/86ca316b74f0b1ec6d8de3ac132006da When reviewing, note that the indentation of a large chunk of code (scenario 3 part 2) changed, so use `git diff -w` (or similar) or append `?w=1` to the Github URL e.g. https://github.com/zcash/zcash/pull/2440/files?w=1
This commit is contained in:
@@ -342,9 +342,14 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
tx_ = CTransaction(mtx);
|
tx_ = CTransaction(mtx);
|
||||||
|
|
||||||
// Copy zinputs and zoutputs to more flexible containers
|
// Copy zinputs and zoutputs to more flexible containers
|
||||||
std::deque<SendManyInputJSOP> zInputsDeque;
|
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
|
||||||
|
CAmount tmp = 0;
|
||||||
for (auto o : z_inputs_) {
|
for (auto o : z_inputs_) {
|
||||||
zInputsDeque.push_back(o);
|
zInputsDeque.push_back(o);
|
||||||
|
tmp += std::get<2>(o);
|
||||||
|
if (tmp >= targetAmount) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
std::deque<SendManyRecipient> zOutputsDeque;
|
std::deque<SendManyRecipient> zOutputsDeque;
|
||||||
for (auto o : z_outputs_) {
|
for (auto o : z_outputs_) {
|
||||||
@@ -442,283 +447,216 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
* zaddr -> taddrs
|
* zaddr -> taddrs
|
||||||
* -> zaddrs
|
* -> zaddrs
|
||||||
*
|
*
|
||||||
* Processing order:
|
* Send to zaddrs by chaining JoinSplits together and immediately consuming any change
|
||||||
* Part 1: taddrs and miners fee
|
* Send to taddrs by creating dummy z outputs and accumulating value in a change note
|
||||||
* Part 2: zaddrs
|
* which is used to set vpub_new in the last chained joinsplit.
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SCENARIO #3
|
|
||||||
* Part 1: Add to the transparent value pool.
|
|
||||||
*/
|
*/
|
||||||
UniValue obj(UniValue::VOBJ);
|
UniValue obj(UniValue::VOBJ);
|
||||||
CAmount jsChange = 0; // this is updated after each joinsplit
|
CAmount jsChange = 0; // this is updated after each joinsplit
|
||||||
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
|
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
|
||||||
bool minersFeeProcessed = false;
|
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) {
|
if (t_outputs_total > 0) {
|
||||||
add_taddr_outputs_to_tx();
|
add_taddr_outputs_to_tx();
|
||||||
CAmount taddrTargetAmount = t_outputs_total + minersFee;
|
vpubNewTarget += t_outputs_total;
|
||||||
minersFeeProcessed = true;
|
|
||||||
while (zInputsDeque.size() > 0 && taddrTargetAmount > 0) {
|
|
||||||
AsyncJoinSplitInfo info;
|
|
||||||
info.vpub_old = 0;
|
|
||||||
info.vpub_new = 0;
|
|
||||||
std::vector<JSOutPoint> outPoints;
|
|
||||||
int n = 0;
|
|
||||||
while (n++ < ZC_NUM_JS_INPUTS && taddrTargetAmount > 0) {
|
|
||||||
SendManyInputJSOP o = zInputsDeque.front();
|
|
||||||
JSOutPoint outPoint = std::get<0>(o);
|
|
||||||
Note note = std::get<1>(o);
|
|
||||||
CAmount noteFunds = std::get<2>(o);
|
|
||||||
zInputsDeque.pop_front();
|
|
||||||
|
|
||||||
info.notes.push_back(note);
|
|
||||||
outPoints.push_back(outPoint);
|
|
||||||
|
|
||||||
int wtxHeight = -1;
|
|
||||||
int wtxDepth = -1;
|
|
||||||
{
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
||||||
const CWalletTx& wtx = pwalletMain->mapWallet[outPoint.hash];
|
|
||||||
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
|
||||||
wtxDepth = wtx.GetDepthInMainChain();
|
|
||||||
}
|
|
||||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
|
|
||||||
getId(),
|
|
||||||
outPoint.hash.ToString().substr(0, 10),
|
|
||||||
outPoint.js,
|
|
||||||
int(outPoint.n), // uint8_t
|
|
||||||
FormatMoney(noteFunds),
|
|
||||||
wtxHeight,
|
|
||||||
wtxDepth
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Put value back into the value pool
|
|
||||||
if (noteFunds >= taddrTargetAmount) {
|
|
||||||
jsChange = noteFunds - taddrTargetAmount;
|
|
||||||
info.vpub_new += taddrTargetAmount;
|
|
||||||
} else {
|
|
||||||
info.vpub_new += noteFunds;
|
|
||||||
}
|
|
||||||
|
|
||||||
taddrTargetAmount -= noteFunds;
|
|
||||||
if (taddrTargetAmount <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsChange > 0) {
|
|
||||||
info.vjsout.push_back(JSOutput());
|
|
||||||
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
|
|
||||||
|
|
||||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
|
||||||
getId(),
|
|
||||||
FormatMoney(jsChange)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj = perform_joinsplit(info, outPoints);
|
|
||||||
|
|
||||||
if (jsChange > 0) {
|
|
||||||
changeOutputIndex = find_output(obj, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep track of treestate within this transaction
|
||||||
|
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
|
||||||
|
std::vector<uint256> previousCommitments;
|
||||||
|
|
||||||
/**
|
while (!vpubNewProcessed) {
|
||||||
* SCENARIO #3
|
AsyncJoinSplitInfo info;
|
||||||
* Part 2: Send to zaddrs by chaining JoinSplits together and immediately consuming any change
|
info.vpub_old = 0;
|
||||||
*/
|
info.vpub_new = 0;
|
||||||
if (z_outputs_total>0) {
|
|
||||||
|
|
||||||
// Keep track of treestate within this transaction
|
CAmount jsInputValue = 0;
|
||||||
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
|
uint256 jsAnchor;
|
||||||
std::vector<uint256> previousCommitments;
|
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
||||||
|
|
||||||
while (zOutputsDeque.size() > 0) {
|
JSDescription prevJoinSplit;
|
||||||
AsyncJoinSplitInfo info;
|
|
||||||
info.vpub_old = 0;
|
|
||||||
info.vpub_new = 0;
|
|
||||||
|
|
||||||
CAmount jsInputValue = 0;
|
// Keep track of previous JoinSplit and its commitments
|
||||||
uint256 jsAnchor;
|
if (tx_.vjoinsplit.size() > 0) {
|
||||||
std::vector<boost::optional<ZCIncrementalWitness>> witnesses;
|
prevJoinSplit = tx_.vjoinsplit.back();
|
||||||
|
}
|
||||||
|
|
||||||
JSDescription prevJoinSplit;
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
// Keep track of previous JoinSplit and its commitments
|
//
|
||||||
if (tx_.vjoinsplit.size() > 0) {
|
// Consume change as the first input of the JoinSplit.
|
||||||
prevJoinSplit = tx_.vjoinsplit.back();
|
//
|
||||||
|
if (jsChange > 0) {
|
||||||
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
|
||||||
|
// Update tree state with previous joinsplit
|
||||||
|
ZCIncrementalMerkleTree tree;
|
||||||
|
auto it = intermediates.find(prevJoinSplit.anchor);
|
||||||
|
if (it != intermediates.end()) {
|
||||||
|
tree = it->second;
|
||||||
|
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) {
|
||||||
|
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no change, the chain has terminated so we can reset the tracked treestate.
|
assert(changeOutputIndex != -1);
|
||||||
if (jsChange==0 && tx_.vjoinsplit.size() > 0) {
|
boost::optional<ZCIncrementalWitness> changeWitness;
|
||||||
intermediates.clear();
|
int n = 0;
|
||||||
previousCommitments.clear();
|
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(spendingkey_.viewing_key());
|
||||||
|
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
||||||
|
try {
|
||||||
|
NotePlaintext plaintext = NotePlaintext::decrypt(
|
||||||
|
decryptor,
|
||||||
|
prevJoinSplit.ciphertexts[changeOutputIndex],
|
||||||
|
prevJoinSplit.ephemeralKey,
|
||||||
|
hSig,
|
||||||
|
(unsigned char) changeOutputIndex);
|
||||||
|
|
||||||
|
Note note = plaintext.note(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<Note> vInputNotes;
|
||||||
|
std::vector<JSOutPoint> vOutPoints;
|
||||||
|
std::vector<boost::optional<ZCIncrementalWitness>> 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);
|
||||||
|
Note 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);
|
||||||
// Consume change as the first input of the JoinSplit.
|
vInputNotes.push_back(note);
|
||||||
//
|
|
||||||
if (jsChange > 0) {
|
jsInputValue += noteFunds;
|
||||||
|
|
||||||
|
int wtxHeight = -1;
|
||||||
|
int wtxDepth = -1;
|
||||||
|
{
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||||
|
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
|
||||||
|
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Update tree state with previous joinsplit
|
// Add history of previous commitments to witness
|
||||||
ZCIncrementalMerkleTree tree;
|
if (vInputNotes.size() > 0) {
|
||||||
auto it = intermediates.find(prevJoinSplit.anchor);
|
|
||||||
if (it != intermediates.end()) {
|
|
||||||
tree = it->second;
|
|
||||||
} else if (!pcoinsTip->GetAnchorAt(prevJoinSplit.anchor, tree)) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(changeOutputIndex != -1);
|
if (vInputWitnesses.size()==0) {
|
||||||
boost::optional<ZCIncrementalWitness> changeWitness;
|
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
|
||||||
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(spendingkey_.viewing_key());
|
|
||||||
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
|
|
||||||
try {
|
|
||||||
NotePlaintext plaintext = NotePlaintext::decrypt(
|
|
||||||
decryptor,
|
|
||||||
prevJoinSplit.ciphertexts[changeOutputIndex],
|
|
||||||
prevJoinSplit.ephemeralKey,
|
|
||||||
hSig,
|
|
||||||
(unsigned char) changeOutputIndex);
|
|
||||||
|
|
||||||
Note note = plaintext.note(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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto & optionalWitness : vInputWitnesses) {
|
||||||
//
|
if (!optionalWitness) {
|
||||||
// Consume spendable non-change notes
|
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
|
||||||
//
|
|
||||||
std::vector<Note> vInputNotes;
|
|
||||||
std::vector<JSOutPoint> vOutPoints;
|
|
||||||
std::vector<boost::optional<ZCIncrementalWitness>> 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);
|
|
||||||
Note 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");
|
|
||||||
}
|
}
|
||||||
|
ZCIncrementalWitness w = *optionalWitness; // could use .get();
|
||||||
vOutPoints.push_back(jso);
|
if (jsChange > 0) {
|
||||||
vInputNotes.push_back(note);
|
for (const uint256& commitment : previousCommitments) {
|
||||||
|
w.append(commitment);
|
||||||
jsInputValue += noteFunds;
|
}
|
||||||
|
if (jsAnchor != w.root()) {
|
||||||
int wtxHeight = -1;
|
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
|
||||||
int wtxDepth = -1;
|
}
|
||||||
{
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
||||||
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
|
|
||||||
wtxHeight = mapBlockIndex[wtx.hashBlock]->nHeight;
|
|
||||||
wtxDepth = wtx.GetDepthInMainChain();
|
|
||||||
}
|
}
|
||||||
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
|
witnesses.push_back(w);
|
||||||
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
|
// The jsAnchor is null if this JoinSplit is at the start of a new chain
|
||||||
if (vInputNotes.size() > 0) {
|
if (jsAnchor.IsNull()) {
|
||||||
|
jsAnchor = inputAnchor;
|
||||||
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");
|
|
||||||
}
|
|
||||||
ZCIncrementalWitness 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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add spendable notes as inputs
|
||||||
|
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
|
||||||
|
}
|
||||||
|
|
||||||
//
|
// Find recipient to transfer funds to
|
||||||
// Find recipient to transfer funds to
|
std::string address, hexMemo;
|
||||||
//
|
CAmount value = 0;
|
||||||
|
if (zOutputsDeque.size() > 0) {
|
||||||
SendManyRecipient smr = zOutputsDeque.front();
|
SendManyRecipient smr = zOutputsDeque.front();
|
||||||
std::string address = std::get<0>(smr);
|
address = std::get<0>(smr);
|
||||||
CAmount value = std::get<1>(smr);
|
value = std::get<1>(smr);
|
||||||
std::string hexMemo = std::get<2>(smr);
|
hexMemo = std::get<2>(smr);
|
||||||
zOutputsDeque.pop_front();
|
zOutputsDeque.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
// Will we have any change? Has the miners fee been processed yet?
|
// Reset change
|
||||||
jsChange = 0;
|
jsChange = 0;
|
||||||
CAmount outAmount = value;
|
CAmount outAmount = value;
|
||||||
if (!minersFeeProcessed) {
|
|
||||||
if (jsInputValue < minersFee) {
|
// Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy)
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Not enough funds to pay miners fee");
|
if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) {
|
||||||
}
|
assert(!vpubNewProcessed);
|
||||||
outAmount += minersFee;
|
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) {
|
if (jsInputValue > outAmount) {
|
||||||
jsChange = jsInputValue - outAmount;
|
jsChange = jsInputValue - outAmount;
|
||||||
} else if (outAmount > jsInputValue) {
|
} else if (outAmount > jsInputValue) {
|
||||||
@@ -729,42 +667,44 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
|||||||
|
|
||||||
// reduce the amount being sent right now to the value of all inputs
|
// reduce the amount being sent right now to the value of all inputs
|
||||||
value = jsInputValue;
|
value = jsInputValue;
|
||||||
if (!minersFeeProcessed) {
|
|
||||||
value -= minersFee;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!minersFeeProcessed) {
|
// create output for recipient
|
||||||
minersFeeProcessed = true;
|
if (address.empty()) {
|
||||||
info.vpub_new += minersFee; // funds flowing back to public pool
|
assert(value==0);
|
||||||
}
|
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
|
||||||
|
} else {
|
||||||
// create output for recipient
|
|
||||||
PaymentAddress pa = CZCPaymentAddress(address).Get();
|
PaymentAddress pa = CZCPaymentAddress(address).Get();
|
||||||
JSOutput jso = JSOutput(pa, value);
|
JSOutput jso = JSOutput(pa, value);
|
||||||
if (hexMemo.size() > 0) {
|
if (hexMemo.size() > 0) {
|
||||||
jso.memo = get_memo_from_hex_string(hexMemo);
|
jso.memo = get_memo_from_hex_string(hexMemo);
|
||||||
}
|
}
|
||||||
info.vjsout.push_back(jso);
|
info.vjsout.push_back(jso);
|
||||||
|
}
|
||||||
|
|
||||||
// create output for any change
|
// create output for any change
|
||||||
if (jsChange>0) {
|
if (jsChange>0) {
|
||||||
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
|
info.vjsout.push_back(JSOutput(frompaymentaddress_, jsChange));
|
||||||
|
|
||||||
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
|
||||||
getId(),
|
getId(),
|
||||||
FormatMoney(jsChange)
|
FormatMoney(jsChange)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
obj = perform_joinsplit(info, witnesses, jsAnchor);
|
||||||
|
|
||||||
if (jsChange > 0) {
|
if (jsChange > 0) {
|
||||||
changeOutputIndex = find_output(obj, 1);
|
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);
|
sign_send_raw_transaction(obj);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user