// 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 #include SpendDescriptionInfo::SpendDescriptionInfo( libzcash::SaplingExpandedSpendingKey expsk, libzcash::SaplingNote note, uint256 anchor, SaplingWitness 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, SaplingWitness 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 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 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 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); }