LCOV - code coverage report
Current view: top level - src/consensus - zerocoin_verify.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 22 158 13.9 %
Date: 2025-02-23 09:33:43 Functions: 1 8 12.5 %

          Line data    Source code
       1             : // Copyright (c) 2020-2021 The PIVX Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "zerocoin_verify.h"
       6             : 
       7             : #include "chainparams.h"
       8             : #include "consensus/consensus.h"
       9             : #include "invalid.h"
      10             : #include "script/interpreter.h"
      11             : #include "txdb.h" // for zerocoinDb
      12             : #include "utilmoneystr.h"        // for FormatMoney
      13             : #include "../validation.h"
      14             : #include "zpiv/zpivmodule.h"
      15             : 
      16             : 
      17           0 : static bool CheckZerocoinSpend(const CTransactionRef _tx, CValidationState& state)
      18             : {
      19           0 :     const CTransaction& tx = *_tx;
      20             :     //max needed non-mint outputs should be 2 - one for redemption address and a possible 2nd for change
      21           0 :     if (tx.vout.size() > 2) {
      22           0 :         int outs = 0;
      23           0 :         for (const CTxOut& out : tx.vout) {
      24           0 :             if (out.IsZerocoinMint())
      25           0 :                 continue;
      26           0 :             outs++;
      27             :         }
      28           0 :         if (outs > 2 && !tx.IsCoinStake())
      29           0 :             return state.DoS(100, error("%s: over two non-mint outputs in a zerocoinspend transaction", __func__));
      30             :     }
      31             : 
      32             :     //compute the txout hash that is used for the zerocoinspend signatures
      33           0 :     CMutableTransaction txTemp;
      34           0 :     for (const CTxOut& out : tx.vout) {
      35           0 :         txTemp.vout.push_back(out);
      36             :     }
      37           0 :     uint256 hashTxOut = txTemp.GetHash();
      38             : 
      39           0 :     bool fValidated = false;
      40           0 :     const Consensus::Params& consensus = Params().GetConsensus();
      41           0 :     std::set<CBigNum> serials;
      42           0 :     CAmount nTotalRedeemed = 0;
      43           0 :     for (const CTxIn& txin : tx.vin) {
      44             : 
      45             :         //only check txin that is a zcspend
      46           0 :         bool isPublicSpend = txin.IsZerocoinPublicSpend();
      47           0 :         if (!txin.IsZerocoinSpend() && !isPublicSpend)
      48           0 :             continue;
      49             : 
      50           0 :         libzerocoin::CoinSpend newSpend;
      51           0 :         CTxOut prevOut;
      52           0 :         if (isPublicSpend) {
      53           0 :             if(!GetOutput(txin.prevout.hash, txin.prevout.n, state, prevOut)){
      54           0 :                 return state.DoS(100, error("%s: public zerocoin spend prev output not found, prevTx %s, index %d", __func__, txin.prevout.hash.GetHex(), txin.prevout.n));
      55             :             }
      56           0 :             libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
      57           0 :             PublicCoinSpend publicSpend(params);
      58           0 :             if (!ZPIVModule::parseCoinSpend(txin, tx, prevOut, publicSpend)){
      59           0 :                 return state.DoS(100, error("%s: public zerocoin spend parse failed", __func__));
      60             :             }
      61           0 :             newSpend = publicSpend;
      62             :         } else {
      63           0 :             newSpend = ZPIVModule::TxInToZerocoinSpend(txin);
      64             :         }
      65             : 
      66             :         //check that the denomination is valid
      67           0 :         if (newSpend.getDenomination() == libzerocoin::ZQ_ERROR)
      68           0 :             return state.DoS(100, error("%s: Zerocoinspend does not have the correct denomination", __func__));
      69             : 
      70             :         //check that denomination is what it claims to be in nSequence
      71           0 :         if (newSpend.getDenomination() != txin.nSequence)
      72           0 :             return state.DoS(100, error("%s: Zerocoinspend nSequence denomination does not match CoinSpend", __func__));
      73             : 
      74             :         //make sure the txout has not changed
      75           0 :         if (newSpend.getTxOutHash() != hashTxOut)
      76           0 :             return state.DoS(100, error("%s: Zerocoinspend does not use the same txout that was used in the SoK", __func__));
      77             : 
      78           0 :         if (isPublicSpend) {
      79           0 :             libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
      80           0 :             PublicCoinSpend ret(params);
      81           0 :             if (!ZPIVModule::validateInput(txin, prevOut, tx, ret)){
      82           0 :                 return state.DoS(100, error("%s: public zerocoin spend did not verify", __func__));
      83             :             }
      84             :         }
      85             : 
      86           0 :         if (serials.count(newSpend.getCoinSerialNumber()))
      87           0 :             return state.DoS(100, error("%s: Zerocoinspend serial is used twice in the same tx", __func__));
      88           0 :         serials.insert(newSpend.getCoinSerialNumber());
      89             : 
      90             :         //make sure that there is no over redemption of coins
      91           0 :         nTotalRedeemed += libzerocoin::ZerocoinDenominationToAmount(newSpend.getDenomination());
      92           0 :         fValidated = true;
      93             :     }
      94             : 
      95           0 :     if (!tx.IsCoinStake() && nTotalRedeemed < tx.GetValueOut()) {
      96           0 :         LogPrintf("%s: redeemed = %s , spend = %s \n", __func__, FormatMoney(nTotalRedeemed), FormatMoney(tx.GetValueOut()));
      97           0 :         return state.DoS(100, error("%s: Transaction spend more than was redeemed in zerocoins", __func__));
      98             :     }
      99             : 
     100             :     return fValidated;
     101             : }
     102             : 
     103           0 : bool isBlockBetweenFakeSerialAttackRange(int nHeight)
     104             : {
     105           0 :     if (Params().NetworkIDString() != CBaseChainParams::MAIN)
     106             :         return false;
     107             : 
     108           0 :     return nHeight <= Params().GetConsensus().height_last_ZC_WrappedSerials;
     109             : }
     110             : 
     111           0 : bool CheckPublicCoinSpendEnforced(int blockHeight, bool isPublicSpend)
     112             : {
     113           0 :     if (Params().GetConsensus().NetworkUpgradeActive(blockHeight, Consensus::UPGRADE_ZC_PUBLIC)) {
     114             :         // reject old coin spend
     115           0 :         if (!isPublicSpend) {
     116           0 :             return error("%s: failed to add block with older zc spend version", __func__);
     117             :         }
     118             : 
     119             :     } else {
     120           0 :         if (isPublicSpend) {
     121           0 :             return error("%s: failed to add block, public spend enforcement not activated", __func__);
     122             :         }
     123             :     }
     124             :     return true;
     125             : }
     126             : 
     127      663439 : bool ContextualCheckZerocoinTx(const CTransactionRef& tx, CValidationState& state, const Consensus::Params& consensus, int nHeight, bool isMined)
     128             : {
     129             :     // zerocoin enforced via block time. First block with a zc mint is 863735
     130      663439 :     const bool fZerocoinEnforced = (nHeight >= consensus.ZC_HeightStart);
     131      663439 :     const bool fPublicSpendEnforced = consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_ZC_PUBLIC);
     132      663439 :     const bool fRejectMintsAndPrivateSpends = !isMined || !fZerocoinEnforced || fPublicSpendEnforced;
     133      663439 :     const bool fRejectPublicSpends = !isMined || !fPublicSpendEnforced || consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_0);
     134             : 
     135      663439 :     const bool hasPrivateSpendInputs = !tx->vin.empty() && tx->vin[0].IsZerocoinSpend();
     136      663439 :     const bool hasPublicSpendInputs = !tx->vin.empty() && tx->vin[0].IsZerocoinPublicSpend();
     137     1326874 :     const std::string& txId = tx->GetHash().ToString();
     138             : 
     139      663439 :     int nSpendCount{0};
     140     1857193 :     for (const CTxIn& in : tx->vin) {
     141     1193760 :         if (in.IsZerocoinSpend()) {
     142           2 :             if (fRejectMintsAndPrivateSpends)
     143           6 :                 return state.DoS(100, error("%s: zerocoin spend tx %s not accepted at height %d",
     144             :                                             __func__, txId, nHeight), REJECT_INVALID, "bad-txns-zc-private-spend");
     145           0 :             if (!hasPrivateSpendInputs)
     146           0 :                 return state.DoS(100, error("%s: zerocoin spend tx %s has mixed spend inputs",
     147             :                                             __func__, txId), REJECT_INVALID, "bad-txns-zc-private-spend-mixed-types");
     148           0 :             if (++nSpendCount > consensus.ZC_MaxSpendsPerTx)
     149           0 :                 return state.DoS(100, error("%s: zerocoin spend tx %s has more than %d inputs",
     150           0 :                                             __func__, txId, consensus.ZC_MaxSpendsPerTx), REJECT_INVALID, "bad-txns-zc-private-spend-max-inputs");
     151             : 
     152     1193758 :         } else if (in.IsZerocoinPublicSpend()) {
     153           2 :             if (fRejectPublicSpends)
     154           6 :                 return state.DoS(100, error("%s: zerocoin public spend tx %s not accepted at height %d",
     155             :                                             __func__, txId, nHeight), REJECT_INVALID, "bad-txns-zc-public-spend");
     156           0 :             if (!hasPublicSpendInputs)
     157           0 :                 return state.DoS(100, error("%s: zerocoin spend tx %s has mixed spend inputs",
     158             :                                             __func__, txId), REJECT_INVALID, "bad-txns-zc-public-spend-mixed-types");
     159           0 :             if (++nSpendCount > consensus.ZC_MaxPublicSpendsPerTx)
     160           0 :                 return state.DoS(100, error("%s: zerocoin spend tx %s has more than %d inputs",
     161           0 :                                             __func__, txId, consensus.ZC_MaxPublicSpendsPerTx), REJECT_INVALID, "bad-txns-zc-public-spend-max-inputs");
     162             : 
     163             :         } else {
     164             :             // this is a transparent input
     165     1193756 :             if (hasPrivateSpendInputs || hasPublicSpendInputs)
     166           0 :                 return state.DoS(100, error("%s: zerocoin spend tx %s has mixed spend inputs",
     167             :                                             __func__, txId), REJECT_INVALID, "bad-txns-zc-spend-mixed-types");
     168             :         }
     169             :     }
     170             : 
     171      663435 :     if (hasPrivateSpendInputs || hasPublicSpendInputs) {
     172           0 :         if (!CheckZerocoinSpend(tx, state))
     173             :             return false;   // failure reason logged in validation state
     174             :     }
     175             : 
     176     2412940 :     for (const CTxOut& o : tx->vout) {
     177     1749509 :         if (o.IsZerocoinMint() && fRejectMintsAndPrivateSpends) {
     178           6 :             return state.DoS(100, error("%s: zerocoin mint tx %s not accepted at height %d",
     179             :                                         __func__, txId, nHeight), REJECT_INVALID, "bad-txns-zc-mint");
     180             :         }
     181             :     }
     182             : 
     183      663433 :     return true;
     184             : }
     185             : 
     186           0 : bool IsSerialInBlockchain(const CBigNum& bnSerial, int& nHeightTx)
     187             : {
     188           0 :     uint256 txHash;
     189             :     // if not in zerocoinDB then its not in the blockchain
     190           0 :     if (!zerocoinDB->ReadCoinSpend(bnSerial, txHash))
     191             :         return false;
     192             : 
     193             :     // Now get the chain tx
     194           0 :     CTransactionRef tx;
     195           0 :     uint256 hashBlock;
     196           0 :     if (!GetTransaction(txHash, tx, hashBlock, true))
     197             :         return false;
     198             : 
     199           0 :     if (hashBlock.IsNull() || !mapBlockIndex.count(hashBlock)) {
     200             :         return false;
     201             :     }
     202             : 
     203           0 :     CBlockIndex* pindex = mapBlockIndex[hashBlock];
     204           0 :     if (!chainActive.Contains(pindex)) {
     205             :         return false;
     206             :     }
     207             : 
     208           0 :     nHeightTx = pindex->nHeight;
     209           0 :     return true;
     210             : }
     211             : 
     212           0 : bool ContextualCheckZerocoinSpend(const CTransaction& tx, const libzerocoin::CoinSpend* spend, int nHeight)
     213             : {
     214           0 :     if(!ContextualCheckZerocoinSpendNoSerialCheck(tx, spend, nHeight)){
     215             :         return false;
     216             :     }
     217             : 
     218             :     //Reject serial's that are already in the blockchain
     219           0 :     int nHeightTx = 0;
     220           0 :     if (IsSerialInBlockchain(spend->getCoinSerialNumber(), nHeightTx))
     221           0 :         return error("%s : zPIV spend with serial %s is already in block %d\n", __func__,
     222           0 :                      spend->getCoinSerialNumber().GetHex(), nHeightTx);
     223             : 
     224             :     return true;
     225             : }
     226             : 
     227           0 : bool ContextualCheckZerocoinSpendNoSerialCheck(const CTransaction& tx, const libzerocoin::CoinSpend* spend, int nHeight)
     228             : {
     229           0 :     const Consensus::Params& consensus = Params().GetConsensus();
     230             :     //Check to see if the zPIV is properly signed
     231           0 :     if (consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_ZC_V2)) {
     232           0 :         try {
     233           0 :             if (!spend->HasValidSignature())
     234           0 :                 return error("%s: V2 zPIV spend does not have a valid signature\n", __func__);
     235           0 :         } catch (const libzerocoin::InvalidSerialException& e) {
     236             :             // Check if we are in the range of the attack
     237           0 :             if(!isBlockBetweenFakeSerialAttackRange(nHeight))
     238           0 :                 return error("%s: Invalid serial detected, txid %s, in block %d\n", __func__, tx.GetHash().GetHex(), nHeight);
     239             :             else
     240           0 :                 LogPrintf("%s: Invalid serial detected within range in block %d\n", __func__, nHeight);
     241             :         }
     242             : 
     243           0 :         libzerocoin::SpendType expectedType = libzerocoin::SpendType::SPEND;
     244           0 :         if (tx.IsCoinStake())
     245           0 :             expectedType = libzerocoin::SpendType::STAKE;
     246           0 :         if (spend->getSpendType() != expectedType) {
     247           0 :             return error("%s: trying to spend zPIV without the correct spend type. txid=%s\n", __func__,
     248           0 :                          tx.GetHash().GetHex());
     249             :         }
     250             :     }
     251             : 
     252           0 :     bool fUseV1Params = spend->getCoinVersion() < libzerocoin::PUBKEY_VERSION;
     253             : 
     254             :     //Reject serial's that are not in the acceptable value range
     255           0 :     if (!spend->HasValidSerial(consensus.Zerocoin_Params(fUseV1Params)))  {
     256             :         // Up until this block our chain was not checking serials correctly..
     257           0 :         if (!isBlockBetweenFakeSerialAttackRange(nHeight))
     258           0 :             return error("%s : zPIV spend with serial %s from tx %s is not in valid range\n", __func__,
     259           0 :                      spend->getCoinSerialNumber().GetHex(), tx.GetHash().GetHex());
     260             :         else
     261           0 :             LogPrintf("%s:: HasValidSerial :: Invalid serial detected within range in block %d\n", __func__, nHeight);
     262             :     }
     263             : 
     264             : 
     265             :     return true;
     266             : }
     267             : 
     268           0 : bool ParseAndValidateZerocoinSpends(const Consensus::Params& consensus,
     269             :                                     const CTransaction& tx, int chainHeight,
     270             :                                     CValidationState& state,
     271             :                                     std::vector<std::pair<CBigNum, uint256>>& vSpendsRet)
     272             : {
     273           0 :     for (const CTxIn& txIn : tx.vin) {
     274           0 :         bool isPublicSpend = txIn.IsZerocoinPublicSpend();
     275           0 :         bool isPrivZerocoinSpend = txIn.IsZerocoinSpend();
     276           0 :         if (!isPrivZerocoinSpend && !isPublicSpend)
     277           0 :             continue;
     278             : 
     279             :         // Check enforcement
     280           0 :         if (!CheckPublicCoinSpendEnforced(chainHeight, isPublicSpend)) {
     281           0 :             return false;
     282             :         }
     283             : 
     284           0 :         if (isPublicSpend) {
     285           0 :             libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
     286           0 :             PublicCoinSpend publicSpend(params);
     287           0 :             if (!ZPIVModule::ParseZerocoinPublicSpend(txIn, tx, state, publicSpend)) {
     288           0 :                 return false;
     289             :             }
     290             :             //queue for db write after the 'justcheck' section has concluded
     291           0 :             if (!ContextualCheckZerocoinSpend(tx, &publicSpend, chainHeight)) {
     292           0 :                 state.DoS(100, error("%s: failed to add block %s with invalid public zc spend", __func__,
     293           0 :                                      tx.GetHash().GetHex()), REJECT_INVALID);
     294           0 :                 return false;
     295             :             }
     296           0 :             vSpendsRet.emplace_back(publicSpend.getCoinSerialNumber(), tx.GetHash());
     297             :         } else {
     298           0 :             libzerocoin::CoinSpend spend = ZPIVModule::TxInToZerocoinSpend(txIn);
     299             :             //queue for db write after the 'justcheck' section has concluded
     300           0 :             if (!ContextualCheckZerocoinSpend(tx, &spend, chainHeight)) {
     301           0 :                 return state.DoS(100, error("%s: failed to add block %s with invalid zerocoinspend", __func__,
     302           0 :                                      tx.GetHash().GetHex()), REJECT_INVALID);
     303             :             }
     304           0 :             vSpendsRet.emplace_back(spend.getCoinSerialNumber(), tx.GetHash());
     305             :         }
     306             :     }
     307           0 :     return !vSpendsRet.empty();
     308             : }

Generated by: LCOV version 1.14