Merge pull request #5544 from str4d/feature/wallet_orchard-builder_recipients

Add Orchard recipient support to the transaction builder
This commit is contained in:
Kris Nuttycombe 2022-02-16 08:22:43 -07:00 committed by GitHub
commit f6a3c68b09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 686 additions and 87 deletions

View File

@ -3,7 +3,12 @@ replace-with = "vendored-sources"
[source."https://github.com/zcash/librustzcash.git"]
git = "https://github.com/zcash/librustzcash.git"
rev = "5622b060b1f57de7afc3d0b4e425b9b4b22482a0"
rev = "3d935a94e75786a67c3ea4992d7c372af203086f"
replace-with = "vendored-sources"
[source."https://github.com/zcash/orchard.git"]
git = "https://github.com/zcash/orchard.git"
rev = "c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65"
replace-with = "vendored-sources"
[source.vendored-sources]

19
Cargo.lock generated
View File

@ -501,7 +501,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"blake2b_simd 1.0.0",
"byteorder",
@ -510,7 +510,7 @@ dependencies = [
[[package]]
name = "f4jumble"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"blake2b_simd 1.0.0",
]
@ -1090,8 +1090,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "orchard"
version = "0.1.0-beta.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be"
source = "git+https://github.com/zcash/orchard.git?rev=c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65#c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65"
dependencies = [
"aes",
"arrayvec 0.7.2",
@ -1917,7 +1916,7 @@ dependencies = [
[[package]]
name = "zcash_address"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"bech32",
"bs58",
@ -1928,7 +1927,7 @@ dependencies = [
[[package]]
name = "zcash_encoding"
version = "0.0.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"byteorder",
"nonempty",
@ -1937,7 +1936,7 @@ dependencies = [
[[package]]
name = "zcash_history"
version = "0.2.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"bigint",
"blake2b_simd 1.0.0",
@ -1947,7 +1946,7 @@ dependencies = [
[[package]]
name = "zcash_note_encryption"
version = "0.1.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"chacha20",
"chacha20poly1305",
@ -1958,7 +1957,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"aes",
"bip0039",
@ -1994,7 +1993,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.5.0"
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
dependencies = [
"bellman",
"blake2b_simd 1.0.0",

View File

@ -72,8 +72,9 @@ codegen-units = 1
[patch.crates-io]
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65" }
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }

View File

@ -11,6 +11,8 @@
#include "transaction_builder.h"
#include "utiltest.h"
#include <optional>
TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
{
auto consensusParams = RegtestActivateSapling();
@ -20,7 +22,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
CTxDestination taddr = tsk.GetPubKey().GetID();
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddTransparentOutput(taddr, 40000);
@ -53,7 +55,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
auto sk = libzcash::SaplingSpendingKey::random();
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
@ -74,7 +76,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
auto sk = libzcash::SaplingSpendingKey::random();
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 40000, {});
@ -96,7 +98,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
auto sk = libzcash::SaplingSpendingKey::random();
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddTransparentOutput(taddr, 40000);

View File

@ -119,7 +119,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
// Default fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
@ -130,7 +130,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
// Lower than standard fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
static_assert(DEFAULT_FEE == 1000);
@ -143,7 +143,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
// Larger Tx
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});

View File

@ -9,6 +9,9 @@
#include "transaction_builder.h"
#include "utiltest.h"
#include "zcash/Address.hpp"
#include "zcash/address/mnemonic.h"
#include <optional>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@ -34,6 +37,10 @@ public:
return false;
}
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
return false;
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false;
}
@ -92,7 +99,7 @@ TEST(TransactionBuilder, TransparentToSapling)
// Create a shielding transaction from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -124,7 +131,7 @@ TEST(TransactionBuilder, SaplingToSapling) {
// Create a Sapling-only transaction
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
auto builder = TransactionBuilder(consensusParams, 2);
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
// Check that trying to add a different anchor fails
@ -165,7 +172,7 @@ TEST(TransactionBuilder, SaplingToSprout) {
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
// - 0.00005 Sapling-ZEC change
// - default t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr);
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSproutOutput(sproutAddr, 25000);
auto tx = builder.Build().GetTxOrThrow();
@ -217,7 +224,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
// - 0.00005 Sprout-ZEC change
// - 0.00005 Sapling-ZEC out
// - 0.00005 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 2, nullptr, &view);
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr, &view);
builder.SetFee(5000);
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
builder.AddSproutOutput(sproutAddr, 6000);
@ -248,12 +255,60 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
RegtestDeactivateSapling();
}
TEST(TransactionBuilder, TransparentToOrchard)
{
auto consensusParams = RegtestActivateNU5();
CBasicKeyStore keystore;
CKey tsk = AddTestCKeyToKeyStore(keystore);
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
auto coinType = Params().BIP44CoinType();
auto seed = MnemonicSeed::Random(coinType);
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, coinType, 0);
auto fvk = sk.ToFullViewingKey();
auto ivk = fvk.ToIncomingViewingKey();
libzcash::diversifier_index_t j(0);
auto recipient = ivk.Address(j);
TransactionBuilderCoinsViewDB fakeDB;
auto orchardAnchor = fakeDB.GetBestAnchor(ShieldedType::ORCHARD);
// Create a shielding transaction from transparent to Orchard
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
auto builder = TransactionBuilder(consensusParams, 1, orchardAnchor, &keystore);
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
builder.AddOrchardOutput(std::nullopt, recipient, 40000, std::nullopt);
auto maybeTx = builder.Build();
EXPECT_TRUE(maybeTx.IsTx());
if (maybeTx.IsError()) {
std::cerr << "Failed to build transaction: " << maybeTx.GetError() << std::endl;
GTEST_FAIL();
}
auto tx = maybeTx.GetTxOrThrow();
EXPECT_EQ(tx.vin.size(), 1);
EXPECT_EQ(tx.vout.size(), 0);
EXPECT_EQ(tx.vJoinSplit.size(), 0);
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
EXPECT_EQ(tx.vShieldedOutput.size(), 0);
EXPECT_TRUE(tx.GetOrchardBundle().IsPresent());
EXPECT_EQ(tx.GetOrchardBundle().GetValueBalance(), -40000);
CValidationState state;
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, true));
EXPECT_EQ(state.GetRejectReason(), "");
// Revert to default
RegtestDeactivateNU5();
}
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
{
SelectParams(CBaseChainParams::REGTEST);
const Consensus::Params& consensusParams = Params().GetConsensus();
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
}
@ -264,7 +319,7 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
// Default CTxDestination type is an invalid address
CTxDestination taddr;
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
}
@ -289,13 +344,13 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, default fee
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
// Fail if there is only a transparent output
// 0.0005 t-ZEC out, default fee
builder = TransactionBuilder(consensusParams, 1, &keystore);
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentOutput(taddr, 50000);
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
@ -336,14 +391,14 @@ TEST(TransactionBuilder, ChangeOutput)
// No change address and no Sapling spends
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
}
// Change to the same address as the first Sapling spend
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
auto tx = builder.Build().GetTxOrThrow();
@ -358,7 +413,7 @@ TEST(TransactionBuilder, ChangeOutput)
// Change to a Sapling address
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
auto tx = builder.Build().GetTxOrThrow();
@ -373,7 +428,7 @@ TEST(TransactionBuilder, ChangeOutput)
// Change to a transparent address
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(tkeyid, {});
auto tx = builder.Build().GetTxOrThrow();
@ -405,7 +460,7 @@ TEST(TransactionBuilder, SetFee)
// Default fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -420,7 +475,7 @@ TEST(TransactionBuilder, SetFee)
// Configured fee
{
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
builder.SetFee(20000);
@ -449,7 +504,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
auto pk = sk.default_address();
// Cannot add Sapling outputs to a non-Sapling transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
try {
builder.AddSaplingOutput(uint256(), pk, 12345, {});
} catch (std::runtime_error const & err) {

View File

@ -172,7 +172,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
// Create a transparent transaction that spends the coin, targeting
// a height during the Overwinter epoch.
auto builder = TransactionBuilder(consensusParams, 15, &keystore);
auto builder = TransactionBuilder(consensusParams, 15, std::nullopt, &keystore);
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
builder.AddTransparentOutput(destination, 40000);
auto tx = builder.Build().GetTxOrThrow();

View File

@ -11,9 +11,10 @@
#include <rust/orchard.h>
class OrchardMerkleFrontier;
namespace orchard { class UnauthorizedBundle; }
/**
* The Orchard component of a transaction.
* The Orchard component of an authorized transaction.
*/
class OrchardBundle
{
@ -22,7 +23,10 @@ private:
/// Memory is allocated by Rust.
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
OrchardBundle(OrchardBundlePtr* bundle) : inner(bundle, orchard_bundle_free) {}
friend class OrchardMerkleFrontier;
friend class orchard::UnauthorizedBundle;
public:
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}

View File

@ -0,0 +1,82 @@
// Copyright (c) 2022 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_RUST_INCLUDE_RUST_BUILDER_H
#define ZCASH_RUST_INCLUDE_RUST_BUILDER_H
#include "rust/orchard.h"
#include "rust/orchard/keys.h"
#include "rust/transaction.h"
#ifdef __cplusplus
extern "C" {
#endif
/// Pointer to Rust-allocated Orchard bundle builder.
struct OrchardBuilderPtr;
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
/// Pointer to Rust-allocated Orchard bundle without proofs
/// or authorizing data.
struct OrchardUnauthorizedBundlePtr;
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
/// Construct a new Orchard transaction builder.
OrchardBuilderPtr* orchard_builder_new(
bool spends_enabled,
bool outputs_enabled,
const unsigned char* anchor);
/// Frees an Orchard builder returned from `orchard_builder_new`.
void orchard_builder_free(OrchardBuilderPtr* ptr);
/// Adds an address which will receive funds in this bundle.
///
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
/// or `null` to make the recipient unrecoverable by the sender.
///
/// `memo` is a pointer to the 512-byte memo field encoding, or `null` for "no memo".
bool orchard_builder_add_recipient(
OrchardBuilderPtr* ptr,
const unsigned char* ovk,
const OrchardRawAddressPtr* recipient,
uint64_t value,
const unsigned char* memo);
/// Builds a bundle containing the given spent notes and recipients.
///
/// Returns `null` if an error occurs.
///
/// `builder` is always freed by this method.
OrchardUnauthorizedBundlePtr* orchard_builder_build(OrchardBuilderPtr* builder);
/// Frees an Orchard bundle returned from `orchard_bundle_build`.
void orchard_unauthorized_bundle_free(OrchardUnauthorizedBundlePtr* bundle);
/// Adds proofs and signatures to the bundle.
///
/// Returns `null` if an error occurs.
///
/// `bundle` is always freed by this method.
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
OrchardUnauthorizedBundlePtr* bundle,
const unsigned char* sighash);
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
/// transaction.
///
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
/// will be unaltered.
///
/// `preTx` is always freed by this method.
bool zcash_builder_zip244_shielded_signature_digest(
PrecomputedTxParts* preTx,
const OrchardUnauthorizedBundlePtr* bundle,
unsigned char* sighash_ret);
#ifdef __cplusplus
}
#endif
#endif // ZCASH_RUST_INCLUDE_RUST_BUILDER_H

View File

@ -13,6 +13,7 @@
extern "C" {
#endif
/// Typesafe pointer to a Rust-allocated orchard::bundle::Bundle value
struct OrchardBundlePtr;
typedef struct OrchardBundlePtr OrchardBundlePtr;

169
src/rust/src/builder_ffi.rs Normal file
View File

@ -0,0 +1,169 @@
use std::convert::TryInto;
use std::ptr;
use orchard::{
builder::{Builder, InProgress, Unauthorized, Unproven},
bundle::{Authorized, Flags},
keys::OutgoingViewingKey,
value::NoteValue,
Bundle,
};
use rand_core::OsRng;
use tracing::error;
use zcash_primitives::transaction::{
components::{sapling, transparent, Amount},
sighash::{SignableInput, SIGHASH_ALL},
sighash_v5::v5_signature_hash,
Authorization, TransactionData, TxVersion,
};
use crate::{transaction_ffi::PrecomputedTxParts, ORCHARD_PK};
#[no_mangle]
pub extern "C" fn orchard_builder_new(
spends_enabled: bool,
outputs_enabled: bool,
anchor: *const [u8; 32],
) -> *mut Builder {
let anchor = unsafe { anchor.as_ref() }.expect("Anchor pointer may not be null.");
Box::into_raw(Box::new(Builder::new(
Flags::from_parts(spends_enabled, outputs_enabled),
orchard::Anchor::from_bytes(*anchor).unwrap(),
)))
}
#[no_mangle]
pub extern "C" fn orchard_builder_add_recipient(
builder: *mut Builder,
ovk: *const [u8; 32],
recipient: *const orchard::Address,
value: u64,
memo: *const [u8; 512],
) -> bool {
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
let ovk = unsafe { ovk.as_ref() }
.copied()
.map(OutgoingViewingKey::from);
let recipient = unsafe { recipient.as_ref() }.expect("Recipient may not be null.");
let value = NoteValue::from_raw(value);
let memo = unsafe { memo.as_ref() }.copied();
match builder.add_recipient(ovk, *recipient, value, memo) {
Ok(()) => true,
Err(e) => {
error!("Failed to add Orchard recipient: {}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn orchard_builder_free(builder: *mut Builder) {
if !builder.is_null() {
drop(unsafe { Box::from_raw(builder) });
}
}
#[no_mangle]
pub extern "C" fn orchard_builder_build(
builder: *mut Builder,
) -> *mut Bundle<InProgress<Unproven, Unauthorized>, Amount> {
if builder.is_null() {
error!("Called with null builder");
return ptr::null_mut();
}
let builder = unsafe { Box::from_raw(builder) };
match builder.build(OsRng) {
Ok(bundle) => Box::into_raw(Box::new(bundle)),
Err(e) => {
error!("Failed to build Orchard bundle: {:?}", e);
ptr::null_mut()
}
}
}
#[no_mangle]
pub extern "C" fn orchard_unauthorized_bundle_free(
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
) {
if !bundle.is_null() {
drop(unsafe { Box::from_raw(bundle) });
}
}
#[no_mangle]
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
sighash: *const [u8; 32],
) -> *mut Bundle<Authorized, Amount> {
let bundle = unsafe { Box::from_raw(bundle) };
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
let res = bundle
.create_proof(pk)
.and_then(|b| b.apply_signatures(OsRng, *sighash, &[]));
match res {
Ok(signed) => Box::into_raw(Box::new(signed)),
Err(e) => {
error!(
"An error occurred while authorizing the orchard bundle: {:?}",
e
);
std::ptr::null_mut()
}
}
}
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
/// transaction.
///
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
/// will be unaltered.
#[no_mangle]
pub extern "C" fn zcash_builder_zip244_shielded_signature_digest(
precomputed_tx: *mut PrecomputedTxParts,
bundle: *const Bundle<InProgress<Unproven, Unauthorized>, Amount>,
sighash_ret: *mut [u8; 32],
) -> bool {
let precomputed_tx = if !precomputed_tx.is_null() {
unsafe { Box::from_raw(precomputed_tx) }
} else {
error!("Invalid precomputed transaction");
return false;
};
if matches!(
precomputed_tx.tx.version(),
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling,
) {
error!("Cannot calculate ZIP 244 digest for pre-v5 transaction");
return false;
}
let bundle = unsafe { bundle.as_ref().unwrap() };
struct Signable {}
impl Authorization for Signable {
type TransparentAuth = transparent::Authorized;
type SaplingAuth = sapling::Authorized;
type OrchardAuth = InProgress<Unproven, Unauthorized>;
}
let txdata: TransactionData<Signable> =
precomputed_tx
.tx
.into_data()
.map_bundles(|b| b, |b| b, |_| Some(bundle.clone()));
let sighash = v5_signature_hash(
&txdata,
SIGHASH_ALL,
&SignableInput::Shielded,
&precomputed_tx.txid_parts,
);
// `v5_signature_hash` output is always 32 bytes.
*unsafe { &mut *sighash_ret } = sighash.as_ref().try_into().unwrap();
true
}

View File

@ -71,6 +71,7 @@ mod streams_ffi;
mod tracing_ffi;
mod address_ffi;
mod builder_ffi;
mod history_ffi;
mod incremental_merkle_tree_ffi;
mod orchard_ffi;

View File

@ -56,8 +56,8 @@ pub extern "C" fn zcash_transaction_digests(
}
pub struct PrecomputedTxParts {
tx: Transaction,
txid_parts: TxDigests<Hash>,
pub(crate) tx: Transaction,
pub(crate) txid_parts: TxDigests<Hash>,
}
/// Precomputes the `TxDigest` struct for the given transaction.

View File

@ -15,6 +15,81 @@
#include <librustzcash.h>
#include <rust/ed25519.h>
uint256 ProduceZip244SignatureHash(
const CTransaction& tx,
const orchard::UnauthorizedBundle& orchardBundle)
{
uint256 dataToBeSigned;
PrecomputedTransactionData local(tx);
if (!zcash_builder_zip244_shielded_signature_digest(
local.preTx.release(),
orchardBundle.inner.get(),
dataToBeSigned.begin()))
{
throw std::logic_error("ZIP 225 signature hash failed");
}
return dataToBeSigned;
}
namespace orchard {
Builder::Builder(
bool spendsEnabled,
bool outputsEnabled,
uint256 anchor) : inner(nullptr, orchard_builder_free)
{
inner.reset(orchard_builder_new(spendsEnabled, outputsEnabled, anchor.begin()));
}
void Builder::AddOutput(
const std::optional<uint256>& ovk,
const libzcash::OrchardRawAddress& to,
CAmount value,
const std::optional<std::array<unsigned char, ZC_MEMO_SIZE>>& memo)
{
if (!inner) {
throw std::logic_error("orchard::Builder has already been used");
}
orchard_builder_add_recipient(
inner.get(),
ovk.has_value() ? ovk->begin() : nullptr,
to.inner.get(),
value,
memo.has_value() ? memo->data() : nullptr);
}
std::optional<UnauthorizedBundle> Builder::Build() {
if (!inner) {
throw std::logic_error("orchard::Builder has already been used");
}
auto bundle = orchard_builder_build(inner.release());
if (bundle == nullptr) {
return std::nullopt;
} else {
return UnauthorizedBundle(bundle);
}
}
std::optional<OrchardBundle> UnauthorizedBundle::ProveAndSign(
uint256 sighash)
{
if (!inner) {
throw std::logic_error("orchard::UnauthorizedBundle has already been used");
}
auto authorizedBundle = orchard_unauthorized_bundle_prove_and_sign(
inner.release(), sighash.begin());
if (authorizedBundle == nullptr) {
return std::nullopt;
} else {
return OrchardBundle(authorizedBundle);
}
}
} // namespace orchard
SpendDescriptionInfo::SpendDescriptionInfo(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
@ -150,16 +225,19 @@ std::string TransactionBuilderResult::GetError() {
TransactionBuilder::TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
std::optional<uint256> orchardAnchor,
CKeyStore* keystore,
CCoinsViewCache* coinsView,
CCriticalSection* cs_coinsView) :
usingSprout(std::nullopt),
consensusParams(consensusParams),
nHeight(nHeight),
keystore(keystore),
coinsView(coinsView),
cs_coinsView(cs_coinsView)
{
if (orchardAnchor.has_value()) {
orchardBuilder = orchard::Builder(true, true, orchardAnchor.value());
}
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
}
@ -174,7 +252,6 @@ private:
std::string msg;
};
void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
{
if (nExpiryHeight < nHeight || nExpiryHeight <= 0 || nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD) {
@ -183,6 +260,20 @@ void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
mtx.nExpiryHeight = nExpiryHeight;
}
void TransactionBuilder::AddOrchardOutput(
const std::optional<uint256>& ovk,
const libzcash::OrchardRawAddress& to,
CAmount value,
const std::optional<std::array<unsigned char, ZC_MEMO_SIZE>>& memo)
{
if (!orchardBuilder.has_value()) {
throw std::runtime_error("TransactionBuilder cannot add Orchard output without Orchard anchor");
}
orchardBuilder.value().AddOutput(ovk, to, value, memo);
valueBalanceOrchard -= value;
}
void TransactionBuilder::AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
@ -314,7 +405,7 @@ TransactionBuilderResult TransactionBuilder::Build()
//
// Valid change
CAmount change = mtx.valueBalanceSapling - fee;
CAmount change = mtx.valueBalanceSapling + valueBalanceOrchard - fee;
for (auto jsInput : jsInputs) {
change += jsInput.note.value();
}
@ -360,6 +451,20 @@ TransactionBuilderResult TransactionBuilder::Build()
}
}
//
// Orchard
//
std::optional<orchard::UnauthorizedBundle> orchardBundle;
if (orchardBuilder.has_value()) {
auto bundle = orchardBuilder->Build();
if (bundle.has_value()) {
orchardBundle = std::move(bundle);
} else {
return TransactionBuilderResult("Failed to build Orchard bundle");
}
}
//
// Sapling spends and outputs
//
@ -449,14 +554,28 @@ TransactionBuilderResult TransactionBuilder::Build()
// Empty output script.
uint256 dataToBeSigned;
CScript scriptCode;
try {
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
if (orchardBundle.has_value()) {
// Orchard is only usable with v5+ transactions.
dataToBeSigned = ProduceZip244SignatureHash(mtx, orchardBundle.value());
} else {
CScript scriptCode;
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
}
} catch (std::logic_error ex) {
librustzcash_sapling_proving_ctx_free(ctx);
return TransactionBuilderResult("Could not construct signature hash: " + std::string(ex.what()));
}
if (orchardBundle.has_value()) {
auto authorizedBundle = orchardBundle.value().ProveAndSign(dataToBeSigned);
if (authorizedBundle.has_value()) {
mtx.orchardBundle = authorizedBundle.value();
} else {
return TransactionBuilderResult("Failed to create Orchard proof or signatures");
}
}
// Create Sapling spendAuth and binding signatures
for (size_t i = 0; i < spends.size(); i++) {
librustzcash_sapling_spend_sig(
@ -513,15 +632,11 @@ TransactionBuilderResult TransactionBuilder::Build()
void TransactionBuilder::CheckOrSetUsingSprout()
{
if (usingSprout.has_value()) {
if (!usingSprout.value()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't use Sprout with a v5 transaction.");
}
if (orchardBuilder.has_value()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Can't use Sprout with a v5 transaction.");
} else {
usingSprout = true;
// Switch if necessary to a Sprout-supporting transaction format.
auto txVersionInfo = CurrentTxVersionInfo(consensusParams, nHeight, usingSprout.value());
auto txVersionInfo = CurrentTxVersionInfo(consensusParams, nHeight, true);
mtx.nVersionGroupId = txVersionInfo.nVersionGroupId;
mtx.nVersion = txVersionInfo.nVersion;
mtx.nConsensusBranchId = std::nullopt;

View File

@ -21,8 +21,107 @@
#include <optional>
#include <rust/builder.h>
#define NO_MEMO {{0xF6}}
namespace orchard { class UnauthorizedBundle; }
uint256 ProduceZip244SignatureHash(
const CTransaction& tx,
const orchard::UnauthorizedBundle& orchardBundle);
namespace orchard {
/// 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;
Builder() : inner(nullptr, orchard_builder_free) { }
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 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);
/// 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 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(uint256 sighash);
};
} // namespace orchard
struct SpendDescriptionInfo {
libzcash::SaplingExpandedSpendingKey expsk;
libzcash::SaplingNote note;
@ -107,7 +206,6 @@ public:
class TransactionBuilder
{
private:
std::optional<bool> usingSprout;
Consensus::Params consensusParams;
int nHeight;
const CKeyStore* keystore;
@ -115,6 +213,8 @@ private:
CCriticalSection* cs_coinsView;
CMutableTransaction mtx;
CAmount fee = 10000;
std::optional<orchard::Builder> orchardBuilder;
CAmount valueBalanceOrchard = 0;
std::vector<SpendDescriptionInfo> spends;
std::vector<OutputDescriptionInfo> outputs;
@ -131,14 +231,66 @@ public:
TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
std::optional<uint256> orchardAnchor,
CKeyStore* keyStore = nullptr,
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)),
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);
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(

View File

@ -8,6 +8,7 @@
#include "transaction_builder.h"
#include <array>
#include <optional>
#include <rust/ed25519.h>
@ -348,7 +349,7 @@ CWalletTx GetValidSaplingReceive(const Consensus::Params& consensusParams,
auto fvk = sk.expsk.full_viewing_key();
auto pa = sk.ToXFVK().DefaultAddress();
auto builder = TransactionBuilder(consensusParams, 1, &keyStore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keyStore);
builder.SetFee(0);
builder.AddTransparentInput(COutPoint(), scriptPubKey, value);
builder.AddSaplingOutput(fvk.ovk, pa, value, {});

View File

@ -89,7 +89,7 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
builder_ = builder.value();
builder_ = std::move(builder.value());
}
KeyIO keyIO(Params());

View File

@ -111,7 +111,7 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
CCoinsViewCache coinsView(pcoinsTip);
do {
CAmount amountToSend = chooseAmount(availableFunds);
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain, &coinsView, &cs_main);
auto builder = TransactionBuilder(consensusParams, targetHeight_, std::nullopt, pwalletMain, &coinsView, &cs_main);
builder.SetExpiryHeight(targetHeight_ + MIGRATION_EXPIRY_DELTA);
LogPrint("zrpcunsafe", "%s: Beginning creating transaction with Sapling output amount=%s\n", getId(), FormatMoney(amountToSend - DEFAULT_FEE));
std::vector<SproutNoteEntry> fromNotes;

View File

@ -51,7 +51,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
CAmount fee,
bool allowRevealedAmounts,
UniValue contextInfo) :
builder_(builder), ztxoSelector_(ztxoSelector), recipients_(recipients),
builder_(std::move(builder)), ztxoSelector_(ztxoSelector), recipients_(recipients),
mindepth_(minDepth), fee_(fee), allowRevealedAmounts_(allowRevealedAmounts),
contextinfo_(contextInfo)
{

View File

@ -67,7 +67,7 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
PaymentAddress toAddress,
CAmount fee,
UniValue contextInfo) :
builder_(builder), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
builder_(std::move(builder)), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
{
assert(contextualTx.nVersion >= 2); // transaction format version must support vJoinSplit

View File

@ -402,7 +402,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
ASSERT_TRUE(nf);
uint256 nullifier = nf.value();
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
@ -537,7 +537,7 @@ TEST(WalletTests, FindMySaplingNotes) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -682,7 +682,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
auto witness = saplingTree.witness();
// Generate tx to create output note B
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -738,13 +738,13 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
anchor = saplingTree.root();
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000, {});
auto tx2 = builder2.Build().GetTxOrThrow();
// Create conflicting transaction which also spends note B
auto builder3 = TransactionBuilder(consensusParams, 2);
auto builder3 = TransactionBuilder(consensusParams, 2, std::nullopt);
builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999, {});
auto tx3 = builder3.Build().GetTxOrThrow();
@ -833,7 +833,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -918,7 +918,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -1054,7 +1054,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
auto witness = saplingTree.witness();
// Generate transaction, which sends funds to note B
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, note, anchor, witness);
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -1126,7 +1126,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
anchor = saplingTree.root();
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {});
auto tx2 = builder2.Build().GetTxOrThrow();
@ -1861,7 +1861,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
auto testNote = GetTestSaplingNote(pa, 50000);
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
builder.AddSaplingOutput(extfvk.fvk.ovk, pa2, 25000, {});
auto tx = builder.Build().GetTxOrThrow();
@ -2004,7 +2004,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// Generate shielding tx from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 40000, {});
auto tx1 = builder.Build().GetTxOrThrow();
@ -2058,7 +2058,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// Create a Sapling-only transaction
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
auto builder2 = TransactionBuilder(consensusParams, 2);
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
builder2.AddSaplingSpend(expsk, note, anchor, witness);
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {});
auto tx2 = builder2.Build().GetTxOrThrow();

View File

@ -4623,12 +4623,15 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
o.pushKV("fee", std::stod(FormatMoney(nFee)));
UniValue contextInfo = o;
TransactionBuilder builder(chainparams.GetConsensus(), nextBlockHeight, pwalletMain);
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
auto orchardAnchor = std::nullopt;
TransactionBuilder builder(chainparams.GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_sendmany(builder, ztxoSelector, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
new AsyncRPCOperation_sendmany(
std::move(builder), ztxoSelector, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
);
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
@ -4972,8 +4975,10 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
contextInfo.pushKV("fee", ValueFromAmount(nFee));
// Builder (used if Sapling addresses are involved)
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
auto orchardAnchor = std::nullopt;
TransactionBuilder builder = TransactionBuilder(
Params().GetConsensus(), nextBlockHeight, pwalletMain);
Params().GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
// Contextual transaction we will build on
// (used if no Sapling addresses are involved)
@ -4985,7 +4990,8 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress.value(), nFee, contextInfo) );
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(
std::move(builder), contextualTx, inputs, destaddress.value(), nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
@ -5439,12 +5445,15 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
// Builder (used if Sapling addresses are involved)
std::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
auto orchardAnchor = std::nullopt;
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
new AsyncRPCOperation_mergetoaddress(
std::move(builder), contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();

View File

@ -1191,7 +1191,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
// Mutable tx containing contextual information we need to build tx
UniValue retValue = CallRPC("getblockcount");
int nHeight = retValue.get_int();
TransactionBuilder builder(Params().GetConsensus(), nHeight + 1, pwalletMain);
TransactionBuilder builder(Params().GetConsensus(), nHeight + 1, std::nullopt, pwalletMain);
}
BOOST_AUTO_TEST_CASE(asyncrpcoperation_sign_send_raw_transaction) {
@ -1234,9 +1234,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// there are no utxos to spend
{
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
@ -1246,9 +1246,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// there are no unspent notes to spend
{
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
operation->main();
BOOST_CHECK(operation->isFailed());
std::string msg = operation->getErrorMessage();
@ -1258,9 +1258,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
// get_memo_from_hex_string())
{
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
@ -1355,12 +1355,12 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
pwalletMain->AddToWallet(wtx, true, NULL);
// Context that z_sendmany requires
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, std::nullopt, pwalletMain);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true).value();
std::vector<SendManyRecipient> recipients = { SendManyRecipient(pa, 1*COIN, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 0));
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0));
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
// Enable test mode so tx is not sent

View File

@ -9,6 +9,8 @@
#include "zcash/address/zip32.h"
#include <rust/orchard/keys.h>
namespace orchard { class Builder; }
namespace libzcash {
class OrchardIncomingViewingKey;
@ -23,6 +25,7 @@ private:
OrchardRawAddress(OrchardRawAddressPtr* ptr) : inner(ptr, orchard_address_free) {}
friend class OrchardIncomingViewingKey;
friend class ::orchard::Builder;
public:
OrchardRawAddress(OrchardRawAddress&& key) : inner(std::move(key.inner)) {}