2018-07-17 09:36:38 -07:00
|
|
|
// 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"
|
2018-07-30 03:03:29 -07:00
|
|
|
#include "pubkey.h"
|
|
|
|
#include "script/sign.h"
|
2018-07-17 09:36:38 -07:00
|
|
|
|
|
|
|
#include <boost/variant.hpp>
|
|
|
|
#include <librustzcash.h>
|
|
|
|
|
|
|
|
SpendDescriptionInfo::SpendDescriptionInfo(
|
|
|
|
libzcash::SaplingExpandedSpendingKey xsk,
|
|
|
|
libzcash::SaplingNote note,
|
|
|
|
uint256 anchor,
|
2018-07-27 00:49:43 -07:00
|
|
|
ZCSaplingIncrementalWitness witness) : xsk(xsk), note(note), anchor(anchor), witness(witness)
|
2018-07-17 09:36:38 -07:00
|
|
|
{
|
|
|
|
librustzcash_sapling_generate_r(alpha.begin());
|
|
|
|
}
|
|
|
|
|
|
|
|
TransactionBuilder::TransactionBuilder(
|
2018-07-27 00:49:43 -07:00
|
|
|
const Consensus::Params& consensusParams,
|
2018-07-30 03:03:29 -07:00
|
|
|
int nHeight,
|
|
|
|
CKeyStore* keystore) : consensusParams(consensusParams), nHeight(nHeight), keystore(keystore)
|
2018-07-17 09:36:38 -07:00
|
|
|
{
|
|
|
|
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
|
|
|
}
|
|
|
|
|
2018-07-27 00:46:38 -07:00
|
|
|
bool TransactionBuilder::AddSaplingSpend(
|
2018-07-17 09:36:38 -07:00
|
|
|
libzcash::SaplingExpandedSpendingKey xsk,
|
|
|
|
libzcash::SaplingNote note,
|
|
|
|
uint256 anchor,
|
2018-07-27 00:46:38 -07:00
|
|
|
ZCSaplingIncrementalWitness witness)
|
|
|
|
{
|
|
|
|
// Consistency check: all anchors must equal the first one
|
|
|
|
if (!spends.empty()) {
|
|
|
|
if (spends[0].anchor != anchor) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
spends.emplace_back(xsk, note, anchor, witness);
|
|
|
|
mtx.valueBalance += note.value();
|
2018-07-27 00:46:38 -07:00
|
|
|
return true;
|
2018-07-17 09:36:38 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void TransactionBuilder::AddSaplingOutput(
|
|
|
|
libzcash::SaplingFullViewingKey from,
|
|
|
|
libzcash::SaplingPaymentAddress to,
|
|
|
|
CAmount value,
|
2018-07-27 00:49:43 -07:00
|
|
|
std::array<unsigned char, ZC_MEMO_SIZE> memo)
|
|
|
|
{
|
2018-07-17 09:36:38 -07:00
|
|
|
auto note = libzcash::SaplingNote(to, value);
|
|
|
|
outputs.emplace_back(from.ovk, note, memo);
|
|
|
|
mtx.valueBalance -= value;
|
|
|
|
}
|
|
|
|
|
2018-07-30 03:03:29 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
boost::optional<CTransaction> TransactionBuilder::Build()
|
|
|
|
{
|
2018-07-30 03:03:29 -07:00
|
|
|
// 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
|
|
|
|
//
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
auto ctx = librustzcash_sapling_proving_ctx_init();
|
|
|
|
|
|
|
|
// Create Sapling SpendDescriptions
|
|
|
|
for (auto spend : spends) {
|
|
|
|
auto cm = spend.note.cm();
|
|
|
|
auto nf = spend.note.nullifier(
|
|
|
|
spend.xsk.full_viewing_key(), spend.witness.position());
|
|
|
|
if (!(cm && nf)) {
|
|
|
|
librustzcash_sapling_proving_ctx_free(ctx);
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
|
|
ss << spend.witness.path();
|
|
|
|
std::vector<unsigned char> witness(ss.begin(), ss.end());
|
|
|
|
|
|
|
|
SpendDescription sdesc;
|
|
|
|
if (!librustzcash_sapling_spend_proof(
|
2018-07-27 00:49:43 -07:00
|
|
|
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())) {
|
2018-07-17 09:36:38 -07:00
|
|
|
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(
|
2018-07-27 00:49:43 -07:00
|
|
|
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())) {
|
2018-07-17 09:36:38 -07:00
|
|
|
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,
|
2018-07-27 00:49:43 -07:00
|
|
|
encryptor);
|
2018-07-17 09:36:38 -07:00
|
|
|
mtx.vShieldedOutput.push_back(odesc);
|
|
|
|
}
|
|
|
|
|
2018-07-30 03:03:29 -07:00
|
|
|
//
|
|
|
|
// Signatures
|
|
|
|
//
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
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);
|
2018-07-30 03:03:29 -07:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-17 09:36:38 -07:00
|
|
|
return CTransaction(mtx);
|
|
|
|
}
|