2018-07-17 09:36:38 -07:00
|
|
|
// Copyright (c) 2018 The Zcash developers
|
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2019-07-18 07:16:09 -07:00
|
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
2018-07-17 09:36:38 -07:00
|
|
|
|
|
|
|
#include "transaction_builder.h"
|
|
|
|
|
|
|
|
#include "main.h"
|
2018-07-30 03:03:29 -07:00
|
|
|
#include "pubkey.h"
|
2018-10-30 13:12:40 -07:00
|
|
|
#include "rpc/protocol.h"
|
2018-07-30 03:03:29 -07:00
|
|
|
#include "script/sign.h"
|
2018-10-12 17:36:26 -07:00
|
|
|
#include "utilmoneystr.h"
|
2020-06-18 20:10:19 -07:00
|
|
|
#include "zcash/Note.hpp"
|
2018-07-17 09:36:38 -07:00
|
|
|
|
|
|
|
#include <boost/variant.hpp>
|
|
|
|
#include <librustzcash.h>
|
|
|
|
|
|
|
|
SpendDescriptionInfo::SpendDescriptionInfo(
|
2018-07-30 06:26:29 -07:00
|
|
|
libzcash::SaplingExpandedSpendingKey expsk,
|
2018-07-17 09:36:38 -07:00
|
|
|
libzcash::SaplingNote note,
|
|
|
|
uint256 anchor,
|
2018-08-01 09:41:36 -07:00
|
|
|
SaplingWitness witness) : expsk(expsk), note(note), anchor(anchor), witness(witness)
|
2018-07-17 09:36:38 -07:00
|
|
|
{
|
|
|
|
librustzcash_sapling_generate_r(alpha.begin());
|
|
|
|
}
|
|
|
|
|
2019-05-17 09:39:30 -07:00
|
|
|
boost::optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
|
|
|
|
auto cmu = this->note.cmu();
|
|
|
|
if (!cmu) {
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
libzcash::SaplingNotePlaintext notePlaintext(this->note, this->memo);
|
|
|
|
|
|
|
|
auto res = notePlaintext.encrypt(this->note.pk_d);
|
|
|
|
if (!res) {
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
auto enc = res.get();
|
|
|
|
auto encryptor = enc.second;
|
|
|
|
|
|
|
|
libzcash::SaplingPaymentAddress address(this->note.d, this->note.pk_d);
|
|
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
|
|
ss << address;
|
|
|
|
std::vector<unsigned char> addressBytes(ss.begin(), ss.end());
|
|
|
|
|
|
|
|
OutputDescription odesc;
|
2020-06-17 13:53:12 -07:00
|
|
|
uint256 rcm = this->note.rcm();
|
2019-05-17 09:39:30 -07:00
|
|
|
if (!librustzcash_sapling_output_proof(
|
|
|
|
ctx,
|
|
|
|
encryptor.get_esk().begin(),
|
|
|
|
addressBytes.data(),
|
2020-06-17 13:53:12 -07:00
|
|
|
rcm.begin(),
|
2019-05-17 09:39:30 -07:00
|
|
|
this->note.value(),
|
|
|
|
odesc.cv.begin(),
|
|
|
|
odesc.zkproof.begin())) {
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
odesc.cmu = *cmu;
|
|
|
|
odesc.ephemeralKey = encryptor.get_epk();
|
|
|
|
odesc.encCiphertext = enc.first;
|
|
|
|
|
|
|
|
libzcash::SaplingOutgoingPlaintext outPlaintext(this->note.pk_d, encryptor.get_esk());
|
|
|
|
odesc.outCiphertext = outPlaintext.encrypt(
|
|
|
|
this->ovk,
|
|
|
|
odesc.cv,
|
|
|
|
odesc.cmu,
|
|
|
|
encryptor);
|
|
|
|
|
|
|
|
return odesc;
|
|
|
|
}
|
|
|
|
|
2018-10-30 13:12:40 -07:00
|
|
|
TransactionBuilderResult::TransactionBuilderResult(const CTransaction& tx) : maybeTx(tx) {}
|
|
|
|
|
|
|
|
TransactionBuilderResult::TransactionBuilderResult(const std::string& error) : maybeError(error) {}
|
|
|
|
|
|
|
|
bool TransactionBuilderResult::IsTx() { return maybeTx != boost::none; }
|
|
|
|
|
|
|
|
bool TransactionBuilderResult::IsError() { return maybeError != boost::none; }
|
|
|
|
|
|
|
|
CTransaction TransactionBuilderResult::GetTxOrThrow() {
|
|
|
|
if (maybeTx) {
|
|
|
|
return maybeTx.get();
|
|
|
|
} else {
|
|
|
|
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build transaction: " + GetError());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string TransactionBuilderResult::GetError() {
|
|
|
|
if (maybeError) {
|
|
|
|
return maybeError.get();
|
|
|
|
} else {
|
|
|
|
// This can only happen if isTx() is true in which case we should not call getError()
|
|
|
|
throw std::runtime_error("getError() was called in TransactionBuilderResult, but the result was not initialized as an error.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
TransactionBuilder::TransactionBuilder(
|
2018-07-27 00:49:43 -07:00
|
|
|
const Consensus::Params& consensusParams,
|
2018-07-30 03:03:29 -07:00
|
|
|
int nHeight,
|
2018-10-12 17:36:26 -07:00
|
|
|
CKeyStore* keystore,
|
|
|
|
ZCJoinSplit* sproutParams,
|
|
|
|
CCoinsViewCache* coinsView,
|
|
|
|
CCriticalSection* cs_coinsView) :
|
|
|
|
consensusParams(consensusParams),
|
|
|
|
nHeight(nHeight),
|
|
|
|
keystore(keystore),
|
|
|
|
sproutParams(sproutParams),
|
|
|
|
coinsView(coinsView),
|
|
|
|
cs_coinsView(cs_coinsView)
|
2018-07-17 09:36:38 -07:00
|
|
|
{
|
2019-07-30 00:16:37 -07:00
|
|
|
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
2019-02-28 10:19:36 -08:00
|
|
|
// This exception is thrown in certain scenarios when building JoinSplits fails.
|
2019-03-19 11:55:55 -07:00
|
|
|
struct JSDescException : public std::exception
|
2019-02-28 10:19:36 -08:00
|
|
|
{
|
2019-03-19 11:55:55 -07:00
|
|
|
JSDescException (const std::string msg_) : msg(msg_) {}
|
2019-02-28 10:19:36 -08:00
|
|
|
|
|
|
|
const char* what() { return msg.c_str(); }
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string msg;
|
|
|
|
};
|
|
|
|
|
2019-07-30 00:16:37 -07:00
|
|
|
|
|
|
|
void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
|
|
|
|
{
|
2019-08-05 11:57:10 -07:00
|
|
|
if (nExpiryHeight < nHeight || nExpiryHeight <= 0 || nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD) {
|
2019-08-02 15:01:20 -07:00
|
|
|
throw new std::runtime_error("TransactionBuilder::SetExpiryHeight: invalid expiry height");
|
|
|
|
}
|
2019-08-05 11:57:10 -07:00
|
|
|
mtx.nExpiryHeight = nExpiryHeight;
|
2019-07-30 00:16:37 -07:00
|
|
|
}
|
|
|
|
|
2018-10-31 09:15:37 -07:00
|
|
|
void TransactionBuilder::AddSaplingSpend(
|
2018-07-30 06:26:29 -07:00
|
|
|
libzcash::SaplingExpandedSpendingKey expsk,
|
2018-07-17 09:36:38 -07:00
|
|
|
libzcash::SaplingNote note,
|
|
|
|
uint256 anchor,
|
2018-08-01 09:41:36 -07:00
|
|
|
SaplingWitness witness)
|
2018-07-27 00:46:38 -07:00
|
|
|
{
|
2018-11-07 20:38:14 -08:00
|
|
|
// Sanity check: cannot add Sapling spend to pre-Sapling transaction
|
|
|
|
if (mtx.nVersion < SAPLING_TX_VERSION) {
|
|
|
|
throw std::runtime_error("TransactionBuilder cannot add Sapling spend to pre-Sapling transaction");
|
|
|
|
}
|
|
|
|
|
2018-07-27 00:46:38 -07:00
|
|
|
// Consistency check: all anchors must equal the first one
|
2018-10-31 09:15:37 -07:00
|
|
|
if (spends.size() > 0 && spends[0].anchor != anchor) {
|
|
|
|
throw JSONRPCError(RPC_WALLET_ERROR, "Anchor does not match previously-added Sapling spends.");
|
2018-07-27 00:46:38 -07:00
|
|
|
}
|
|
|
|
|
2018-07-30 06:26:29 -07:00
|
|
|
spends.emplace_back(expsk, note, anchor, witness);
|
2018-07-17 09:36:38 -07:00
|
|
|
mtx.valueBalance += note.value();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TransactionBuilder::AddSaplingOutput(
|
2018-09-18 15:22:50 -07:00
|
|
|
uint256 ovk,
|
2018-07-17 09:36:38 -07:00
|
|
|
libzcash::SaplingPaymentAddress to,
|
|
|
|
CAmount value,
|
2018-07-27 00:49:43 -07:00
|
|
|
std::array<unsigned char, ZC_MEMO_SIZE> memo)
|
|
|
|
{
|
2018-11-07 20:38:14 -08:00
|
|
|
// Sanity check: cannot add Sapling output to pre-Sapling transaction
|
|
|
|
if (mtx.nVersion < SAPLING_TX_VERSION) {
|
|
|
|
throw std::runtime_error("TransactionBuilder cannot add Sapling output to pre-Sapling transaction");
|
|
|
|
}
|
|
|
|
|
2020-07-01 16:05:35 -07:00
|
|
|
auto note = libzcash::SaplingNote(to, value, Params().GetConsensus().NetworkUpgradeActive(nHeight + 1, Consensus::UPGRADE_CANOPY));
|
2018-09-18 15:22:50 -07:00
|
|
|
outputs.emplace_back(ovk, note, memo);
|
2018-07-17 09:36:38 -07:00
|
|
|
mtx.valueBalance -= value;
|
|
|
|
}
|
|
|
|
|
2018-10-12 17:36:26 -07:00
|
|
|
void TransactionBuilder::AddSproutInput(
|
|
|
|
libzcash::SproutSpendingKey sk,
|
|
|
|
libzcash::SproutNote note,
|
|
|
|
SproutWitness witness)
|
|
|
|
{
|
|
|
|
if (sproutParams == nullptr) {
|
|
|
|
throw std::runtime_error("Cannot add Sprout inputs to a TransactionBuilder without Sprout params");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Consistency check: all anchors must equal the first one
|
|
|
|
if (!jsInputs.empty()) {
|
|
|
|
if (jsInputs[0].witness.root() != witness.root()) {
|
|
|
|
throw JSONRPCError(RPC_WALLET_ERROR, "Anchor does not match previously-added Sprout spends.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
jsInputs.emplace_back(witness, note, sk);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TransactionBuilder::AddSproutOutput(
|
|
|
|
libzcash::SproutPaymentAddress to,
|
|
|
|
CAmount value,
|
|
|
|
std::array<unsigned char, ZC_MEMO_SIZE> memo)
|
|
|
|
{
|
|
|
|
if (sproutParams == nullptr) {
|
|
|
|
throw std::runtime_error("Cannot add Sprout outputs to a TransactionBuilder without Sprout params");
|
|
|
|
}
|
|
|
|
|
|
|
|
libzcash::JSOutput jsOutput(to, value);
|
|
|
|
jsOutput.memo = memo;
|
|
|
|
jsOutputs.push_back(jsOutput);
|
|
|
|
}
|
|
|
|
|
2018-07-30 03:03:29 -07:00
|
|
|
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value)
|
|
|
|
{
|
|
|
|
if (keystore == nullptr) {
|
|
|
|
throw std::runtime_error("Cannot add transparent inputs to a TransactionBuilder without a keystore");
|
|
|
|
}
|
|
|
|
|
|
|
|
mtx.vin.emplace_back(utxo);
|
|
|
|
tIns.emplace_back(scriptPubKey, value);
|
|
|
|
}
|
|
|
|
|
2020-02-21 07:45:03 -08:00
|
|
|
void TransactionBuilder::AddTransparentOutput(const CTxDestination& to, CAmount value)
|
2018-07-30 03:03:29 -07:00
|
|
|
{
|
|
|
|
if (!IsValidDestination(to)) {
|
2018-10-31 09:15:37 -07:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid output address, not a valid taddr.");
|
2018-07-30 03:03:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
CScript scriptPubKey = GetScriptForDestination(to);
|
|
|
|
CTxOut out(value, scriptPubKey);
|
|
|
|
mtx.vout.push_back(out);
|
|
|
|
}
|
|
|
|
|
2018-07-30 04:52:48 -07:00
|
|
|
void TransactionBuilder::SetFee(CAmount fee)
|
|
|
|
{
|
|
|
|
this->fee = fee;
|
|
|
|
}
|
|
|
|
|
2018-09-18 15:22:50 -07:00
|
|
|
void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk)
|
2018-07-30 04:36:42 -07:00
|
|
|
{
|
2018-10-12 17:36:26 -07:00
|
|
|
saplingChangeAddr = std::make_pair(ovk, changeAddr);
|
|
|
|
sproutChangeAddr = boost::none;
|
|
|
|
tChangeAddr = boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
void TransactionBuilder::SendChangeTo(libzcash::SproutPaymentAddress changeAddr)
|
|
|
|
{
|
|
|
|
sproutChangeAddr = changeAddr;
|
|
|
|
saplingChangeAddr = boost::none;
|
2018-07-30 04:36:42 -07:00
|
|
|
tChangeAddr = boost::none;
|
|
|
|
}
|
|
|
|
|
2018-10-31 09:15:37 -07:00
|
|
|
void TransactionBuilder::SendChangeTo(CTxDestination& changeAddr)
|
2018-07-30 04:36:42 -07:00
|
|
|
{
|
|
|
|
if (!IsValidDestination(changeAddr)) {
|
2018-10-31 09:15:37 -07:00
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid change address, not a valid taddr.");
|
2018-07-30 04:36:42 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
tChangeAddr = changeAddr;
|
2018-10-12 17:36:26 -07:00
|
|
|
saplingChangeAddr = boost::none;
|
|
|
|
sproutChangeAddr = boost::none;
|
2018-07-30 04:36:42 -07:00
|
|
|
}
|
|
|
|
|
2018-10-30 13:12:40 -07:00
|
|
|
TransactionBuilderResult TransactionBuilder::Build()
|
2018-07-17 09:36:38 -07:00
|
|
|
{
|
2018-07-30 03:03:29 -07:00
|
|
|
//
|
|
|
|
// Consistency checks
|
|
|
|
//
|
|
|
|
|
|
|
|
// Valid change
|
|
|
|
CAmount change = mtx.valueBalance - fee;
|
2018-10-12 17:36:26 -07:00
|
|
|
for (auto jsInput : jsInputs) {
|
|
|
|
change += jsInput.note.value();
|
|
|
|
}
|
|
|
|
for (auto jsOutput : jsOutputs) {
|
|
|
|
change -= jsOutput.value;
|
|
|
|
}
|
2018-07-30 03:03:29 -07:00
|
|
|
for (auto tIn : tIns) {
|
|
|
|
change += tIn.value;
|
|
|
|
}
|
|
|
|
for (auto tOut : mtx.vout) {
|
|
|
|
change -= tOut.nValue;
|
|
|
|
}
|
|
|
|
if (change < 0) {
|
2018-10-30 13:12:40 -07:00
|
|
|
return TransactionBuilderResult("Change cannot be negative");
|
2018-07-30 03:03:29 -07:00
|
|
|
}
|
|
|
|
|
2018-07-30 04:36:42 -07:00
|
|
|
//
|
|
|
|
// Change output
|
|
|
|
//
|
|
|
|
|
|
|
|
if (change > 0) {
|
|
|
|
// Send change to the specified change address. If no change address
|
2019-03-19 11:55:55 -07:00
|
|
|
// was set, send change to the first Sapling address given as input
|
|
|
|
// if any; otherwise the first Sprout address given as input.
|
|
|
|
// (A t-address can only be used as the change address if explicitly set.)
|
2018-10-12 17:36:26 -07:00
|
|
|
if (saplingChangeAddr) {
|
|
|
|
AddSaplingOutput(saplingChangeAddr->first, saplingChangeAddr->second, change);
|
|
|
|
} else if (sproutChangeAddr) {
|
|
|
|
AddSproutOutput(sproutChangeAddr.get(), change);
|
2018-07-30 04:36:42 -07:00
|
|
|
} else if (tChangeAddr) {
|
|
|
|
// tChangeAddr has already been validated.
|
2018-10-31 09:15:37 -07:00
|
|
|
AddTransparentOutput(tChangeAddr.value(), change);
|
2018-07-30 04:36:42 -07:00
|
|
|
} else if (!spends.empty()) {
|
2018-07-30 06:26:29 -07:00
|
|
|
auto fvk = spends[0].expsk.full_viewing_key();
|
2018-07-30 04:36:42 -07:00
|
|
|
auto note = spends[0].note;
|
|
|
|
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
|
2018-09-13 14:04:00 -07:00
|
|
|
AddSaplingOutput(fvk.ovk, changeAddr, change);
|
2018-10-12 17:36:26 -07:00
|
|
|
} else if (!jsInputs.empty()) {
|
|
|
|
auto changeAddr = jsInputs[0].key.address();
|
|
|
|
AddSproutOutput(changeAddr, change);
|
2018-07-30 04:36:42 -07:00
|
|
|
} else {
|
2018-10-30 13:12:40 -07:00
|
|
|
return TransactionBuilderResult("Could not determine change address");
|
2018-07-30 04:36:42 -07:00
|
|
|
}
|
|
|
|
}
|
2018-07-30 03:03:29 -07:00
|
|
|
|
|
|
|
//
|
|
|
|
// Sapling spends and outputs
|
|
|
|
//
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
|
|
|
|
|
|
|
// Create Sapling SpendDescriptions
|
|
|
|
for (auto spend : spends) {
|
2020-02-26 11:49:34 -08:00
|
|
|
auto cm = spend.note.cmu();
|
2018-07-17 09:36:38 -07:00
|
|
|
auto nf = spend.note.nullifier(
|
2018-07-30 06:26:29 -07:00
|
|
|
spend.expsk.full_viewing_key(), spend.witness.position());
|
2019-01-30 03:12:15 -08:00
|
|
|
if (!cm || !nf) {
|
2018-07-17 09:36:38 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2019-01-30 03:12:15 -08:00
|
|
|
return TransactionBuilderResult("Spend is invalid");
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
|
|
ss << spend.witness.path();
|
|
|
|
std::vector<unsigned char> witness(ss.begin(), ss.end());
|
|
|
|
|
|
|
|
SpendDescription sdesc;
|
2020-06-17 13:53:12 -07:00
|
|
|
uint256 rcm = spend.note.rcm();
|
2018-07-17 09:36:38 -07:00
|
|
|
if (!librustzcash_sapling_spend_proof(
|
2018-07-27 00:49:43 -07:00
|
|
|
ctx,
|
2018-07-30 06:26:29 -07:00
|
|
|
spend.expsk.full_viewing_key().ak.begin(),
|
|
|
|
spend.expsk.nsk.begin(),
|
2018-07-27 00:49:43 -07:00
|
|
|
spend.note.d.data(),
|
2020-06-17 13:53:12 -07:00
|
|
|
rcm.begin(),
|
2018-07-27 00:49:43 -07:00
|
|
|
spend.alpha.begin(),
|
|
|
|
spend.note.value(),
|
|
|
|
spend.anchor.begin(),
|
|
|
|
witness.data(),
|
|
|
|
sdesc.cv.begin(),
|
|
|
|
sdesc.rk.begin(),
|
|
|
|
sdesc.zkproof.data())) {
|
2018-07-17 09:36:38 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2018-10-30 13:12:40 -07:00
|
|
|
return TransactionBuilderResult("Spend proof failed");
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
sdesc.anchor = spend.anchor;
|
|
|
|
sdesc.nullifier = *nf;
|
|
|
|
mtx.vShieldedSpend.push_back(sdesc);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create Sapling OutputDescriptions
|
|
|
|
for (auto output : outputs) {
|
2019-05-17 09:39:30 -07:00
|
|
|
// Check this out here as well to provide better logging.
|
|
|
|
if (!output.note.cmu()) {
|
2018-07-17 09:36:38 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2019-01-30 03:12:15 -08:00
|
|
|
return TransactionBuilderResult("Output is invalid");
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:39:30 -07:00
|
|
|
auto odesc = output.Build(ctx);
|
|
|
|
if (!odesc) {
|
2018-07-17 09:36:38 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2019-05-17 09:39:30 -07:00
|
|
|
return TransactionBuilderResult("Failed to create output description");
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
2019-05-17 09:39:30 -07:00
|
|
|
mtx.vShieldedOutput.push_back(odesc.get());
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
2018-10-12 17:36:26 -07:00
|
|
|
//
|
|
|
|
// Sprout JoinSplits
|
|
|
|
//
|
|
|
|
|
|
|
|
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES];
|
|
|
|
crypto_sign_keypair(mtx.joinSplitPubKey.begin(), joinSplitPrivKey);
|
|
|
|
|
|
|
|
// Create Sprout JSDescriptions
|
|
|
|
if (!jsInputs.empty() || !jsOutputs.empty()) {
|
2019-02-28 10:19:36 -08:00
|
|
|
try {
|
|
|
|
CreateJSDescriptions();
|
2019-03-19 11:55:55 -07:00
|
|
|
} catch (JSDescException e) {
|
2019-02-28 10:19:36 -08:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
|
|
|
return TransactionBuilderResult(e.what());
|
|
|
|
} catch (std::runtime_error e) {
|
2018-10-12 17:36:26 -07:00
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2019-02-28 10:19:36 -08:00
|
|
|
throw e;
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-30 03:03:29 -07:00
|
|
|
//
|
|
|
|
// Signatures
|
|
|
|
//
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams);
|
|
|
|
|
|
|
|
// Empty output script.
|
|
|
|
uint256 dataToBeSigned;
|
|
|
|
CScript scriptCode;
|
|
|
|
try {
|
|
|
|
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
|
|
|
} catch (std::logic_error ex) {
|
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2019-01-30 03:12:15 -08:00
|
|
|
return TransactionBuilderResult("Could not construct signature hash: " + std::string(ex.what()));
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create Sapling spendAuth and binding signatures
|
|
|
|
for (size_t i = 0; i < spends.size(); i++) {
|
|
|
|
librustzcash_sapling_spend_sig(
|
2018-07-30 06:26:29 -07:00
|
|
|
spends[i].expsk.ask.begin(),
|
2018-07-17 09:36:38 -07:00
|
|
|
spends[i].alpha.begin(),
|
|
|
|
dataToBeSigned.begin(),
|
|
|
|
mtx.vShieldedSpend[i].spendAuthSig.data());
|
|
|
|
}
|
|
|
|
librustzcash_sapling_binding_sig(
|
|
|
|
ctx,
|
|
|
|
mtx.valueBalance,
|
|
|
|
dataToBeSigned.begin(),
|
|
|
|
mtx.bindingSig.data());
|
|
|
|
|
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
2018-07-30 03:03:29 -07:00
|
|
|
|
2018-10-12 17:36:26 -07:00
|
|
|
// Create Sprout joinSplitSig
|
|
|
|
if (crypto_sign_detached(
|
|
|
|
mtx.joinSplitSig.data(), NULL,
|
|
|
|
dataToBeSigned.begin(), 32,
|
|
|
|
joinSplitPrivKey) != 0)
|
|
|
|
{
|
|
|
|
return TransactionBuilderResult("Failed to create Sprout joinSplitSig");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sanity check Sprout joinSplitSig
|
|
|
|
if (crypto_sign_verify_detached(
|
|
|
|
mtx.joinSplitSig.data(),
|
|
|
|
dataToBeSigned.begin(), 32,
|
|
|
|
mtx.joinSplitPubKey.begin()) != 0)
|
|
|
|
{
|
|
|
|
return TransactionBuilderResult("Sprout joinSplitSig sanity check failed");
|
|
|
|
}
|
|
|
|
|
2018-07-30 03:03:29 -07:00
|
|
|
// Transparent signatures
|
|
|
|
CTransaction txNewConst(mtx);
|
|
|
|
for (int nIn = 0; nIn < mtx.vin.size(); nIn++) {
|
|
|
|
auto tIn = tIns[nIn];
|
|
|
|
SignatureData sigdata;
|
|
|
|
bool signSuccess = ProduceSignature(
|
|
|
|
TransactionSignatureCreator(
|
|
|
|
keystore, &txNewConst, nIn, tIn.value, SIGHASH_ALL),
|
|
|
|
tIn.scriptPubKey, sigdata, consensusBranchId);
|
|
|
|
|
|
|
|
if (!signSuccess) {
|
2018-10-30 13:12:40 -07:00
|
|
|
return TransactionBuilderResult("Failed to sign transaction");
|
2018-07-30 03:03:29 -07:00
|
|
|
} else {
|
|
|
|
UpdateTransaction(mtx, nIn, sigdata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-30 13:12:40 -07:00
|
|
|
return TransactionBuilderResult(CTransaction(mtx));
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
2018-10-12 17:36:26 -07:00
|
|
|
|
2019-02-28 10:19:36 -08:00
|
|
|
void TransactionBuilder::CreateJSDescriptions()
|
2018-10-12 17:36:26 -07:00
|
|
|
{
|
|
|
|
// Copy jsInputs and jsOutputs to more flexible containers
|
|
|
|
std::deque<libzcash::JSInput> jsInputsDeque;
|
|
|
|
for (auto jsInput : jsInputs) {
|
|
|
|
jsInputsDeque.push_back(jsInput);
|
|
|
|
}
|
|
|
|
std::deque<libzcash::JSOutput> jsOutputsDeque;
|
|
|
|
for (auto jsOutput : jsOutputs) {
|
|
|
|
jsOutputsDeque.push_back(jsOutput);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have no Sprout shielded inputs, then we do the simpler more-leaky
|
|
|
|
// process where we just create outputs directly. We save the chaining logic,
|
|
|
|
// at the expense of leaking the sums of pairs of output values in vpub_old.
|
|
|
|
if (jsInputs.empty()) {
|
|
|
|
// Create joinsplits, where each output represents a zaddr recipient.
|
|
|
|
while (jsOutputsDeque.size() > 0) {
|
|
|
|
// Default array entries are dummy inputs and outputs
|
|
|
|
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> vjsin;
|
|
|
|
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> vjsout;
|
|
|
|
uint64_t vpub_old = 0;
|
|
|
|
|
|
|
|
for (int n = 0; n < ZC_NUM_JS_OUTPUTS && jsOutputsDeque.size() > 0; n++) {
|
|
|
|
vjsout[n] = jsOutputsDeque.front();
|
|
|
|
jsOutputsDeque.pop_front();
|
|
|
|
|
|
|
|
// Funds are removed from the value pool and enter the private pool
|
|
|
|
vpub_old += vjsout[n].value;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
|
|
|
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
|
|
|
CreateJSDescription(vpub_old, 0, vjsin, vjsout, inputMap, outputMap);
|
|
|
|
}
|
2019-02-28 10:19:36 -08:00
|
|
|
return;
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// At this point, we are guaranteed to have at least one input note.
|
|
|
|
// Use address of first input note as the temporary change address.
|
|
|
|
auto changeKey = jsInputsDeque.front().key;
|
|
|
|
auto changeAddress = changeKey.address();
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
CAmount valueOut = 0;
|
|
|
|
for (auto jsInput : jsInputs) {
|
|
|
|
valueOut += jsInput.note.value();
|
|
|
|
}
|
|
|
|
for (auto jsOutput : jsOutputs) {
|
|
|
|
valueOut -= jsOutput.value;
|
|
|
|
}
|
|
|
|
CAmount vpubOldTarget = valueOut < 0 ? -valueOut : 0;
|
|
|
|
CAmount vpubNewTarget = valueOut > 0 ? valueOut : 0;
|
|
|
|
|
|
|
|
// Keep track of treestate within this transaction
|
|
|
|
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
|
|
|
|
std::vector<uint256> previousCommitments;
|
|
|
|
|
|
|
|
while (!vpubNewProcessed) {
|
|
|
|
// Default array entries are dummy inputs and outputs
|
|
|
|
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> vjsin;
|
|
|
|
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> vjsout;
|
|
|
|
uint64_t vpub_old = 0;
|
|
|
|
uint64_t vpub_new = 0;
|
|
|
|
|
|
|
|
// Set vpub_old in the first joinsplit
|
|
|
|
if (!vpubOldProcessed) {
|
|
|
|
vpub_old += vpubOldTarget; // funds flowing from public pool
|
|
|
|
vpubOldProcessed = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
CAmount jsInputValue = 0;
|
|
|
|
uint256 jsAnchor;
|
|
|
|
|
|
|
|
JSDescription prevJoinSplit;
|
|
|
|
|
|
|
|
// Keep track of previous JoinSplit and its commitments
|
2019-06-16 04:39:05 -07:00
|
|
|
if (mtx.vJoinSplit.size() > 0) {
|
|
|
|
prevJoinSplit = mtx.vJoinSplit.back();
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there is no change, the chain has terminated so we can reset the tracked treestate.
|
2019-06-16 04:39:05 -07:00
|
|
|
if (jsChange == 0 && mtx.vJoinSplit.size() > 0) {
|
2018-10-12 17:36:26 -07:00
|
|
|
intermediates.clear();
|
|
|
|
previousCommitments.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Consume change as the first input of the JoinSplit.
|
|
|
|
//
|
|
|
|
if (jsChange > 0) {
|
|
|
|
// Update tree state with previous joinsplit
|
|
|
|
SproutMerkleTree tree;
|
|
|
|
{
|
2019-05-07 11:02:54 -07:00
|
|
|
// assert that coinsView is not null
|
|
|
|
assert(coinsView);
|
|
|
|
// We do not check cs_coinView because we do not set this in testing
|
|
|
|
// assert(cs_coinsView);
|
2018-10-12 17:36:26 -07:00
|
|
|
LOCK(cs_coinsView);
|
|
|
|
auto it = intermediates.find(prevJoinSplit.anchor);
|
|
|
|
if (it != intermediates.end()) {
|
|
|
|
tree = it->second;
|
|
|
|
} else if (!coinsView->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
|
2019-03-19 11:55:55 -07:00
|
|
|
throw JSDescException("Could not find previous JoinSplit anchor");
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(changeOutputIndex != -1);
|
|
|
|
assert(changeOutputIndex < prevJoinSplit.commitments.size());
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(changeWitness.has_value());
|
|
|
|
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());
|
2020-04-30 16:16:39 -07:00
|
|
|
auto hSig = prevJoinSplit.h_sig(mtx.joinSplitPubKey);
|
2018-10-12 17:36:26 -07:00
|
|
|
try {
|
|
|
|
auto plaintext = libzcash::SproutNotePlaintext::decrypt(
|
|
|
|
decryptor,
|
|
|
|
prevJoinSplit.ciphertexts[changeOutputIndex],
|
|
|
|
prevJoinSplit.ephemeralKey,
|
|
|
|
hSig,
|
|
|
|
(unsigned char)changeOutputIndex);
|
|
|
|
|
|
|
|
auto note = plaintext.note(changeAddress);
|
|
|
|
vjsin[0] = libzcash::JSInput(changeWitness.get(), note, changeKey);
|
|
|
|
|
|
|
|
jsInputValue += plaintext.value();
|
|
|
|
|
|
|
|
LogPrint("zrpcunsafe", "spending change (amount=%s)\n", FormatMoney(plaintext.value()));
|
|
|
|
|
|
|
|
} catch (const std::exception& e) {
|
2019-03-19 11:55:55 -07:00
|
|
|
throw JSDescException("Error decrypting output note of previous JoinSplit");
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Consume spendable non-change notes
|
|
|
|
//
|
|
|
|
for (int n = (jsChange > 0) ? 1 : 0; n < ZC_NUM_JS_INPUTS && jsInputsDeque.size() > 0; n++) {
|
|
|
|
auto jsInput = jsInputsDeque.front();
|
|
|
|
jsInputsDeque.pop_front();
|
|
|
|
|
|
|
|
// Add history of previous commitments to witness
|
|
|
|
if (jsChange > 0) {
|
|
|
|
for (const uint256& commitment : previousCommitments) {
|
|
|
|
jsInput.witness.append(commitment);
|
|
|
|
}
|
|
|
|
if (jsAnchor != jsInput.witness.root()) {
|
2019-03-19 11:55:55 -07:00
|
|
|
throw JSDescException("Witness for spendable note does not have same anchor as change input");
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The jsAnchor is null if this JoinSplit is at the start of a new chain
|
|
|
|
if (jsAnchor.IsNull()) {
|
|
|
|
jsAnchor = jsInput.witness.root();
|
|
|
|
}
|
|
|
|
|
|
|
|
jsInputValue += jsInput.note.value();
|
|
|
|
vjsin[n] = jsInput;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find recipient to transfer funds to
|
|
|
|
libzcash::JSOutput recipient;
|
|
|
|
if (jsOutputsDeque.size() > 0) {
|
|
|
|
recipient = jsOutputsDeque.front();
|
|
|
|
jsOutputsDeque.pop_front();
|
|
|
|
}
|
|
|
|
// `recipient` is now either a valid recipient, or a dummy output with value = 0
|
|
|
|
|
|
|
|
// Reset change
|
|
|
|
jsChange = 0;
|
|
|
|
CAmount outAmount = recipient.value;
|
|
|
|
|
|
|
|
// Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy)
|
|
|
|
if (jsOutputsDeque.empty() && jsInputsDeque.empty()) {
|
|
|
|
assert(!vpubNewProcessed);
|
|
|
|
if (jsInputValue < vpubNewTarget) {
|
2019-03-19 11:55:55 -07:00
|
|
|
throw JSDescException(strprintf("Insufficient funds for vpub_new %s", FormatMoney(vpubNewTarget)));
|
2018-10-12 17:36:26 -07:00
|
|
|
}
|
|
|
|
outAmount += vpubNewTarget;
|
|
|
|
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;
|
|
|
|
libzcash::JSOutput recipientDue(recipient.addr, due);
|
|
|
|
recipientDue.memo = recipient.memo;
|
|
|
|
jsOutputsDeque.push_front(recipientDue);
|
|
|
|
|
|
|
|
// reduce the amount being sent right now to the value of all inputs
|
|
|
|
recipient.value = jsInputValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create output for recipient
|
|
|
|
assert(ZC_NUM_JS_OUTPUTS == 2); // If this changes, the logic here will need to be adjusted
|
|
|
|
vjsout[0] = recipient;
|
|
|
|
|
|
|
|
// create output for any change
|
|
|
|
if (jsChange > 0) {
|
|
|
|
vjsout[1] = libzcash::JSOutput(changeAddress, jsChange);
|
|
|
|
|
|
|
|
LogPrint("zrpcunsafe", "generating note for change (amount=%s)\n", FormatMoney(jsChange));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
|
|
|
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
|
|
|
CreateJSDescription(vpub_old, vpub_new, vjsin, vjsout, inputMap, outputMap);
|
|
|
|
|
|
|
|
if (jsChange > 0) {
|
|
|
|
changeOutputIndex = -1;
|
|
|
|
for (size_t i = 0; i < outputMap.size(); i++) {
|
|
|
|
if (outputMap[i] == 1) {
|
|
|
|
changeOutputIndex = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(changeOutputIndex != -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TransactionBuilder::CreateJSDescription(
|
|
|
|
uint64_t vpub_old,
|
|
|
|
uint64_t vpub_new,
|
|
|
|
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> vjsin,
|
|
|
|
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> vjsout,
|
|
|
|
std::array<size_t, ZC_NUM_JS_INPUTS>& inputMap,
|
|
|
|
std::array<size_t, ZC_NUM_JS_OUTPUTS>& outputMap)
|
|
|
|
{
|
2019-05-08 20:32:04 -07:00
|
|
|
LogPrint("zrpcunsafe", "CreateJSDescription: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.size(),
|
2018-10-12 17:36:26 -07:00
|
|
|
FormatMoney(vpub_old), FormatMoney(vpub_new),
|
|
|
|
FormatMoney(vjsin[0].note.value()), FormatMoney(vjsin[1].note.value()),
|
|
|
|
FormatMoney(vjsout[0].value), FormatMoney(vjsout[1].value));
|
|
|
|
|
|
|
|
uint256 esk; // payment disclosure - secret
|
|
|
|
|
|
|
|
// Generate the proof, this can take over a minute.
|
2019-09-16 05:10:54 -07:00
|
|
|
assert(mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION));
|
2018-10-12 17:36:26 -07:00
|
|
|
JSDescription jsdesc = JSDescription::Randomized(
|
|
|
|
*sproutParams,
|
|
|
|
mtx.joinSplitPubKey,
|
|
|
|
vjsin[0].witness.root(),
|
|
|
|
vjsin,
|
|
|
|
vjsout,
|
|
|
|
inputMap,
|
|
|
|
outputMap,
|
|
|
|
vpub_old,
|
|
|
|
vpub_new,
|
|
|
|
true, //!this->testmode,
|
|
|
|
&esk); // parameter expects pointer to esk, so pass in address
|
|
|
|
|
|
|
|
{
|
|
|
|
auto verifier = libzcash::ProofVerifier::Strict();
|
|
|
|
if (!jsdesc.Verify(*sproutParams, verifier, mtx.joinSplitPubKey)) {
|
|
|
|
throw std::runtime_error("error verifying joinsplit");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-16 04:39:05 -07:00
|
|
|
mtx.vJoinSplit.push_back(jsdesc);
|
2018-10-12 17:36:26 -07:00
|
|
|
|
|
|
|
// TODO: Sprout payment disclosure
|
|
|
|
}
|