From 66a519f55dc6b3a1c403b865f6675bef37a04d88 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 16 Jun 2018 23:06:15 +1200 Subject: [PATCH 01/13] Alter SaplingNote::nullifier() to take a SaplingFullViewingKey This means the API will work if you only have a SaplingExtendedSpendingKey, as will be the case with ZIP 32. --- src/gtest/test_sapling_note.cpp | 2 +- src/zcash/Note.cpp | 3 +-- src/zcash/Note.hpp | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gtest/test_sapling_note.cpp b/src/gtest/test_sapling_note.cpp index 06f2d6188..3e336ec7e 100644 --- a/src/gtest/test_sapling_note.cpp +++ b/src/gtest/test_sapling_note.cpp @@ -49,7 +49,7 @@ TEST(SaplingNote, TestVectors) // Test nullifier SaplingSpendingKey spendingKey(sk); - ASSERT_EQ(note.nullifier(spendingKey, note_pos), nf); + ASSERT_EQ(note.nullifier(spendingKey.full_viewing_key(), note_pos), nf); } diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 1108e1db5..c6c72e297 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -65,9 +65,8 @@ boost::optional SaplingNote::cm() const { } // Call librustzcash to compute the nullifier -boost::optional SaplingNote::nullifier(const SaplingSpendingKey& sk, const uint64_t position) const +boost::optional SaplingNote::nullifier(const SaplingFullViewingKey& vk, const uint64_t position) const { - auto vk = sk.full_viewing_key(); auto ak = vk.ak; auto nk = vk.nk; diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index 5a3e55dfe..f1b8e4323 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -57,7 +57,7 @@ public: virtual ~SaplingNote() {}; boost::optional cm() const; - boost::optional nullifier(const SaplingSpendingKey &sk, const uint64_t position) const; + boost::optional nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const; }; class BaseNotePlaintext { From 70a7535ae8c782943b0fda1e56b41ac260732d30 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Jul 2018 10:34:48 -0600 Subject: [PATCH 02/13] Expose note position in IncrementalMerkleWitness --- src/zcash/IncrementalMerkleTree.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zcash/IncrementalMerkleTree.hpp b/src/zcash/IncrementalMerkleTree.hpp index 912e9fff0..1e4c8ac85 100644 --- a/src/zcash/IncrementalMerkleTree.hpp +++ b/src/zcash/IncrementalMerkleTree.hpp @@ -170,6 +170,10 @@ public: return tree.last(); } + uint64_t position() const { + return tree.size() - 1; + } + Hash root() const { return tree.root(Depth, partial_path()); } From e691e21f4021e8d3cc8b45deec92a072b3015698 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Jul 2018 10:36:38 -0600 Subject: [PATCH 03/13] TransactionBuilder with support for creating Sapling-only transactions --- depends/packages/librustzcash.mk | 4 +- src/Makefile.am | 2 + src/Makefile.gtest.include | 1 + src/gtest/test_transaction_builder.cpp | 80 ++++++++++++ src/transaction_builder.cpp | 169 +++++++++++++++++++++++++ src/transaction_builder.h | 73 +++++++++++ 6 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 src/gtest/test_transaction_builder.cpp create mode 100644 src/transaction_builder.cpp create mode 100644 src/transaction_builder.h diff --git a/depends/packages/librustzcash.mk b/depends/packages/librustzcash.mk index 3a338cc21..03c172dc9 100644 --- a/depends/packages/librustzcash.mk +++ b/depends/packages/librustzcash.mk @@ -3,8 +3,8 @@ $(package)_version=0.1 $(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 diff --git a/src/Makefile.am b/src/Makefile.am index cf5cc46cf..804bc4865 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -199,6 +199,7 @@ BITCOIN_CORE_H = \ timedata.h \ tinyformat.h \ torcontrol.h \ + transaction_builder.h \ txdb.h \ txmempool.h \ ui_interface.h \ @@ -384,6 +385,7 @@ libbitcoin_common_a_SOURCES = \ script/script_error.cpp \ script/sign.cpp \ script/standard.cpp \ + transaction_builder.cpp \ $(BITCOIN_CORE_H) \ $(LIBZCASH_H) diff --git a/src/Makefile.gtest.include b/src/Makefile.gtest.include index 519fe1552..e089d5783 100644 --- a/src/Makefile.gtest.include +++ b/src/Makefile.gtest.include @@ -34,6 +34,7 @@ zcash_gtest_SOURCES += \ 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 \ diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp new file mode 100644 index 000000000..9f56da19f --- /dev/null +++ b/src/gtest/test_transaction_builder.cpp @@ -0,0 +1,80 @@ +#include "chainparams.h" +#include "consensus/params.h" +#include "consensus/validation.h" +#include "main.h" +#include "transaction_builder.h" +#include "zcash/Address.hpp" + +#include +#include + +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(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(maybe_pt), true); + auto maybe_note = maybe_pt.get().note(ivk); + ASSERT_EQ(static_cast(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(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); +} diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp new file mode 100644 index 000000000..91de871f9 --- /dev/null +++ b/src/transaction_builder.cpp @@ -0,0 +1,169 @@ +// 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 +#include + +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 memo +) { + auto note = libzcash::SaplingNote(to, value); + outputs.emplace_back(from.ovk, note, memo); + mtx.valueBalance -= value; +} + +boost::optional 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 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); +} diff --git a/src/transaction_builder.h b/src/transaction_builder.h new file mode 100644 index 000000000..e3d7096e0 --- /dev/null +++ b/src/transaction_builder.h @@ -0,0 +1,73 @@ +// 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 + +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 memo; + + OutputDescriptionInfo( + uint256 ovk, + libzcash::SaplingNote note, + std::array memo) : ovk(ovk), note(note), memo(memo) {} +}; + +class TransactionBuilder +{ +private: + Consensus::Params consensusParams; + int nHeight; + CMutableTransaction mtx; + + std::vector spends; + std::vector 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 memo); + + boost::optional Build(); +}; + +#endif /* TRANSACTION_BUILDER_H */ From e5dc5228ea33cf76507484814430c94b228b16f9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 27 Jul 2018 09:46:38 +0200 Subject: [PATCH 04/13] TransactionBuilder: Check that all anchors in a transaction are identical This reduces the amount of information that is leaked by the choice of anchor. In future we will make a protocol change to enforce that all inputs use the same anchor. --- src/gtest/test_transaction_builder.cpp | 5 ++++- src/transaction_builder.cpp | 14 +++++++++++--- src/transaction_builder.h | 4 +++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 9f56da19f..3755dbf7f 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -58,7 +58,10 @@ TEST(TransactionBuilder, Invoke) { // Create a Sapling-only transaction auto builder2 = TransactionBuilder(consensusParams, 2); - builder2.AddSaplingSpend(xsk, note, anchor, witness); + ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness)); + // Check that trying to add a different anchor fails + ASSERT_FALSE(builder2.AddSaplingSpend(xsk, note, uint256(), witness)); + builder2.AddSaplingOutput(fvk, pk, 25, {}); auto maybe_tx2 = builder2.Build(); ASSERT_EQ(static_cast(maybe_tx2), true); diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 91de871f9..1e4d266d0 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -27,14 +27,22 @@ TransactionBuilder::TransactionBuilder( mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); } -void TransactionBuilder::AddSaplingSpend( +bool TransactionBuilder::AddSaplingSpend( libzcash::SaplingExpandedSpendingKey xsk, libzcash::SaplingNote note, uint256 anchor, - ZCSaplingIncrementalWitness witness -) { + ZCSaplingIncrementalWitness witness) +{ + // Consistency check: all anchors must equal the first one + if (!spends.empty()) { + if (spends[0].anchor != anchor) { + return false; + } + } + spends.emplace_back(xsk, note, anchor, witness); mtx.valueBalance += note.value(); + return true; } void TransactionBuilder::AddSaplingOutput( diff --git a/src/transaction_builder.h b/src/transaction_builder.h index e3d7096e0..2279bae6c 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -55,7 +55,9 @@ private: public: TransactionBuilder(const Consensus::Params& consensusParams, int nHeight); - void AddSaplingSpend( + // Returns false if the anchor does not match the anchor used by + // previously-added Sapling spends. + bool AddSaplingSpend( libzcash::SaplingExpandedSpendingKey xsk, libzcash::SaplingNote note, uint256 anchor, From 25bb05de23ec057c62ab97402df4d5ec127ae64e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 27 Jul 2018 09:49:43 +0200 Subject: [PATCH 05/13] Formatting --- src/gtest/test_transaction_builder.cpp | 8 ++-- src/transaction_builder.cpp | 56 ++++++++++++-------------- src/transaction_builder.h | 6 +-- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 3755dbf7f..90cfef008 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -5,10 +5,11 @@ #include "transaction_builder.h" #include "zcash/Address.hpp" -#include #include +#include -TEST(TransactionBuilder, Invoke) { +TEST(TransactionBuilder, Invoke) +{ SelectParams(CBaseChainParams::REGTEST); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); @@ -45,8 +46,7 @@ TEST(TransactionBuilder, Invoke) { // Prepare to spend the note that was just created auto maybe_pt = libzcash::SaplingNotePlaintext::decrypt( - tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey - ); + tx1.vShieldedOutput[0].encCiphertext, ivk, tx1.vShieldedOutput[0].ephemeralKey); ASSERT_EQ(static_cast(maybe_pt), true); auto maybe_note = maybe_pt.get().note(ivk); ASSERT_EQ(static_cast(maybe_note), true); diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 1e4d266d0..0a6add6e7 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -14,15 +14,14 @@ SpendDescriptionInfo::SpendDescriptionInfo( libzcash::SaplingExpandedSpendingKey xsk, libzcash::SaplingNote note, uint256 anchor, - ZCSaplingIncrementalWitness witness -) : xsk(xsk), note(note), anchor(anchor), witness(witness) + 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) + const Consensus::Params& consensusParams, + int nHeight) : consensusParams(consensusParams), nHeight(nHeight) { mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); } @@ -49,8 +48,8 @@ void TransactionBuilder::AddSaplingOutput( libzcash::SaplingFullViewingKey from, libzcash::SaplingPaymentAddress to, CAmount value, - std::array memo -) { + std::array memo) +{ auto note = libzcash::SaplingNote(to, value); outputs.emplace_back(from.ovk, note, memo); mtx.valueBalance -= value; @@ -76,19 +75,18 @@ boost::optional TransactionBuilder::Build() 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() - )) { + 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; } @@ -118,15 +116,14 @@ boost::optional TransactionBuilder::Build() 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() - )) { + 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; } @@ -140,8 +137,7 @@ boost::optional TransactionBuilder::Build() output.ovk, odesc.cv, odesc.cm, - encryptor - ); + encryptor); mtx.vShieldedOutput.push_back(odesc); } diff --git a/src/transaction_builder.h b/src/transaction_builder.h index 2279bae6c..30567b9e0 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -15,8 +15,7 @@ #include -struct SpendDescriptionInfo -{ +struct SpendDescriptionInfo { libzcash::SaplingExpandedSpendingKey xsk; libzcash::SaplingNote note; uint256 alpha; @@ -30,8 +29,7 @@ struct SpendDescriptionInfo ZCSaplingIncrementalWitness witness); }; -struct OutputDescriptionInfo -{ +struct OutputDescriptionInfo { uint256 ovk; libzcash::SaplingNote note; std::array memo; From 3fd0a269e1ded42c26ad9a2e0614460cd86682b9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 27 Jul 2018 17:18:49 +0200 Subject: [PATCH 06/13] test: Move ECC_Start() call into src/gtest/main.cpp --- src/gtest/main.cpp | 3 +++ src/wallet/gtest/test_wallet_zkeys.cpp | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gtest/main.cpp b/src/gtest/main.cpp index 6576edc03..f3db2a4bb 100644 --- a/src/gtest/main.cpp +++ b/src/gtest/main.cpp @@ -1,5 +1,6 @@ #include "gmock/gmock.h" #include "crypto/common.h" +#include "key.h" #include "pubkey.h" #include "zcash/JoinSplit.hpp" #include "util.h" @@ -20,6 +21,8 @@ ZCJoinSplit* params; int main(int argc, char **argv) { assert(init_and_check_sodium() != -1); + ECC_Start(); + libsnark::default_r1cs_ppzksnark_pp::init_public_params(); libsnark::inhibit_profiling_info = true; libsnark::inhibit_profiling_counters = true; diff --git a/src/wallet/gtest/test_wallet_zkeys.cpp b/src/wallet/gtest/test_wallet_zkeys.cpp index 317e4ff54..9fe107ccc 100644 --- a/src/wallet/gtest/test_wallet_zkeys.cpp +++ b/src/wallet/gtest/test_wallet_zkeys.cpp @@ -259,8 +259,6 @@ TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) { * This test covers methods on CWalletDB to load/save crypted z keys. */ TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) { - ECC_Start(); - SelectParams(CBaseChainParams::TESTNET); // Get temporary and unique path for file. From 3466b4677e3f3f868d397b3d341d70482d2a4317 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 30 Jul 2018 11:03:29 +0100 Subject: [PATCH 07/13] TransactionBuilder: Add support for transparent inputs and outputs --- src/gtest/test_transaction_builder.cpp | 98 ++++++++++++++++++++++++-- src/transaction_builder.cpp | 76 +++++++++++++++++++- src/transaction_builder.h | 21 +++++- 3 files changed, 184 insertions(+), 11 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 90cfef008..939ec2881 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -1,13 +1,17 @@ #include "chainparams.h" #include "consensus/params.h" #include "consensus/validation.h" +#include "key_io.h" #include "main.h" +#include "pubkey.h" #include "transaction_builder.h" #include "zcash/Address.hpp" #include #include +static const std::string tSecretRegtest = "cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN"; + TEST(TransactionBuilder, Invoke) { SelectParams(CBaseChainParams::REGTEST); @@ -15,6 +19,11 @@ TEST(TransactionBuilder, Invoke) UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); auto consensusParams = Params().GetConsensus(); + CBasicKeyStore keystore; + CKey tsk = DecodeSecret(tSecretRegtest); + keystore.AddKey(tsk); + auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID()); + auto sk_from = libzcash::SaplingSpendingKey::random(); auto fvk_from = sk_from.full_viewing_key(); @@ -26,19 +35,20 @@ TEST(TransactionBuilder, Invoke) 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, {}); + // 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee + auto builder1 = TransactionBuilder(consensusParams, 1, &keystore); + builder1.AddTransparentInput(COutPoint(), scriptPubKey, 50000); + builder1.AddSaplingOutput(fvk_from, pk, 40000, {}); auto maybe_tx1 = builder1.Build(); ASSERT_EQ(static_cast(maybe_tx1), true); auto tx1 = maybe_tx1.get(); - EXPECT_EQ(tx1.vin.size(), 0); + EXPECT_EQ(tx1.vin.size(), 1); 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); + EXPECT_EQ(tx1.valueBalance, -40000); CValidationState state; EXPECT_TRUE(ContextualCheckTransaction(tx1, state, 2, 0)); @@ -57,12 +67,13 @@ TEST(TransactionBuilder, Invoke) auto witness = tree.witness(); // Create a Sapling-only transaction + // 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 ZEC change auto builder2 = TransactionBuilder(consensusParams, 2); ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness)); // Check that trying to add a different anchor fails ASSERT_FALSE(builder2.AddSaplingSpend(xsk, note, uint256(), witness)); - builder2.AddSaplingOutput(fvk, pk, 25, {}); + builder2.AddSaplingOutput(fvk, pk, 25000, {}); auto maybe_tx2 = builder2.Build(); ASSERT_EQ(static_cast(maybe_tx2), true); auto tx2 = maybe_tx2.get(); @@ -72,7 +83,7 @@ TEST(TransactionBuilder, Invoke) EXPECT_EQ(tx2.vjoinsplit.size(), 0); EXPECT_EQ(tx2.vShieldedSpend.size(), 1); EXPECT_EQ(tx2.vShieldedOutput.size(), 1); - EXPECT_EQ(tx2.valueBalance, 25); + EXPECT_EQ(tx2.valueBalance, 15000); EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0)); EXPECT_EQ(state.GetRejectReason(), ""); @@ -81,3 +92,76 @@ TEST(TransactionBuilder, Invoke) UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } + +TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore) +{ + auto consensusParams = Params().GetConsensus(); + + auto builder = TransactionBuilder(consensusParams, 1); + ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error); +} + +TEST(TransactionBuilder, RejectsInvalidTransparentOutput) +{ + auto consensusParams = Params().GetConsensus(); + + // Default CTxDestination type is an invalid address + CTxDestination taddr; + auto builder = TransactionBuilder(consensusParams, 1); + EXPECT_FALSE(builder.AddTransparentOutput(taddr, 50)); +} + +TEST(TransactionBuilder, FailsWithNegativeChange) +{ + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + auto consensusParams = Params().GetConsensus(); + + // Generate dummy Sapling address + auto sk = libzcash::SaplingSpendingKey::random(); + auto xsk = sk.expanded_spending_key(); + auto fvk = sk.full_viewing_key(); + auto pk = sk.default_address(); + + // Set up dummy transparent address + CBasicKeyStore keystore; + CKey tsk = DecodeSecret(tSecretRegtest); + keystore.AddKey(tsk); + auto tkeyid = tsk.GetPubKey().GetID(); + auto scriptPubKey = GetScriptForDestination(tkeyid); + CTxDestination taddr = tkeyid; + + // Generate dummy Sapling note + libzcash::SaplingNote note(pk, 59999); + auto cm = note.cm().value(); + ZCSaplingIncrementalMerkleTree tree; + tree.append(cm); + auto anchor = tree.root(); + auto witness = tree.witness(); + + // Fail if there is only a Sapling output + // 0.0005 z-ZEC out, 0.0001 t-ZEC fee + auto builder = TransactionBuilder(consensusParams, 1); + builder.AddSaplingOutput(fvk, pk, 50000, {}); + EXPECT_FALSE(static_cast(builder.Build())); + + // Fail if there is only a transparent output + // 0.0005 t-ZEC out, 0.0001 t-ZEC fee + builder = TransactionBuilder(consensusParams, 1, &keystore); + EXPECT_TRUE(builder.AddTransparentOutput(taddr, 50000)); + EXPECT_FALSE(static_cast(builder.Build())); + + // Fails if there is insufficient input + // 0.0005 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in + EXPECT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + EXPECT_FALSE(static_cast(builder.Build())); + + // Succeeds if there is sufficient input + builder.AddTransparentInput(COutPoint(), scriptPubKey, 1); + EXPECT_TRUE(static_cast(builder.Build())); + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 0a6add6e7..25e1e2b17 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -5,7 +5,8 @@ #include "transaction_builder.h" #include "main.h" -#include "script/script.h" +#include "pubkey.h" +#include "script/sign.h" #include #include @@ -21,7 +22,8 @@ SpendDescriptionInfo::SpendDescriptionInfo( TransactionBuilder::TransactionBuilder( const Consensus::Params& consensusParams, - int nHeight) : consensusParams(consensusParams), nHeight(nHeight) + int nHeight, + CKeyStore* keystore) : consensusParams(consensusParams), nHeight(nHeight), keystore(keystore) { mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); } @@ -55,8 +57,55 @@ void TransactionBuilder::AddSaplingOutput( mtx.valueBalance -= value; } +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); +} + +bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value) +{ + if (!IsValidDestination(to)) { + return false; + } + + CScript scriptPubKey = GetScriptForDestination(to); + CTxOut out(value, scriptPubKey); + mtx.vout.push_back(out); + return true; +} + boost::optional TransactionBuilder::Build() { + // Fixed fee + const CAmount fee = 10000; + + // + // Consistency checks + // + + // Valid change + CAmount change = mtx.valueBalance - fee; + for (auto tIn : tIns) { + change += tIn.value; + } + for (auto tOut : mtx.vout) { + change -= tOut.nValue; + } + if (change < 0) { + return boost::none; + } + + // TODO: Create change output (currently, the change is added to the fee) + + // + // Sapling spends and outputs + // + auto ctx = librustzcash_sapling_proving_ctx_init(); // Create Sapling SpendDescriptions @@ -141,7 +190,10 @@ boost::optional TransactionBuilder::Build() mtx.vShieldedOutput.push_back(odesc); } - // Calculate SignatureHash + // + // Signatures + // + auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams); // Empty output script. @@ -169,5 +221,23 @@ boost::optional TransactionBuilder::Build() mtx.bindingSig.data()); librustzcash_sapling_proving_ctx_free(ctx); + + // 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) { + return boost::none; + } else { + UpdateTransaction(mtx, nIn, sigdata); + } + } + return CTransaction(mtx); } diff --git a/src/transaction_builder.h b/src/transaction_builder.h index 30567b9e0..42ce3147d 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -6,7 +6,10 @@ #define TRANSACTION_BUILDER_H #include "consensus/params.h" +#include "keystore.h" #include "primitives/transaction.h" +#include "script/script.h" +#include "script/standard.h" #include "uint256.h" #include "zcash/Address.hpp" #include "zcash/IncrementalMerkleTree.hpp" @@ -40,18 +43,29 @@ struct OutputDescriptionInfo { std::array memo) : ovk(ovk), note(note), memo(memo) {} }; +struct TransparentInputInfo { + CScript scriptPubKey; + CAmount value; + + TransparentInputInfo( + CScript scriptPubKey, + CAmount value) : scriptPubKey(scriptPubKey), value(value) {} +}; + class TransactionBuilder { private: Consensus::Params consensusParams; int nHeight; + const CKeyStore* keystore; CMutableTransaction mtx; std::vector spends; std::vector outputs; + std::vector tIns; public: - TransactionBuilder(const Consensus::Params& consensusParams, int nHeight); + TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr); // Returns false if the anchor does not match the anchor used by // previously-added Sapling spends. @@ -67,6 +81,11 @@ public: CAmount value, std::array memo); + // Assumes that the value correctly corresponds to the provided UTXO. + void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value); + + bool AddTransparentOutput(CTxDestination& to, CAmount value); + boost::optional Build(); }; From 45c0d1ec848f46c504c12ca4e447a3402e9d8381 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 30 Jul 2018 12:36:42 +0100 Subject: [PATCH 08/13] TransactionBuilder: Add change output to transaction --- src/gtest/test_transaction_builder.cpp | 113 ++++++++++++++++++++++++- src/transaction_builder.cpp | 38 ++++++++- src/transaction_builder.h | 7 ++ 3 files changed, 154 insertions(+), 4 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 939ec2881..f7ab416a3 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -67,7 +67,7 @@ TEST(TransactionBuilder, Invoke) auto witness = tree.witness(); // Create a Sapling-only transaction - // 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 ZEC change + // 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change auto builder2 = TransactionBuilder(consensusParams, 2); ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness)); // Check that trying to add a different anchor fails @@ -82,8 +82,8 @@ TEST(TransactionBuilder, Invoke) 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, 15000); + EXPECT_EQ(tx2.vShieldedOutput.size(), 2); + EXPECT_EQ(tx2.valueBalance, 10000); EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0)); EXPECT_EQ(state.GetRejectReason(), ""); @@ -111,6 +111,16 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput) EXPECT_FALSE(builder.AddTransparentOutput(taddr, 50)); } +TEST(TransactionBuilder, RejectsInvalidTransparentChangeAddress) +{ + auto consensusParams = Params().GetConsensus(); + + // Default CTxDestination type is an invalid address + CTxDestination taddr; + auto builder = TransactionBuilder(consensusParams, 1); + EXPECT_FALSE(builder.SendChangeTo(taddr)); +} + TEST(TransactionBuilder, FailsWithNegativeChange) { SelectParams(CBaseChainParams::REGTEST); @@ -165,3 +175,100 @@ TEST(TransactionBuilder, FailsWithNegativeChange) UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } + +TEST(TransactionBuilder, ChangeOutput) +{ + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + auto consensusParams = Params().GetConsensus(); + + // Generate dummy Sapling address + auto sk = libzcash::SaplingSpendingKey::random(); + auto xsk = sk.expanded_spending_key(); + auto pk = sk.default_address(); + + // Generate dummy Sapling note + libzcash::SaplingNote note(pk, 25000); + auto cm = note.cm().value(); + ZCSaplingIncrementalMerkleTree tree; + tree.append(cm); + auto anchor = tree.root(); + auto witness = tree.witness(); + + // Generate change Sapling address + auto sk2 = libzcash::SaplingSpendingKey::random(); + auto fvkOut = sk2.full_viewing_key(); + auto zChangeAddr = sk2.default_address(); + + // Set up dummy transparent address + CBasicKeyStore keystore; + CKey tsk = DecodeSecret(tSecretRegtest); + keystore.AddKey(tsk); + auto tkeyid = tsk.GetPubKey().GetID(); + auto scriptPubKey = GetScriptForDestination(tkeyid); + CTxDestination taddr = tkeyid; + + // No change address and no Sapling spends + { + auto builder = TransactionBuilder(consensusParams, 1, &keystore); + builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); + EXPECT_FALSE(static_cast(builder.Build())); + } + + // Change to the same address as the first Sapling spend + { + auto builder = TransactionBuilder(consensusParams, 1, &keystore); + builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); + ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + EXPECT_EQ(tx.vin.size(), 1); + EXPECT_EQ(tx.vout.size(), 0); + EXPECT_EQ(tx.vjoinsplit.size(), 0); + EXPECT_EQ(tx.vShieldedSpend.size(), 1); + EXPECT_EQ(tx.vShieldedOutput.size(), 1); + EXPECT_EQ(tx.valueBalance, -15000); + } + + // Change to a Sapling address + { + auto builder = TransactionBuilder(consensusParams, 1, &keystore); + builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); + builder.SendChangeTo(zChangeAddr, fvkOut); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + 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(), 1); + EXPECT_EQ(tx.valueBalance, -15000); + } + + // Change to a transparent address + { + auto builder = TransactionBuilder(consensusParams, 1, &keystore); + builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); + ASSERT_TRUE(builder.SendChangeTo(taddr)); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + EXPECT_EQ(tx.vin.size(), 1); + EXPECT_EQ(tx.vout.size(), 1); + EXPECT_EQ(tx.vjoinsplit.size(), 0); + EXPECT_EQ(tx.vShieldedSpend.size(), 0); + EXPECT_EQ(tx.vShieldedOutput.size(), 0); + EXPECT_EQ(tx.valueBalance, 0); + EXPECT_EQ(tx.vout[0].nValue, 15000); + } + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 25e1e2b17..d1f9a0f0b 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -79,6 +79,22 @@ bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value) return true; } +void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut) +{ + zChangeAddr = std::make_pair(fvkOut, changeAddr); + tChangeAddr = boost::none; +} + +bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr) +{ + if (!IsValidDestination(changeAddr)) { + return false; + } + + tChangeAddr = changeAddr; + zChangeAddr = boost::none; +} + boost::optional TransactionBuilder::Build() { // Fixed fee @@ -100,7 +116,27 @@ boost::optional TransactionBuilder::Build() return boost::none; } - // TODO: Create change output (currently, the change is added to the fee) + // + // Change output + // + + if (change > 0) { + // Send change to the specified change address. If no change address + // was set, send change to the first Sapling address given as input. + if (zChangeAddr) { + AddSaplingOutput(zChangeAddr->first, zChangeAddr->second, change, {}); + } else if (tChangeAddr) { + // tChangeAddr has already been validated. + assert(AddTransparentOutput(tChangeAddr.value(), change)); + } else if (!spends.empty()) { + auto fvk = spends[0].xsk.full_viewing_key(); + auto note = spends[0].note; + libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d); + AddSaplingOutput(fvk, changeAddr, change, {}); + } else { + return boost::none; + } + } // // Sapling spends and outputs diff --git a/src/transaction_builder.h b/src/transaction_builder.h index 42ce3147d..3b5b68368 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -64,6 +64,9 @@ private: std::vector outputs; std::vector tIns; + boost::optional> zChangeAddr; + boost::optional tChangeAddr; + public: TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr); @@ -86,6 +89,10 @@ public: bool AddTransparentOutput(CTxDestination& to, CAmount value); + void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut); + + bool SendChangeTo(CTxDestination& changeAddr); + boost::optional Build(); }; From a8dd4b0cf1641adcd969df174f037374673ac633 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 30 Jul 2018 12:52:48 +0100 Subject: [PATCH 09/13] TransactionBuilder: Make fee configurable --- src/gtest/test_transaction_builder.cpp | 61 ++++++++++++++++++++++++++ src/transaction_builder.cpp | 8 ++-- src/transaction_builder.h | 3 ++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index f7ab416a3..399562190 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -272,3 +272,64 @@ TEST(TransactionBuilder, ChangeOutput) UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } + +TEST(TransactionBuilder, SetFee) +{ + SelectParams(CBaseChainParams::REGTEST); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); + auto consensusParams = Params().GetConsensus(); + + // Generate dummy Sapling address + auto sk = libzcash::SaplingSpendingKey::random(); + auto xsk = sk.expanded_spending_key(); + auto fvk = sk.full_viewing_key(); + auto pk = sk.default_address(); + + // Generate dummy Sapling note + libzcash::SaplingNote note(pk, 50000); + auto cm = note.cm().value(); + ZCSaplingIncrementalMerkleTree tree; + tree.append(cm); + auto anchor = tree.root(); + auto witness = tree.witness(); + + // Default fee + { + auto builder = TransactionBuilder(consensusParams, 1); + ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + builder.AddSaplingOutput(fvk, pk, 25000, {}); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + EXPECT_EQ(tx.vin.size(), 0); + EXPECT_EQ(tx.vout.size(), 0); + EXPECT_EQ(tx.vjoinsplit.size(), 0); + EXPECT_EQ(tx.vShieldedSpend.size(), 1); + EXPECT_EQ(tx.vShieldedOutput.size(), 2); + EXPECT_EQ(tx.valueBalance, 10000); + } + + // Configured fee + { + auto builder = TransactionBuilder(consensusParams, 1); + ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + builder.AddSaplingOutput(fvk, pk, 25000, {}); + builder.SetFee(20000); + auto maybe_tx = builder.Build(); + ASSERT_EQ(static_cast(maybe_tx), true); + auto tx = maybe_tx.get(); + + EXPECT_EQ(tx.vin.size(), 0); + EXPECT_EQ(tx.vout.size(), 0); + EXPECT_EQ(tx.vjoinsplit.size(), 0); + EXPECT_EQ(tx.vShieldedSpend.size(), 1); + EXPECT_EQ(tx.vShieldedOutput.size(), 2); + EXPECT_EQ(tx.valueBalance, 20000); + } + + // Revert to default + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index d1f9a0f0b..e7fac9a35 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -79,6 +79,11 @@ bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value) return true; } +void TransactionBuilder::SetFee(CAmount fee) +{ + this->fee = fee; +} + void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut) { zChangeAddr = std::make_pair(fvkOut, changeAddr); @@ -97,9 +102,6 @@ bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr) boost::optional TransactionBuilder::Build() { - // Fixed fee - const CAmount fee = 10000; - // // Consistency checks // diff --git a/src/transaction_builder.h b/src/transaction_builder.h index 3b5b68368..d01260497 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -59,6 +59,7 @@ private: int nHeight; const CKeyStore* keystore; CMutableTransaction mtx; + CAmount fee = 10000; std::vector spends; std::vector outputs; @@ -70,6 +71,8 @@ private: public: TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr); + void SetFee(CAmount fee); + // Returns false if the anchor does not match the anchor used by // previously-added Sapling spends. bool AddSaplingSpend( From 54a868cf0a3b4573e3fc6d3e96a2fd51bd3affd7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 30 Jul 2018 14:26:29 +0100 Subject: [PATCH 10/13] Rename xsk to expsk xsk will be used for ZIP 32 extended spending keys, so renaming here to reduce confusion. --- src/gtest/test_transaction_builder.cpp | 20 ++++++++++---------- src/transaction_builder.cpp | 18 +++++++++--------- src/transaction_builder.h | 6 +++--- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 399562190..335cf26d5 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -28,7 +28,7 @@ TEST(TransactionBuilder, Invoke) auto fvk_from = sk_from.full_viewing_key(); auto sk = libzcash::SaplingSpendingKey::random(); - auto xsk = sk.expanded_spending_key(); + auto expsk = 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}; @@ -69,9 +69,9 @@ TEST(TransactionBuilder, Invoke) // Create a Sapling-only transaction // 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change auto builder2 = TransactionBuilder(consensusParams, 2); - ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness)); + ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note, anchor, witness)); // Check that trying to add a different anchor fails - ASSERT_FALSE(builder2.AddSaplingSpend(xsk, note, uint256(), witness)); + ASSERT_FALSE(builder2.AddSaplingSpend(expsk, note, uint256(), witness)); builder2.AddSaplingOutput(fvk, pk, 25000, {}); auto maybe_tx2 = builder2.Build(); @@ -130,7 +130,7 @@ TEST(TransactionBuilder, FailsWithNegativeChange) // Generate dummy Sapling address auto sk = libzcash::SaplingSpendingKey::random(); - auto xsk = sk.expanded_spending_key(); + auto expsk = sk.expanded_spending_key(); auto fvk = sk.full_viewing_key(); auto pk = sk.default_address(); @@ -164,7 +164,7 @@ TEST(TransactionBuilder, FailsWithNegativeChange) // Fails if there is insufficient input // 0.0005 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in - EXPECT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + EXPECT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); EXPECT_FALSE(static_cast(builder.Build())); // Succeeds if there is sufficient input @@ -185,7 +185,7 @@ TEST(TransactionBuilder, ChangeOutput) // Generate dummy Sapling address auto sk = libzcash::SaplingSpendingKey::random(); - auto xsk = sk.expanded_spending_key(); + auto expsk = sk.expanded_spending_key(); auto pk = sk.default_address(); // Generate dummy Sapling note @@ -220,7 +220,7 @@ TEST(TransactionBuilder, ChangeOutput) { auto builder = TransactionBuilder(consensusParams, 1, &keystore); builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000); - ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); auto maybe_tx = builder.Build(); ASSERT_EQ(static_cast(maybe_tx), true); auto tx = maybe_tx.get(); @@ -282,7 +282,7 @@ TEST(TransactionBuilder, SetFee) // Generate dummy Sapling address auto sk = libzcash::SaplingSpendingKey::random(); - auto xsk = sk.expanded_spending_key(); + auto expsk = sk.expanded_spending_key(); auto fvk = sk.full_viewing_key(); auto pk = sk.default_address(); @@ -297,7 +297,7 @@ TEST(TransactionBuilder, SetFee) // Default fee { auto builder = TransactionBuilder(consensusParams, 1); - ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); builder.AddSaplingOutput(fvk, pk, 25000, {}); auto maybe_tx = builder.Build(); ASSERT_EQ(static_cast(maybe_tx), true); @@ -314,7 +314,7 @@ TEST(TransactionBuilder, SetFee) // Configured fee { auto builder = TransactionBuilder(consensusParams, 1); - ASSERT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness)); + ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness)); builder.AddSaplingOutput(fvk, pk, 25000, {}); builder.SetFee(20000); auto maybe_tx = builder.Build(); diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index e7fac9a35..47eca6153 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -12,10 +12,10 @@ #include SpendDescriptionInfo::SpendDescriptionInfo( - libzcash::SaplingExpandedSpendingKey xsk, + libzcash::SaplingExpandedSpendingKey expsk, libzcash::SaplingNote note, uint256 anchor, - ZCSaplingIncrementalWitness witness) : xsk(xsk), note(note), anchor(anchor), witness(witness) + ZCSaplingIncrementalWitness witness) : expsk(expsk), note(note), anchor(anchor), witness(witness) { librustzcash_sapling_generate_r(alpha.begin()); } @@ -29,7 +29,7 @@ TransactionBuilder::TransactionBuilder( } bool TransactionBuilder::AddSaplingSpend( - libzcash::SaplingExpandedSpendingKey xsk, + libzcash::SaplingExpandedSpendingKey expsk, libzcash::SaplingNote note, uint256 anchor, ZCSaplingIncrementalWitness witness) @@ -41,7 +41,7 @@ bool TransactionBuilder::AddSaplingSpend( } } - spends.emplace_back(xsk, note, anchor, witness); + spends.emplace_back(expsk, note, anchor, witness); mtx.valueBalance += note.value(); return true; } @@ -131,7 +131,7 @@ boost::optional TransactionBuilder::Build() // tChangeAddr has already been validated. assert(AddTransparentOutput(tChangeAddr.value(), change)); } else if (!spends.empty()) { - auto fvk = spends[0].xsk.full_viewing_key(); + auto fvk = spends[0].expsk.full_viewing_key(); auto note = spends[0].note; libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d); AddSaplingOutput(fvk, changeAddr, change, {}); @@ -150,7 +150,7 @@ boost::optional TransactionBuilder::Build() for (auto spend : spends) { auto cm = spend.note.cm(); auto nf = spend.note.nullifier( - spend.xsk.full_viewing_key(), spend.witness.position()); + spend.expsk.full_viewing_key(), spend.witness.position()); if (!(cm && nf)) { librustzcash_sapling_proving_ctx_free(ctx); return boost::none; @@ -163,8 +163,8 @@ boost::optional TransactionBuilder::Build() SpendDescription sdesc; if (!librustzcash_sapling_spend_proof( ctx, - spend.xsk.full_viewing_key().ak.begin(), - spend.xsk.nsk.begin(), + spend.expsk.full_viewing_key().ak.begin(), + spend.expsk.nsk.begin(), spend.note.d.data(), spend.note.r.begin(), spend.alpha.begin(), @@ -247,7 +247,7 @@ boost::optional TransactionBuilder::Build() // 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].expsk.ask.begin(), spends[i].alpha.begin(), dataToBeSigned.begin(), mtx.vShieldedSpend[i].spendAuthSig.data()); diff --git a/src/transaction_builder.h b/src/transaction_builder.h index d01260497..6c159e7aa 100644 --- a/src/transaction_builder.h +++ b/src/transaction_builder.h @@ -19,14 +19,14 @@ #include struct SpendDescriptionInfo { - libzcash::SaplingExpandedSpendingKey xsk; + libzcash::SaplingExpandedSpendingKey expsk; libzcash::SaplingNote note; uint256 alpha; uint256 anchor; ZCSaplingIncrementalWitness witness; SpendDescriptionInfo( - libzcash::SaplingExpandedSpendingKey xsk, + libzcash::SaplingExpandedSpendingKey expsk, libzcash::SaplingNote note, uint256 anchor, ZCSaplingIncrementalWitness witness); @@ -76,7 +76,7 @@ public: // Returns false if the anchor does not match the anchor used by // previously-added Sapling spends. bool AddSaplingSpend( - libzcash::SaplingExpandedSpendingKey xsk, + libzcash::SaplingExpandedSpendingKey expsk, libzcash::SaplingNote note, uint256 anchor, ZCSaplingIncrementalWitness witness); From b7b088c46b5f4e622280f402d64595f8292b124a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 30 Jul 2018 21:59:12 -0600 Subject: [PATCH 11/13] Update librustzcash and sapling-crypto. --- depends/packages/crate_sapling_crypto.mk | 4 ++-- depends/packages/librustzcash.mk | 4 ++-- depends/patches/librustzcash/cargo.config | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/depends/packages/crate_sapling_crypto.mk b/depends/packages/crate_sapling_crypto.mk index e1ef857c9..2da5e2406 100644 --- a/depends/packages/crate_sapling_crypto.mk +++ b/depends/packages/crate_sapling_crypto.mk @@ -3,8 +3,8 @@ $(package)_crate_name=sapling-crypto $(package)_download_path=https://github.com/zcash-hackworks/$($(package)_crate_name)/archive/ $(package)_file_name=$(package)-$($(package)_git_commit).tar.gz $(package)_download_file=$($(package)_git_commit).tar.gz -$(package)_sha256_hash=480ffc31b399a9517178289799f9db01152bedbc534a6a2d2dbac245421fb6fe -$(package)_git_commit=6abfcca25ae233922ecc18a4d2d0b5cb7aab7c8c +$(package)_sha256_hash=ae3a122b1f1ce97b4e80e0e8542e19aa1516e99e6c72875688c886af1a881558 +$(package)_git_commit=21084bde2019c04bd34208e63c3560fe2c02fb0e $(package)_crate_versioned_name=$($(package)_crate_name) define $(package)_preprocess_cmds diff --git a/depends/packages/librustzcash.mk b/depends/packages/librustzcash.mk index 03c172dc9..aeff4d890 100644 --- a/depends/packages/librustzcash.mk +++ b/depends/packages/librustzcash.mk @@ -3,8 +3,8 @@ $(package)_version=0.1 $(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=86139e8a6cc76ae1a04ed8229beef760de1beb2a72fbe15450b44c3649d48a5d -$(package)_git_commit=32026ea0a13337548f1b6e57d99f0b7b6b9d0d81 +$(package)_sha256_hash=1700c1699552f3aa4db8de265399bc6606ae976b6741f7bbeb0b89f24abf9f7f +$(package)_git_commit=f55a654901dc6278dd5738f06f2e70263fccd96b $(package)_dependencies=rust $(rust_crates) $(package)_patches=cargo.config diff --git a/depends/patches/librustzcash/cargo.config b/depends/patches/librustzcash/cargo.config index 234a9c97b..7696632a1 100644 --- a/depends/patches/librustzcash/cargo.config +++ b/depends/patches/librustzcash/cargo.config @@ -8,7 +8,7 @@ replace-with = "vendored-sources" [source."https://github.com/zcash-hackworks/sapling-crypto"] git = "https://github.com/zcash-hackworks/sapling-crypto" -rev = "6abfcca25ae233922ecc18a4d2d0b5cb7aab7c8c" +rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e" replace-with = "vendored-sources" [source.vendored-sources] From 04ed758e37507e504d00f5319765679fe2fc998b Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 30 Jul 2018 22:03:40 -0600 Subject: [PATCH 12/13] Fix bug in return value. --- src/transaction_builder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/transaction_builder.cpp b/src/transaction_builder.cpp index 47eca6153..f2b915c5a 100644 --- a/src/transaction_builder.cpp +++ b/src/transaction_builder.cpp @@ -98,6 +98,8 @@ bool TransactionBuilder::SendChangeTo(CTxDestination& changeAddr) tChangeAddr = changeAddr; zChangeAddr = boost::none; + + return true; } boost::optional TransactionBuilder::Build() From a310f6c5fa1c1b12043e6ed3e49962d555cd2e31 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Wed, 1 Aug 2018 00:42:02 -0600 Subject: [PATCH 13/13] Relocate ECC_Start() to avoid test failures. --- src/gtest/test_paymentdisclosure.cpp | 1 - src/gtest/test_transaction_builder.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gtest/test_paymentdisclosure.cpp b/src/gtest/test_paymentdisclosure.cpp index d4cefa229..89be17dae 100644 --- a/src/gtest/test_paymentdisclosure.cpp +++ b/src/gtest/test_paymentdisclosure.cpp @@ -93,7 +93,6 @@ public: // Note that the zpd: prefix is not part of the payment disclosure blob itself. It is only // used as convention to improve the user experience when sharing payment disclosure blobs. TEST(paymentdisclosure, mainnet) { - ECC_Start(); SelectParams(CBaseChainParams::MAIN); boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); diff --git a/src/gtest/test_transaction_builder.cpp b/src/gtest/test_transaction_builder.cpp index 335cf26d5..2ad90942b 100644 --- a/src/gtest/test_transaction_builder.cpp +++ b/src/gtest/test_transaction_builder.cpp @@ -14,6 +14,8 @@ static const std::string tSecretRegtest = "cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotP TEST(TransactionBuilder, Invoke) { + ECC_Start(); + SelectParams(CBaseChainParams::REGTEST); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);