desprout
This commit is contained in:
@@ -429,9 +429,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
* END SCENARIO #0
|
||||
*/
|
||||
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
||||
/**
|
||||
* SCENARIO #1
|
||||
*
|
||||
@@ -440,6 +437,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
* There are no zaddrs or joinsplits involved.
|
||||
*/
|
||||
if (isPureTaddrOnlyTx) {
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("rawtxn", EncodeHexTx(tx_)));
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
@@ -448,8 +446,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||
* END SCENARIO #1
|
||||
*/
|
||||
|
||||
sign_send_raw_transaction(obj);
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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. *
|
||||
@@ -566,430 +566,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1143,19 +720,6 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
for (auto entry : saplingEntries) {
|
||||
z_sapling_inputs_.push_back(entry);
|
||||
std::string data(entry.memo.begin(), entry.memo.end());
|
||||
@@ -1167,15 +731,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,189 +740,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(
|
||||
*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_);
|
||||
|
||||
Reference in New Issue
Block a user