Auto merge of #3417 - str4d:sapling-tx-builder, r=ebfull

TransactionBuilder for Sapling and transparent transactions

This PR includes zcash/librustzcash#24, and will create a testnet chain split once merged into master (and once a Sapling transaction is mined).
This commit is contained in:
Homu 2018-07-31 23:44:29 -07:00
commit d84f14ec4a
15 changed files with 740 additions and 12 deletions

View File

@ -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

View File

@ -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=1700c1699552f3aa4db8de265399bc6606ae976b6741f7bbeb0b89f24abf9f7f
$(package)_git_commit=f55a654901dc6278dd5738f06f2e70263fccd96b
$(package)_dependencies=rust $(rust_crates)
$(package)_patches=cargo.config

View File

@ -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]

View File

@ -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)

View File

@ -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 \

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

View File

@ -0,0 +1,337 @@
#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 <gmock/gmock.h>
#include <gtest/gtest.h>
static const std::string tSecretRegtest = "cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN";
TEST(TransactionBuilder, Invoke)
{
ECC_Start();
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
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();
auto sk = libzcash::SaplingSpendingKey::random();
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};
auto pk = *ivk.address(d);
// Create a shielding transaction from transparent to Sapling
// 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<bool>(maybe_tx1), true);
auto tx1 = maybe_tx1.get();
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, -40000);
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
// 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(expsk, note, anchor, witness));
// Check that trying to add a different anchor fails
ASSERT_FALSE(builder2.AddSaplingSpend(expsk, note, uint256(), witness));
builder2.AddSaplingOutput(fvk, pk, 25000, {});
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(), 2);
EXPECT_EQ(tx2.valueBalance, 10000);
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);
}
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, 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);
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 expsk = 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<bool>(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<bool>(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(expsk, note, anchor, witness));
EXPECT_FALSE(static_cast<bool>(builder.Build()));
// Succeeds if there is sufficient input
builder.AddTransparentInput(COutPoint(), scriptPubKey, 1);
EXPECT_TRUE(static_cast<bool>(builder.Build()));
// Revert to default
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 expsk = 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<bool>(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(expsk, note, anchor, witness));
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(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<bool>(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<bool>(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);
}
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 expsk = 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(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(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(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.SetFee(20000);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(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);
}

283
src/transaction_builder.cpp Normal file
View File

@ -0,0 +1,283 @@
// 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 "pubkey.h"
#include "script/sign.h"
#include <boost/variant.hpp>
#include <librustzcash.h>
SpendDescriptionInfo::SpendDescriptionInfo(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
ZCSaplingIncrementalWitness witness) : expsk(expsk), note(note), anchor(anchor), witness(witness)
{
librustzcash_sapling_generate_r(alpha.begin());
}
TransactionBuilder::TransactionBuilder(
const Consensus::Params& consensusParams,
int nHeight,
CKeyStore* keystore) : consensusParams(consensusParams), nHeight(nHeight), keystore(keystore)
{
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
}
bool TransactionBuilder::AddSaplingSpend(
libzcash::SaplingExpandedSpendingKey expsk,
libzcash::SaplingNote note,
uint256 anchor,
ZCSaplingIncrementalWitness witness)
{
// Consistency check: all anchors must equal the first one
if (!spends.empty()) {
if (spends[0].anchor != anchor) {
return false;
}
}
spends.emplace_back(expsk, note, anchor, witness);
mtx.valueBalance += note.value();
return true;
}
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;
}
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;
}
void TransactionBuilder::SetFee(CAmount fee)
{
this->fee = fee;
}
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;
return true;
}
boost::optional<CTransaction> TransactionBuilder::Build()
{
//
// 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;
}
//
// 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].expsk.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
//
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.expsk.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.expsk.full_viewing_key().ak.begin(),
spend.expsk.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);
}
//
// Signatures
//
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].expsk.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);
// 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);
}

102
src/transaction_builder.h Normal file
View File

@ -0,0 +1,102 @@
// 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 "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"
#include "zcash/Note.hpp"
#include "zcash/NoteEncryption.hpp"
#include <boost/optional.hpp>
struct SpendDescriptionInfo {
libzcash::SaplingExpandedSpendingKey expsk;
libzcash::SaplingNote note;
uint256 alpha;
uint256 anchor;
ZCSaplingIncrementalWitness witness;
SpendDescriptionInfo(
libzcash::SaplingExpandedSpendingKey expsk,
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) {}
};
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;
CAmount fee = 10000;
std::vector<SpendDescriptionInfo> spends;
std::vector<OutputDescriptionInfo> outputs;
std::vector<TransparentInputInfo> tIns;
boost::optional<std::pair<libzcash::SaplingFullViewingKey, libzcash::SaplingPaymentAddress>> zChangeAddr;
boost::optional<CTxDestination> tChangeAddr;
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(
libzcash::SaplingExpandedSpendingKey expsk,
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);
// Assumes that the value correctly corresponds to the provided UTXO.
void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value);
bool AddTransparentOutput(CTxDestination& to, CAmount value);
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut);
bool SendChangeTo(CTxDestination& changeAddr);
boost::optional<CTransaction> Build();
};
#endif /* TRANSACTION_BUILDER_H */

View File

@ -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.

View File

@ -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());
}

View File

@ -65,9 +65,8 @@ boost::optional<uint256> SaplingNote::cm() const {
}
// Call librustzcash to compute the nullifier
boost::optional<uint256> SaplingNote::nullifier(const SaplingSpendingKey& sk, const uint64_t position) const
boost::optional<uint256> SaplingNote::nullifier(const SaplingFullViewingKey& vk, const uint64_t position) const
{
auto vk = sk.full_viewing_key();
auto ak = vk.ak;
auto nk = vk.nk;

View File

@ -57,7 +57,7 @@ public:
virtual ~SaplingNote() {};
boost::optional<uint256> cm() const;
boost::optional<uint256> nullifier(const SaplingSpendingKey &sk, const uint64_t position) const;
boost::optional<uint256> nullifier(const SaplingFullViewingKey &vk, const uint64_t position) const;
};
class BaseNotePlaintext {