LCOV - code coverage report
Current view: top level - src/sapling - transaction_builder.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 214 236 90.7 %
Date: 2026-08-09 10:51:41 Functions: 24 25 96.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-2020 The Zcash developers
       2             : // Copyright (c) 2020-2021 The PIVX Core developers
       3             : // Distributed under the MIT software license, see the accompanying
       4             : // file COPYING or https://www.opensource.org/licenses/mit-license.php .
       5             : 
       6             : #include "sapling/transaction_builder.h"
       7             : 
       8             : #include "script/sign.h"
       9             : #include "utilmoneystr.h"
      10             : #include "consensus/upgrades.h"
      11             : #include "policy/policy.h"
      12             : #include "validation.h"
      13             : 
      14             : #include <librustzcash.h>
      15             : 
      16          98 : SpendDescriptionInfo::SpendDescriptionInfo(const libzcash::SaplingExpandedSpendingKey& _expsk,
      17             :                                            const libzcash::SaplingNote& _note,
      18             :                                            const uint256& _anchor,
      19          98 :                                            const SaplingWitness& _witness):
      20             :    expsk(_expsk),
      21             :    note(_note),
      22             :    anchor(_anchor),
      23         196 :    witness(_witness)
      24             : {
      25          98 :     librustzcash_sapling_generate_r(alpha.begin());
      26          98 : }
      27             : 
      28         327 : Optional<OutputDescription> OutputDescriptionInfo::Build(void* ctx) {
      29         654 :     auto cmu = this->note.cmu();
      30         327 :     if (!cmu) {
      31           0 :         return nullopt;
      32             :     }
      33             : 
      34         654 :     libzcash::SaplingNotePlaintext notePlaintext(this->note, this->memo);
      35             : 
      36         654 :     auto res = notePlaintext.encrypt(this->note.pk_d);
      37         327 :     if (!res) {
      38           0 :         return nullopt;
      39             :     }
      40         327 :     auto enc = res.get();
      41         327 :     auto encryptor = enc.second;
      42             : 
      43         327 :     libzcash::SaplingPaymentAddress address(this->note.d, this->note.pk_d);
      44         654 :     CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
      45         327 :     ss << address;
      46         654 :     std::vector<unsigned char> addressBytes(ss.begin(), ss.end());
      47             : 
      48         327 :     OutputDescription odesc;
      49         327 :     if (!librustzcash_sapling_output_proof(
      50             :             ctx,
      51         327 :             encryptor.get_esk().begin(),
      52         327 :             addressBytes.data(),
      53         327 :             this->note.r.begin(),
      54             :             this->note.value(),
      55             :             odesc.cv.begin(),
      56             :             odesc.zkproof.begin())) {
      57           0 :         return nullopt;
      58             :     }
      59             : 
      60         327 :     odesc.cmu = *cmu;
      61         327 :     odesc.ephemeralKey = encryptor.get_epk();
      62         327 :     odesc.encCiphertext = enc.first;
      63             : 
      64         327 :     libzcash::SaplingOutgoingPlaintext outPlaintext(this->note.pk_d, encryptor.get_esk());
      65         327 :     odesc.outCiphertext = outPlaintext.encrypt(
      66         327 :         this->ovk,
      67             :         odesc.cv,
      68             :         odesc.cmu,
      69         327 :         encryptor);
      70             : 
      71         327 :     return odesc;
      72             : }
      73             : 
      74             : // Dummy constants used during fee-calculation loop
      75         416 : static OutputDescription CreateDummyOD()
      76             : {
      77         416 :     OutputDescription dummyOD;
      78         416 :     dummyOD.cv = UINT256_MAX;
      79         416 :     dummyOD.cmu = UINT256_MAX;
      80         416 :     dummyOD.ephemeralKey = UINT256_MAX;
      81         416 :     dummyOD.encCiphertext = {{0xff}};
      82         416 :     dummyOD.outCiphertext = {{0xff}};
      83         416 :     dummyOD.zkproof = {{0xff}};
      84         416 :     return dummyOD;
      85             : }
      86         416 : static SpendDescription CreateDummySD()
      87             : {
      88         416 :     SpendDescription dummySD;
      89         416 :     dummySD.cv = UINT256_MAX;
      90         416 :     dummySD.anchor = UINT256_MAX;
      91         416 :     dummySD.nullifier = UINT256_MAX;
      92         416 :     dummySD.rk = UINT256_MAX;
      93         416 :     dummySD.zkproof = {{0xff}};
      94         416 :     dummySD.spendAuthSig = {{0xff}};
      95         416 :     return dummySD;
      96             : }
      97             : 
      98             : const OutputDescription DUMMY_SHIELD_OUT = CreateDummyOD();
      99             : const SpendDescription DUMMY_SHIELD_SPEND = CreateDummySD();
     100             : const SaplingTxData::binding_sig_t DUMMY_SHIELD_BINDSIG = {{0xff}};
     101             : 
     102             : 
     103         379 : TransactionBuilderResult::TransactionBuilderResult(const CTransaction& tx) : maybeTx(tx) {}
     104             : 
     105           4 : TransactionBuilderResult::TransactionBuilderResult(const std::string& error) : maybeError(error) {}
     106             : 
     107           1 : bool TransactionBuilderResult::IsTx() { return maybeTx != nullopt; }
     108             : 
     109           0 : bool TransactionBuilderResult::IsError() { return maybeError != nullopt; }
     110             : 
     111         144 : CTransaction TransactionBuilderResult::GetTxOrThrow() {
     112         144 :     if (maybeTx) {
     113         144 :         return maybeTx.get();
     114             :     } else {
     115           0 :         throw std::runtime_error("Failed to build transaction: " + GetError());
     116             :     }
     117             : }
     118             : 
     119         234 : Optional<CTransaction> TransactionBuilderResult::GetTx() {
     120         234 :     return maybeTx;
     121             : }
     122             : 
     123           4 : std::string TransactionBuilderResult::GetError() {
     124           4 :     if (maybeError) {
     125           4 :         return maybeError.get();
     126             :     } else {
     127             :         // This can only happen if isTx() is true in which case we should not call getError()
     128           0 :         throw std::runtime_error("getError() was called in TransactionBuilderResult, but the result was not initialized as an error.");
     129             :     }
     130             : }
     131             : 
     132         251 : TransactionBuilder::TransactionBuilder(
     133             :     const Consensus::Params& _consensusParams,
     134         251 :     CKeyStore* _keystore) :
     135             :     consensusParams(_consensusParams),
     136         251 :     keystore(_keystore)
     137             : {
     138         251 :     Clear();
     139         251 : }
     140             : 
     141         305 : void TransactionBuilder::Clear()
     142             : {
     143         305 :     mtx = CMutableTransaction();
     144         305 :     mtx.nVersion = CTransaction::TxVersion::SAPLING;
     145         305 :     spends.clear();
     146         305 :     outputs.clear();
     147         305 :     tIns.clear();
     148         305 :     saplingChangeAddr = nullopt;
     149         305 :     tChangeAddr = nullopt;
     150         305 :     fee = -1;   // Verified in Build(). Must be set before.
     151         305 : }
     152             : 
     153          99 : void TransactionBuilder::AddSaplingSpend(
     154             :     const libzcash::SaplingExpandedSpendingKey& expsk,
     155             :     const libzcash::SaplingNote& note,
     156             :     const uint256& anchor,
     157             :     const SaplingWitness& witness)
     158             : {
     159             :     // Sanity check: cannot add Sapling spend to pre-Sapling transaction
     160          99 :     if (mtx.nVersion < CTransaction::TxVersion::SAPLING) {
     161           0 :         throw std::runtime_error("TransactionBuilder cannot add Sapling spend to pre-Sapling transaction");
     162             :     }
     163             : 
     164             :     // Consistency check: all anchors must equal the first one
     165          99 :     if (spends.size() > 0 && spends[0].anchor != anchor) {
     166           1 :         throw std::runtime_error("Anchor does not match previously-added Sapling spends.");
     167             :     }
     168             : 
     169          98 :     spends.emplace_back(expsk, note, anchor, witness);
     170          98 :     mtx.sapData->valueBalance += note.value();
     171          98 : }
     172             : 
     173         400 : void TransactionBuilder::AddSaplingOutput(
     174             :     const uint256& ovk,
     175             :     const libzcash::SaplingPaymentAddress& to,
     176             :     CAmount value,
     177             :     const std::array<unsigned char, ZC_MEMO_SIZE>& memo)
     178             : {
     179             :     // Sanity check: cannot add Sapling output to pre-Sapling transaction
     180         400 :     if (mtx.nVersion < CTransaction::TxVersion::SAPLING) {
     181           0 :         throw std::runtime_error("TransactionBuilder cannot add Sapling output to pre-Sapling transaction");
     182             :     }
     183             : 
     184         400 :     auto note = libzcash::SaplingNote(to, value);
     185         400 :     outputs.emplace_back(ovk, note, memo);
     186         400 :     mtx.sapData->valueBalance -= value;
     187         400 : }
     188             : 
     189         374 : void TransactionBuilder::AddTransparentInput(const COutPoint& utxo, const CScript& scriptPubKey, CAmount value)
     190             : {
     191         374 :     if (keystore == nullptr) {
     192           1 :         throw std::runtime_error("Cannot add transparent inputs to a TransactionBuilder without a keystore");
     193             :     }
     194             : 
     195         373 :     mtx.vin.emplace_back(utxo);
     196         373 :     tIns.emplace_back(scriptPubKey, value);
     197         373 : }
     198             : 
     199         118 : void TransactionBuilder::AddTransparentOutput(const CTxOut& out)
     200             : {
     201         236 :     std::vector<std::vector<unsigned char> > vSolutions;
     202         118 :     txnouttype whichType;
     203         118 :     if (!Solver(out.scriptPubKey, whichType, vSolutions))
     204           1 :         throw std::runtime_error("Transaction builder: invalid script for transparent output");
     205         117 :     mtx.vout.push_back(out);
     206         117 : }
     207             : 
     208          78 : void TransactionBuilder::AddTransparentOutput(const CTxDestination& dest, CAmount value)
     209             : {
     210         156 :     AddTransparentOutput(CTxOut(value, GetScriptForDestination(dest)));
     211          77 : }
     212             : 
     213         293 : void TransactionBuilder::SetFee(CAmount _fee)
     214             : {
     215         293 :     this->fee = _fee;
     216         293 : }
     217             : 
     218           1 : void TransactionBuilder::SendChangeTo(const libzcash::SaplingPaymentAddress& changeAddr, const uint256& ovk)
     219             : {
     220           1 :     saplingChangeAddr = std::make_pair(ovk, changeAddr);
     221           1 :     tChangeAddr = nullopt;
     222           1 : }
     223             : 
     224          82 : void TransactionBuilder::SendChangeTo(const CTxDestination& changeAddr)
     225             : {
     226          82 :     if (!IsValidDestination(changeAddr)) {
     227           1 :         throw std::runtime_error("Invalid change address, not a valid taddr.");
     228             :     }
     229             : 
     230          81 :     tChangeAddr = changeAddr;
     231          81 :     saplingChangeAddr = nullopt;
     232          81 : }
     233             : 
     234         234 : TransactionBuilderResult TransactionBuilder::ProveAndSign()
     235             : {
     236             :     //
     237             :     // Sapling spend descriptions
     238             :     //
     239         234 :     if (!spends.empty() || !outputs.empty()) {
     240             : 
     241         231 :         auto ctx = librustzcash_sapling_proving_ctx_init();
     242             : 
     243             :         // Create Sapling OutputDescriptions
     244         558 :         for (auto output : outputs) {
     245             :             // Check this out here as well to provide better logging.
     246         327 :             if (!output.note.cmu()) {
     247           0 :                 librustzcash_sapling_proving_ctx_free(ctx);
     248           0 :                 return TransactionBuilderResult("Output is invalid");
     249             :             }
     250             : 
     251         654 :             auto odesc = output.Build(ctx);
     252         327 :             if (!odesc) {
     253           0 :                 librustzcash_sapling_proving_ctx_free(ctx);
     254           0 :                 return TransactionBuilderResult("Failed to create output description");
     255             :             }
     256             : 
     257         327 :             mtx.sapData->vShieldedOutput.push_back(odesc.get());
     258             :         }
     259             : 
     260             :         // Create Sapling SpendDescriptions
     261         303 :         for (auto spend : spends) {
     262          72 :             auto cm = spend.note.cmu();
     263          72 :             auto nf = spend.note.nullifier(
     264         144 :                     spend.expsk.full_viewing_key(), spend.witness.position());
     265          72 :             if (!cm || !nf) {
     266           0 :                 librustzcash_sapling_proving_ctx_free(ctx);
     267           0 :                 return TransactionBuilderResult("Spend is invalid");
     268             :             }
     269             : 
     270         144 :             CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
     271         144 :             ss << spend.witness.path();
     272         144 :             std::vector<unsigned char> witness(ss.begin(), ss.end());
     273             : 
     274          72 :             SpendDescription sdesc;
     275          72 :             if (!librustzcash_sapling_spend_proof(
     276             :                     ctx,
     277          72 :                     spend.expsk.full_viewing_key().ak.begin(),
     278          72 :                     spend.expsk.nsk.begin(),
     279          72 :                     spend.note.d.data(),
     280          72 :                     spend.note.r.begin(),
     281          72 :                     spend.alpha.begin(),
     282             :                     spend.note.value(),
     283          72 :                     spend.anchor.begin(),
     284          72 :                     witness.data(),
     285             :                     sdesc.cv.begin(),
     286             :                     sdesc.rk.begin(),
     287             :                     sdesc.zkproof.data())) {
     288           0 :                 librustzcash_sapling_proving_ctx_free(ctx);
     289           0 :                 return TransactionBuilderResult("Spend proof failed");
     290             :             }
     291             : 
     292          72 :             sdesc.anchor = spend.anchor;
     293          72 :             sdesc.nullifier = *nf;
     294          72 :             mtx.sapData->vShieldedSpend.push_back(sdesc);
     295             :         }
     296             : 
     297             :         //
     298             :         // Signatures
     299             :         //
     300             : 
     301             :         // Empty output script.
     302         231 :         uint256 dataToBeSigned;
     303         462 :         CScript scriptCode;
     304         231 :         try {
     305         231 :             dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, SIGVERSION_SAPLING);
     306           0 :         } catch (const std::logic_error& ex) {
     307           0 :             librustzcash_sapling_proving_ctx_free(ctx);
     308           0 :             return TransactionBuilderResult("Could not construct signature hash: " + std::string(ex.what()));
     309             :         }
     310             : 
     311             :         // Create Sapling spendAuth and binding signatures
     312         303 :         for (size_t i = 0; i < spends.size(); i++) {
     313          72 :             librustzcash_sapling_spend_sig(
     314          72 :                     spends[i].expsk.ask.begin(),
     315          72 :                     spends[i].alpha.begin(),
     316          72 :                     dataToBeSigned.begin(),
     317          72 :                     mtx.sapData->vShieldedSpend[i].spendAuthSig.data());
     318             :         }
     319             : 
     320         462 :         librustzcash_sapling_binding_sig(
     321             :                 ctx,
     322         231 :                 mtx.sapData->valueBalance,
     323         231 :                 dataToBeSigned.begin(),
     324         231 :                 mtx.sapData->bindingSig.data());
     325             : 
     326         231 :         librustzcash_sapling_proving_ctx_free(ctx);
     327             :     }
     328             : 
     329             :     // Transparent signatures
     330         468 :     CTransaction txNewConst(mtx);
     331         554 :     for (int nIn = 0; nIn < (int) mtx.vin.size(); nIn++) {
     332         640 :         auto tIn = tIns[nIn];
     333         640 :         SignatureData sigdata;
     334         320 :         bool signSuccess = ProduceSignature(
     335         320 :             TransactionSignatureCreator(
     336             :                 keystore, &txNewConst, nIn, tIn.value, SIGHASH_ALL),
     337             :             tIn.scriptPubKey, sigdata, SIGVERSION_SAPLING, false);
     338             : 
     339         320 :         if (!signSuccess) {
     340           0 :             return TransactionBuilderResult("Failed to sign transaction");
     341             :         } else {
     342         320 :             UpdateTransaction(mtx, nIn, sigdata);
     343             :         }
     344             :     }
     345             : 
     346         234 :     return TransactionBuilderResult(CTransaction(mtx));
     347             : }
     348             : 
     349         145 : TransactionBuilderResult TransactionBuilder::AddDummySignatures()
     350             : {
     351         145 :     if (!spends.empty() || !outputs.empty()) {
     352             :         // Add Dummy Sapling OutputDescriptions
     353         378 :         for (unsigned int i = 0; i < outputs.size(); i++) {
     354         235 :             mtx.sapData->vShieldedOutput.push_back(DUMMY_SHIELD_OUT);
     355             :         }
     356             :         // Add Dummy Sapling SpendDescriptions
     357         221 :         for (unsigned int i = 0; i < spends.size(); i++) {
     358          78 :             mtx.sapData->vShieldedSpend.push_back(DUMMY_SHIELD_SPEND);
     359             :         }
     360             :         // Add Dummy Binding sig
     361         143 :         mtx.sapData->bindingSig = DUMMY_SHIELD_BINDSIG;
     362             :     }
     363             : 
     364             :     // Add Dummmy Transparent signatures
     365         290 :     CTransaction txNewConst(mtx);
     366         268 :     for (int nIn = 0; nIn < (int) mtx.vin.size(); nIn++) {
     367         246 :         auto tIn = tIns[nIn];
     368         246 :         SignatureData sigdata;
     369         123 :         if (!ProduceSignature(DummySignatureCreator(keystore), tIn.scriptPubKey, sigdata, SIGVERSION_SAPLING, false)) {
     370           0 :             return TransactionBuilderResult("Failed to sign transaction");
     371             :         } else {
     372         123 :             UpdateTransaction(mtx, nIn, sigdata);
     373             :         }
     374             :     }
     375             : 
     376         145 :     return TransactionBuilderResult(CTransaction(mtx));
     377             : }
     378             : 
     379          89 : void TransactionBuilder::ClearProofsAndSignatures()
     380             : {
     381             :     // Clear Sapling output descriptions
     382          89 :     mtx.sapData->vShieldedOutput.clear();
     383             : 
     384             :     // Clear Sapling spend descriptions
     385          89 :     mtx.sapData->vShieldedSpend.clear();
     386             : 
     387             :     // Clear Binding sig
     388          89 :     mtx.sapData->bindingSig = {{0}};
     389             : 
     390             :     // Clear Transparent signatures
     391         160 :     for (CTxIn& in : mtx.vin) in.scriptSig = CScript();
     392          89 : }
     393             : 
     394         294 : TransactionBuilderResult TransactionBuilder::Build(bool fDummySig)
     395             : {
     396             :     //
     397             :     // Consistency checks
     398             :     //
     399             :     // Valid fee
     400         294 :     if (fee < 0) {
     401           0 :         return TransactionBuilderResult("Fee cannot be negative");
     402             :     }
     403             : 
     404             :     // Valid change
     405         294 :     CAmount change = mtx.sapData->valueBalance - fee;
     406         667 :     for (auto& tIn : tIns) {
     407         373 :         change += tIn.value;
     408             :     }
     409         338 :     for (auto& tOut : mtx.vout) {
     410          44 :         change -= tOut.nValue;
     411             :     }
     412         294 :     if (change < 0) {
     413           6 :         return TransactionBuilderResult("Change cannot be negative");
     414             :     }
     415             : 
     416             :     //
     417             :     // Change output
     418             :     //
     419             : 
     420         291 :     if (change > 0) {
     421             :         // If we get here and the change is dust, add it to the fee
     422         149 :         CAmount dustThreshold = (spends.empty() && outputs.empty()) ? GetDustThreshold(dustRelayFee)
     423         144 :                                                                     : GetShieldedDustThreshold(dustRelayFee);
     424         149 :         if (change > dustThreshold) {
     425             :             // Send change to the specified change address. If no change address
     426             :             // was set, send change to the first Sapling address given as input
     427             :             // (A t-address can only be used as the change address if explicitly set.)
     428         148 :             if (saplingChangeAddr) {
     429           1 :                 AddSaplingOutput(saplingChangeAddr->first, saplingChangeAddr->second, change);
     430         147 :             } else if (tChangeAddr) {
     431             :                 // tChangeAddr has already been validated.
     432          75 :                 AddTransparentOutput(*tChangeAddr, change);
     433          72 :             } else if (!spends.empty()) {
     434          71 :                 auto fvk = spends[0].expsk.full_viewing_key();
     435          71 :                 auto note = spends[0].note;
     436          71 :                 libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
     437          71 :                 AddSaplingOutput(fvk.ovk, changeAddr, change);
     438             :             } else {
     439           2 :                 return TransactionBuilderResult("Could not determine change address");
     440             :             }
     441             :         } else {
     442             :             // Not used after, but update for consistency
     443           1 :             fee += change;
     444           1 :             change = 0;
     445             :         }
     446             :     }
     447             : 
     448         290 :     return fDummySig ? AddDummySignatures() : ProveAndSign();
     449             : }

Generated by: LCOV version 1.14