zcashd/src/transaction_builder.h

414 lines
15 KiB
C++

// Copyright (c) 2018-2023 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_TRANSACTION_BUILDER_H
#define ZCASH_TRANSACTION_BUILDER_H
#include "coins.h"
#include "consensus/params.h"
#include "keystore.h"
#include "policy/fees.h"
#include "primitives/transaction.h"
#include "random.h"
#include "script/script.h"
#include "script/standard.h"
#include "uint256.h"
#include "zcash/Address.hpp"
#include "zcash/IncrementalMerkleTree.hpp"
#include "zcash/JoinSplit.hpp"
#include "zcash/Note.hpp"
#include "zcash/NoteEncryption.hpp"
#include <optional>
#include <rust/bridge.h>
#include <rust/builder.h>
#include <rust/ed25519.h>
#define NO_MEMO {{0xF6}}
class OrchardWallet;
namespace orchard { class UnauthorizedBundle; }
uint256 ProduceZip244SignatureHash(
const CTransaction& tx,
const std::vector<CTxOut>& allPrevOutputs,
const orchard::UnauthorizedBundle& orchardBundle);
namespace orchard {
/// The information necessary to spend an Orchard note.
class SpendInfo
{
private:
/// Memory is allocated by Rust.
std::unique_ptr<OrchardSpendInfoPtr, decltype(&orchard_spend_info_free)> inner;
libzcash::OrchardRawAddress from;
uint64_t noteValue;
// SpendInfo() : inner(nullptr, orchard_spend_info_free) {}
SpendInfo(
OrchardSpendInfoPtr* spendInfo,
libzcash::OrchardRawAddress fromIn,
uint64_t noteValueIn
) : inner(spendInfo, orchard_spend_info_free), from(fromIn), noteValue(noteValueIn) {}
friend class Builder;
friend class ::OrchardWallet;
public:
// SpendInfo should never be copied
SpendInfo(const SpendInfo&) = delete;
SpendInfo& operator=(const SpendInfo&) = delete;
SpendInfo(SpendInfo&& spendInfo) :
inner(std::move(spendInfo.inner)), from(std::move(spendInfo.from)), noteValue(std::move(spendInfo.noteValue)) {}
SpendInfo& operator=(SpendInfo&& spendInfo)
{
if (this != &spendInfo) {
inner = std::move(spendInfo.inner);
from = std::move(spendInfo.from);
noteValue = std::move(spendInfo.noteValue);
}
return *this;
}
inline libzcash::OrchardRawAddress FromAddress() const { return from; };
inline uint64_t Value() const { return noteValue; };
};
/// A builder that constructs an `UnauthorizedBundle` from a set of notes to be spent,
/// and recipients to receive funds.
class Builder {
private:
/// The Orchard builder. Memory is allocated by Rust. If this is `nullptr` then
/// `Builder::Build` has been called, and all subsequent operations will throw an
/// exception.
std::unique_ptr<OrchardBuilderPtr, decltype(&orchard_builder_free)> inner;
bool hasActions{false};
Builder() : inner(nullptr, orchard_builder_free), hasActions(false) { }
public:
Builder(bool spendsEnabled, bool outputsEnabled, uint256 anchor);
// Builder should never be copied
Builder(const Builder&) = delete;
Builder& operator=(const Builder&) = delete;
Builder(Builder&& builder) : inner(std::move(builder.inner)) {}
Builder& operator=(Builder&& builder)
{
if (this != &builder) {
inner = std::move(builder.inner);
}
return *this;
}
/// Adds a note to be spent in this bundle.
///
/// Returns `false` if the given Merkle path does not have the required anchor
/// for the given note.
bool AddSpend(orchard::SpendInfo spendInfo);
/// Adds an address which will receive funds in this bundle.
void AddOutput(
const std::optional<uint256>& ovk,
const libzcash::OrchardRawAddress& to,
CAmount value,
const std::optional<std::array<unsigned char, ZC_MEMO_SIZE>>& memo);
/// Returns `true` if any spends or outputs have been added to this builder. This can
/// be used to avoid calling `Build()` and creating a dummy Orchard bundle.
bool HasActions() {
return hasActions;
}
/// Builds a bundle containing the given spent notes and recipients.
///
/// Returns `std::nullopt` if an error occurs.
///
/// Calling this method invalidates this object; in particular, if an error occurs
/// this builder must be discarded and a new builder created. Subsequent usage of this
/// object in any way will cause an exception. This emulates Rust's compile-time move
/// semantics at runtime.
std::optional<UnauthorizedBundle> Build();
};
/// An unauthorized Orchard bundle, ready for its proof to be created and signatures
/// applied.
class UnauthorizedBundle {
private:
/// An optional Orchard bundle (with `nullptr` corresponding to `None`).
/// Memory is allocated by Rust.
std::unique_ptr<OrchardUnauthorizedBundlePtr, decltype(&orchard_unauthorized_bundle_free)> inner;
UnauthorizedBundle() : inner(nullptr, orchard_unauthorized_bundle_free) {}
UnauthorizedBundle(OrchardUnauthorizedBundlePtr* bundle) : inner(bundle, orchard_unauthorized_bundle_free) {}
friend class Builder;
// The parentheses here are necessary to avoid the following compilation error:
// error: C++ requires a type specifier for all declarations
// friend uint256 ::ProduceZip244SignatureHash(
// ~~~~~~ ^
friend uint256 (::ProduceZip244SignatureHash(
const CTransaction& tx,
const std::vector<CTxOut>& allPrevOutputs,
const UnauthorizedBundle& orchardBundle));
public:
// UnauthorizedBundle should never be copied
UnauthorizedBundle(const UnauthorizedBundle&) = delete;
UnauthorizedBundle& operator=(const UnauthorizedBundle&) = delete;
UnauthorizedBundle(UnauthorizedBundle&& bundle) : inner(std::move(bundle.inner)) {}
UnauthorizedBundle& operator=(UnauthorizedBundle&& bundle)
{
if (this != &bundle) {
inner = std::move(bundle.inner);
}
return *this;
}
/// Adds proofs and signatures to this bundle.
///
/// Returns `std::nullopt` if an error occurs.
///
/// Calling this method invalidates this object; in particular, if an error occurs
/// this bundle must be discarded and a new bundle built. Subsequent usage of this
/// object in any way will cause an exception. This emulates Rust's compile-time
/// move semantics at runtime.
std::optional<OrchardBundle> ProveAndSign(
const std::vector<libzcash::OrchardSpendingKey>& keys, uint256 sighash);
};
} // namespace orchard
struct SpendDescriptionInfo {
libzcash::SaplingExpandedSpendingKey expsk;
libzcash::SaplingNote note;
uint256 alpha;
uint256 anchor;
SaplingWitness witness;
SpendDescriptionInfo(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
SaplingWitness witness);
};
struct OutputDescriptionInfo {
uint256 ovk;
libzcash::SaplingNote note;
std::array<unsigned char, ZC_MEMO_SIZE> memo;
OutputDescriptionInfo(
uint256 ovk,
libzcash::SaplingNote note,
std::array<unsigned char, ZC_MEMO_SIZE> memo) : ovk(ovk), note(note), memo(memo) {}
std::optional<OutputDescription> Build(rust::Box<sapling::Prover>& ctx);
};
struct JSDescriptionInfo {
ed25519::VerificationKey joinSplitPubKey;
uint256 anchor;
// We store references to these so they are correctly randomised for the caller.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs;
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs;
CAmount vpub_old;
CAmount vpub_new;
JSDescriptionInfo(
ed25519::VerificationKey joinSplitPubKey,
uint256 anchor,
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new) : joinSplitPubKey(joinSplitPubKey), anchor(anchor), inputs(inputs), outputs(outputs), vpub_old(vpub_old), vpub_new(vpub_new) {}
JSDescription BuildDeterministic(
bool computeProof = true, // Set to false in some tests
uint256* esk = nullptr // payment disclosure
);
JSDescription BuildRandomized(
std::array<size_t, ZC_NUM_JS_INPUTS>& inputMap,
std::array<size_t, ZC_NUM_JS_OUTPUTS>& outputMap,
bool computeProof = true, // Set to false in some tests
uint256* esk = nullptr, // payment disclosure
std::function<int(int)> gen = GetRandInt
);
};
class TransactionBuilderResult {
private:
std::optional<CTransaction> maybeTx;
std::optional<std::string> maybeError;
public:
TransactionBuilderResult() = delete;
TransactionBuilderResult(const CTransaction& tx);
TransactionBuilderResult(const std::string& error);
bool IsTx();
bool IsError();
CTransaction GetTxOrThrow();
std::string GetError();
};
class TransactionBuilder
{
private:
Consensus::Params consensusParams;
int nHeight;
const CKeyStore* keystore;
const CCoinsViewCache* coinsView;
CCriticalSection* cs_coinsView;
CMutableTransaction mtx;
CAmount fee = DEFAULT_FEE;
std::optional<uint256> orchardAnchor;
std::optional<orchard::Builder> orchardBuilder;
CAmount valueBalanceOrchard = 0;
std::vector<libzcash::OrchardSpendingKey> orchardSpendingKeys;
std::optional<libzcash::OrchardRawAddress> firstOrchardSpendAddr;
std::vector<SpendDescriptionInfo> spends;
std::vector<OutputDescriptionInfo> outputs;
std::vector<libzcash::JSInput> jsInputs;
std::vector<libzcash::JSOutput> jsOutputs;
std::vector<CTxOut> tIns;
std::optional<std::pair<uint256, libzcash::SaplingPaymentAddress>> saplingChangeAddr;
std::optional<std::pair<uint256, libzcash::OrchardRawAddress>> orchardChangeAddr;
std::optional<libzcash::SproutPaymentAddress> sproutChangeAddr;
std::optional<CTxDestination> tChangeAddr;
public:
TransactionBuilder() {}
/**
* If an Orchard anchor is provided, the resulting builder will always attempt
* to construct a V5 transaction unless NU5 is not yet active. If an Orchard
* anchor is not provided, the transaction version will be based on the
* value of the -preferredtxversion configuration flag.
*/
TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
std::optional<uint256> orchardAnchor,
const CKeyStore* keyStore = nullptr,
const CCoinsViewCache* coinsView = nullptr,
CCriticalSection* cs_coinsView = nullptr);
// TransactionBuilder should never be copied
TransactionBuilder(const TransactionBuilder&) = delete;
TransactionBuilder& operator=(const TransactionBuilder&) = delete;
TransactionBuilder(TransactionBuilder&& builder) :
consensusParams(std::move(builder.consensusParams)),
nHeight(std::move(builder.nHeight)),
keystore(std::move(builder.keystore)),
coinsView(std::move(builder.coinsView)),
cs_coinsView(std::move(builder.cs_coinsView)),
mtx(std::move(builder.mtx)),
fee(std::move(builder.fee)),
orchardAnchor(std::move(builder.orchardAnchor)),
orchardBuilder(std::move(builder.orchardBuilder)),
valueBalanceOrchard(std::move(builder.valueBalanceOrchard)),
spends(std::move(builder.spends)),
outputs(std::move(builder.outputs)),
jsInputs(std::move(builder.jsInputs)),
jsOutputs(std::move(builder.jsOutputs)),
tIns(std::move(builder.tIns)),
saplingChangeAddr(std::move(builder.saplingChangeAddr)),
sproutChangeAddr(std::move(builder.sproutChangeAddr)),
tChangeAddr(std::move(builder.tChangeAddr)) {}
TransactionBuilder& operator=(TransactionBuilder&& builder)
{
if (this != &builder) {
consensusParams = std::move(builder.consensusParams);
nHeight = std::move(builder.nHeight);
keystore = std::move(builder.keystore);
coinsView = std::move(builder.coinsView);
cs_coinsView = std::move(builder.cs_coinsView);
mtx = std::move(builder.mtx);
fee = std::move(builder.fee);
orchardBuilder = std::move(builder.orchardBuilder);
valueBalanceOrchard = std::move(builder.valueBalanceOrchard);
spends = std::move(builder.spends);
outputs = std::move(builder.outputs);
jsInputs = std::move(builder.jsInputs);
jsOutputs = std::move(builder.jsOutputs);
tIns = std::move(builder.tIns);
saplingChangeAddr = std::move(builder.saplingChangeAddr);
sproutChangeAddr = std::move(builder.sproutChangeAddr);
tChangeAddr = std::move(builder.tChangeAddr);
}
return *this;
}
void SetExpiryHeight(uint32_t nExpiryHeight);
void SetFee(CAmount fee);
bool SupportsOrchard() const;
std::optional<uint256> GetOrchardAnchor() const;
bool AddOrchardSpend(
libzcash::OrchardSpendingKey sk,
orchard::SpendInfo spendInfo);
void AddOrchardOutput(
const std::optional<uint256>& ovk,
const libzcash::OrchardRawAddress& to,
CAmount value,
const std::optional<std::array<unsigned char, ZC_MEMO_SIZE>>& memo);
// Throws if the anchor does not match the anchor used by
// previously-added Sapling spends.
void AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
SaplingWitness witness);
void AddSaplingOutput(
uint256 ovk,
libzcash::SaplingPaymentAddress to,
CAmount value,
std::array<unsigned char, ZC_MEMO_SIZE> memo = NO_MEMO);
// Throws if the anchor does not match the anchor used by
// previously-added Sprout inputs.
void AddSproutInput(
libzcash::SproutSpendingKey sk,
libzcash::SproutNote note,
SproutWitness witness);
void AddSproutOutput(
libzcash::SproutPaymentAddress to,
CAmount value,
std::array<unsigned char, ZC_MEMO_SIZE> memo = NO_MEMO);
// Assumes that the value correctly corresponds to the provided UTXO.
void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value);
void AddTransparentOutput(const CTxDestination& to, CAmount value);
void SendChangeTo(const libzcash::RecipientAddress& changeAddr, const uint256& ovk);
void SendChangeToSprout(const libzcash::SproutPaymentAddress& changeAddr);
TransactionBuilderResult Build();
private:
void CheckOrSetUsingSprout();
void CreateJSDescriptions();
void 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);
};
#endif // ZCASH_TRANSACTION_BUILDER_H