$(package)_download_path=https://github.com/zcash/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz
-$(package)_sha256_hash=5a50aae38a9ef4823cd319278ad95706a129cc091e1cca9342802f1ff75aba15
-$(package)_git_commit=93e26d1d8716ac88f8bb372d442315edcd2deabd
+$(package)_sha256_hash=86139e8a6cc76ae1a04ed8229beef760de1beb2a72fbe15450b44c3649d48a5d
+$(package)_git_commit=32026ea0a13337548f1b6e57d99f0b7b6b9d0d81
$(package)_dependencies=rust $(rust_crates)
$(package)_patches=cargo.config
timedata.h \
tinyformat.h \
torcontrol.h \
+ transaction_builder.h \
txdb.h \
txmempool.h \
ui_interface.h \
script/script_error.cpp \
script/sign.cpp \
script/standard.cpp \
+ transaction_builder.cpp \
$(BITCOIN_CORE_H) \
$(LIBZCASH_H)
gtest/test_rpc.cpp \
gtest/test_sapling_note.cpp \
gtest/test_transaction.cpp \
+ gtest/test_transaction_builder.cpp \
gtest/test_upgrades.cpp \
gtest/test_validation.cpp \
gtest/test_circuit.cpp \
--- /dev/null
+#include "chainparams.h"
+#include "consensus/params.h"
+#include "consensus/validation.h"
+#include "main.h"
+#include "transaction_builder.h"
+#include "zcash/Address.hpp"
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+TEST(TransactionBuilder, Invoke) {
+ SelectParams(CBaseChainParams::REGTEST);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
+ auto consensusParams = Params().GetConsensus();
+
+ auto sk_from = libzcash::SaplingSpendingKey::random();
+ auto fvk_from = sk_from.full_viewing_key();
+
+ auto sk = libzcash::SaplingSpendingKey::random();
+ auto xsk = sk.expanded_spending_key();
+ auto fvk = sk.full_viewing_key();
+ auto ivk = fvk.in_viewing_key();
+ libzcash::diversifier_t d = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ auto pk = *ivk.address(d);
+
+ // Create a shielding transaction from transparent to Sapling
+ // TODO: Add transparent inputs :P
+ auto builder1 = TransactionBuilder(consensusParams, 1);
+ builder1.AddSaplingOutput(fvk_from, pk, 50, {});
+ auto maybe_tx1 = builder1.Build();
+ ASSERT_EQ(static_cast<bool>(maybe_tx1), true);
+ auto tx1 = maybe_tx1.get();
+
+ EXPECT_EQ(tx1.vin.size(), 0);
+ EXPECT_EQ(tx1.vout.size(), 0);
+ EXPECT_EQ(tx1.vjoinsplit.size(), 0);
+ EXPECT_EQ(tx1.vShieldedSpend.size(), 0);
+ EXPECT_EQ(tx1.vShieldedOutput.size(), 1);
+ EXPECT_EQ(tx1.valueBalance, -50);
+
+ CValidationState state;
+ EXPECT_TRUE(ContextualCheckTransaction(tx1, state, 2, 0));
+ EXPECT_EQ(state.GetRejectReason(), "");
+
+ // Prepare to spend the note that was just created
+ auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt(
+ tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey
+ );
+ ASSERT_EQ(static_cast<bool>(maybe_pt), true);
+ auto maybe_note = maybe_pt.get().note(ivk);
+ ASSERT_EQ(static_cast<bool>(maybe_note), true);
+ auto note = maybe_note.get();
+ ZCSaplingIncrementalMerkleTree tree;
+ tree.append(tx1.vShieldedOutput[0].cm);
+ auto anchor = tree.root();
+ auto witness = tree.witness();
+
+ // Create a Sapling-only transaction
+ auto builder2 = TransactionBuilder(consensusParams, 2);
+ builder2.AddSaplingSpend(xsk, note, anchor, witness);
+ builder2.AddSaplingOutput(fvk, pk, 25, {});
+ auto maybe_tx2 = builder2.Build();
+ ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
+ auto tx2 = maybe_tx2.get();
+
+ EXPECT_EQ(tx2.vin.size(), 0);
+ EXPECT_EQ(tx2.vout.size(), 0);
+ EXPECT_EQ(tx2.vjoinsplit.size(), 0);
+ EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
+ EXPECT_EQ(tx2.vShieldedOutput.size(), 1);
+ EXPECT_EQ(tx2.valueBalance, 25);
+
+ EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0));
+ EXPECT_EQ(state.GetRejectReason(), "");
+
+ // Revert to default
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
+ UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
+}
--- /dev/null
+// Copyright (c) 2018 The Zcash developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "transaction_builder.h"
+
+#include "main.h"
+#include "script/script.h"
+
+#include <boost/variant.hpp>
+#include <librustzcash.h>
+
+SpendDescriptionInfo::SpendDescriptionInfo(
+ libzcash::SaplingExpandedSpendingKey xsk,
+ libzcash::SaplingNote note,
+ uint256 anchor,
+ ZCSaplingIncrementalWitness witness
+) : xsk(xsk), note(note), anchor(anchor), witness(witness)
+{
+ librustzcash_sapling_generate_r(alpha.begin());
+}
+
+TransactionBuilder::TransactionBuilder(
+ const Consensus::Params& consensusParams, int nHeight
+) : consensusParams(consensusParams), nHeight(nHeight)
+{
+ mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
+}
+
+void TransactionBuilder::AddSaplingSpend(
+ libzcash::SaplingExpandedSpendingKey xsk,
+ libzcash::SaplingNote note,
+ uint256 anchor,
+ ZCSaplingIncrementalWitness witness
+) {
+ spends.emplace_back(xsk, note, anchor, witness);
+ mtx.valueBalance += note.value();
+}
+
+void TransactionBuilder::AddSaplingOutput(
+ libzcash::SaplingFullViewingKey from,
+ libzcash::SaplingPaymentAddress to,
+ CAmount value,
+ std::array<unsigned char, ZC_MEMO_SIZE> memo
+) {
+ auto note = libzcash::SaplingNote(to, value);
+ outputs.emplace_back(from.ovk, note, memo);
+ mtx.valueBalance -= value;
+}
+
+boost::optional<CTransaction> TransactionBuilder::Build()
+{
+ auto ctx = librustzcash_sapling_proving_ctx_init();
+
+ // Create Sapling SpendDescriptions
+ for (auto spend : spends) {
+ auto cm = spend.note.cm();
+ auto nf = spend.note.nullifier(
+ spend.xsk.full_viewing_key(), spend.witness.position());
+ if (!(cm && nf)) {
+ librustzcash_sapling_proving_ctx_free(ctx);
+ return boost::none;
+ }
+
+ CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
+ ss << spend.witness.path();
+ std::vector<unsigned char> witness(ss.begin(), ss.end());
+
+ SpendDescription sdesc;
+ if (!librustzcash_sapling_spend_proof(
+ ctx,
+ spend.xsk.full_viewing_key().ak.begin(),
+ spend.xsk.nsk.begin(),
+ spend.note.d.data(),
+ spend.note.r.begin(),
+ spend.alpha.begin(),
+ spend.note.value(),
+ spend.anchor.begin(),
+ witness.data(),
+ sdesc.cv.begin(),
+ sdesc.rk.begin(),
+ sdesc.zkproof.data()
+ )) {
+ librustzcash_sapling_proving_ctx_free(ctx);
+ return boost::none;
+ }
+
+ sdesc.anchor = spend.anchor;
+ sdesc.nullifier = *nf;
+ mtx.vShieldedSpend.push_back(sdesc);
+ }
+
+ // Create Sapling OutputDescriptions
+ for (auto output : outputs) {
+ auto cm = output.note.cm();
+ if (!cm) {
+ librustzcash_sapling_proving_ctx_free(ctx);
+ return boost::none;
+ }
+
+ libzcash::SaplingNotePlaintext notePlaintext(output.note, output.memo);
+
+ auto res = notePlaintext.encrypt(output.note.pk_d);
+ if (!res) {
+ librustzcash_sapling_proving_ctx_free(ctx);
+ return boost::none;
+ }
+ auto enc = res.get();
+ auto encryptor = enc.second;
+
+ OutputDescription odesc;
+ if (!librustzcash_sapling_output_proof(
+ ctx,
+ encryptor.get_esk().begin(),
+ output.note.d.data(),
+ output.note.pk_d.begin(),
+ output.note.r.begin(),
+ output.note.value(),
+ odesc.cv.begin(),
+ odesc.zkproof.begin()
+ )) {
+ librustzcash_sapling_proving_ctx_free(ctx);
+ return boost::none;
+ }
+
+ odesc.cm = *cm;
+ odesc.ephemeralKey = encryptor.get_epk();
+ odesc.encCiphertext = enc.first;
+
+ libzcash::SaplingOutgoingPlaintext outPlaintext(output.note.pk_d, encryptor.get_esk());
+ odesc.outCiphertext = outPlaintext.encrypt(
+ output.ovk,
+ odesc.cv,
+ odesc.cm,
+ encryptor
+ );
+ mtx.vShieldedOutput.push_back(odesc);
+ }
+
+ // Calculate SignatureHash
+ 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);
+ return boost::none;
+ }
+
+ // Create Sapling spendAuth and binding signatures
+ for (size_t i = 0; i < spends.size(); i++) {
+ librustzcash_sapling_spend_sig(
+ spends[i].xsk.ask.begin(),
+ 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);
+ return CTransaction(mtx);
+}
--- /dev/null
+// Copyright (c) 2018 The Zcash developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef TRANSACTION_BUILDER_H
+#define TRANSACTION_BUILDER_H
+
+#include "consensus/params.h"
+#include "primitives/transaction.h"
+#include "uint256.h"
+#include "zcash/Address.hpp"
+#include "zcash/IncrementalMerkleTree.hpp"
+#include "zcash/Note.hpp"
+#include "zcash/NoteEncryption.hpp"
+
+#include <boost/optional.hpp>
+
+struct SpendDescriptionInfo
+{
+ libzcash::SaplingExpandedSpendingKey xsk;
+ libzcash::SaplingNote note;
+ uint256 alpha;
+ uint256 anchor;
+ ZCSaplingIncrementalWitness witness;
+
+ SpendDescriptionInfo(
+ libzcash::SaplingExpandedSpendingKey xsk,
+ libzcash::SaplingNote note,
+ uint256 anchor,
+ ZCSaplingIncrementalWitness 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) {}
+};
+
+class TransactionBuilder
+{
+private:
+ Consensus::Params consensusParams;
+ int nHeight;
+ CMutableTransaction mtx;
+
+ std::vector<SpendDescriptionInfo> spends;
+ std::vector<OutputDescriptionInfo> outputs;
+
+public:
+ TransactionBuilder(const Consensus::Params& consensusParams, int nHeight);
+
+ void AddSaplingSpend(
+ libzcash::SaplingExpandedSpendingKey xsk,
+ libzcash::SaplingNote note,
+ uint256 anchor,
+ ZCSaplingIncrementalWitness witness);
+
+ void AddSaplingOutput(
+ libzcash::SaplingFullViewingKey from,
+ libzcash::SaplingPaymentAddress to,
+ CAmount value,
+ std::array<unsigned char, ZC_MEMO_SIZE> memo);
+
+ boost::optional<CTransaction> Build();
+};
+
+#endif /* TRANSACTION_BUILDER_H */