From e691e21f4021e8d3cc8b45deec92a072b3015698 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 17 Jul 2018 10:36:38 -0600 Subject: [PATCH] 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 */