// 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 #include #include #include #define NO_MEMO {{0xF6}} class OrchardWallet; namespace orchard { class UnauthorizedBundle; } uint256 ProduceZip244SignatureHash( const CTransaction& tx, const std::vector& 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 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 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& ovk, const libzcash::OrchardRawAddress& to, CAmount value, const std::optional>& 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 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 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& 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 ProveAndSign( const std::vector& 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 memo; OutputDescriptionInfo( uint256 ovk, libzcash::SaplingNote note, std::array memo) : ovk(ovk), note(note), memo(memo) {} std::optional Build(rust::Box& ctx); }; struct JSDescriptionInfo { ed25519::VerificationKey joinSplitPubKey; uint256 anchor; // We store references to these so they are correctly randomised for the caller. std::array& inputs; std::array& outputs; CAmount vpub_old; CAmount vpub_new; JSDescriptionInfo( ed25519::VerificationKey joinSplitPubKey, uint256 anchor, std::array& inputs, std::array& 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& inputMap, std::array& outputMap, bool computeProof = true, // Set to false in some tests uint256* esk = nullptr, // payment disclosure std::function gen = GetRandInt ); }; class TransactionBuilderResult { private: std::optional maybeTx; std::optional 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 orchardAnchor; std::optional orchardBuilder; CAmount valueBalanceOrchard = 0; std::vector orchardSpendingKeys; std::optional firstOrchardSpendAddr; std::vector spends; std::vector outputs; std::vector jsInputs; std::vector jsOutputs; std::vector tIns; std::optional> saplingChangeAddr; std::optional> orchardChangeAddr; std::optional sproutChangeAddr; std::optional 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 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 GetOrchardAnchor() const; bool AddOrchardSpend( libzcash::OrchardSpendingKey sk, orchard::SpendInfo spendInfo); void AddOrchardOutput( const std::optional& ovk, const libzcash::OrchardRawAddress& to, CAmount value, const std::optional>& 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 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 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 vjsin, std::array vjsout, std::array& inputMap, std::array& outputMap); }; #endif // ZCASH_TRANSACTION_BUILDER_H