LCOV - code coverage report
Current view: top level - src/zpiv - zpivmodule.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 9 116 7.8 %
Date: 2025-02-23 09:33:43 Functions: 2 17 11.8 %

          Line data    Source code
       1             : // Copyright (c) 2019-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 "zpiv/zpivmodule.h"
       6             : 
       7             : #include "hash.h"
       8             : #include "libzerocoin/Commitment.h"
       9             : #include "libzerocoin/Coin.h"
      10             : #include "validation.h"
      11             : 
      12             : template <typename Stream>
      13           0 : PublicCoinSpend::PublicCoinSpend(libzerocoin::ZerocoinParams* params, Stream& strm): pubCoin(params) {
      14           0 :     strm >> *this;
      15           0 :     this->spendType = libzerocoin::SpendType::SPEND;
      16             : 
      17           0 :     if (this->version < PUBSPEND_SCHNORR) {
      18             :         // coinVersion is serialized only from v4 spends
      19           0 :         this->coinVersion = libzerocoin::ExtractVersionFromSerial(this->coinSerialNumber);
      20             : 
      21             :     } else {
      22             :         // from v4 spends, serialNumber is not serialized for v2 coins anymore.
      23             :         // in this case, we extract it from the coin public key
      24           0 :         if (this->coinVersion >= libzerocoin::PUBKEY_VERSION)
      25           0 :             this->coinSerialNumber = libzerocoin::ExtractSerialFromPubKey(this->pubkey);
      26             : 
      27             :     }
      28             : 
      29           0 : }
      30             : 
      31           0 : bool PublicCoinSpend::Verify() const {
      32           0 :     bool fUseV1Params = getCoinVersion() < libzerocoin::PUBKEY_VERSION;
      33           0 :     if (version < PUBSPEND_SCHNORR) {
      34             :         // spend contains the randomness of the coin
      35           0 :         if (fUseV1Params) {
      36             :             // Only v2+ coins can publish the randomness
      37           0 :             std::string errMsg = strprintf("PublicCoinSpend version %d with coin version 1 not allowed. "
      38           0 :                     "Minimum spend version required: %d", version, PUBSPEND_SCHNORR);
      39           0 :             return error("%s: %s", __func__, errMsg);
      40             :         }
      41             : 
      42             :         // Check that the coin is a commitment to serial and randomness.
      43           0 :         libzerocoin::ZerocoinParams* params = Params().GetConsensus().Zerocoin_Params(false);
      44           0 :         libzerocoin::Commitment comm(&params->coinCommitmentGroup, getCoinSerialNumber(), randomness);
      45           0 :         if (comm.getCommitmentValue() != pubCoin.getValue()) {
      46           0 :             return error("%s: commitments values are not equal", __func__);
      47             :         }
      48             : 
      49             :     } else {
      50             :         // for v1 coins, double check that the serialized coin serial is indeed a v1 serial
      51           0 :         if (coinVersion < libzerocoin::PUBKEY_VERSION &&
      52           0 :                 libzerocoin::ExtractVersionFromSerial(this->coinSerialNumber) != coinVersion) {
      53           0 :             return error("%s: invalid coin version", __func__);
      54             :         }
      55             : 
      56             :         // spend contains a shnorr signature of ptxHash with the randomness of the coin
      57           0 :         libzerocoin::ZerocoinParams* params = Params().GetConsensus().Zerocoin_Params(fUseV1Params);
      58           0 :         if (!schnorrSig.Verify(params, getCoinSerialNumber(), pubCoin.getValue(), getTxOutHash())) {
      59           0 :             return error("%s: schnorr signature does not verify", __func__);
      60             :         }
      61             : 
      62             :     }
      63             : 
      64             :     // Now check that the signature validates with the serial
      65           0 :     if (!HasValidSignature()) {
      66           0 :         return error("%s: signature invalid", __func__);;
      67             :     }
      68             : 
      69             :     return true;
      70             : }
      71             : 
      72           0 : bool PublicCoinSpend::HasValidSignature() const
      73             : {
      74           0 :     if (coinVersion < libzerocoin::PUBKEY_VERSION)
      75             :         return true;
      76             : 
      77             :     // for spend version 3 we must check that the provided pubkey and serial number match
      78           0 :     if (version < PUBSPEND_SCHNORR) {
      79           0 :         CBigNum extractedSerial = libzerocoin::ExtractSerialFromPubKey(this->pubkey);
      80           0 :         if (extractedSerial != this->coinSerialNumber)
      81           0 :             return error("%s: hashedpubkey is not equal to the serial!", __func__);
      82             :     }
      83             : 
      84           0 :     return pubkey.Verify(signatureHash(), vchSig);
      85             : }
      86             : 
      87             : 
      88           0 : const uint256 PublicCoinSpend::signatureHash() const
      89             : {
      90           0 :     CHashWriter h(0, 0);
      91           0 :     h << ptxHash << denomination << getCoinSerialNumber() << randomness << txHash << outputIndex << getSpendType();
      92           0 :     return h.GetHash();
      93             : }
      94             : 
      95             : // 6 comes from OPCODE (1) + vch.size() (1) + BIGNUM size (4)
      96             : #define SCRIPT_OFFSET 6
      97             : 
      98           0 : static bool TxOutToPublicCoin(const CTxOut& txout, libzerocoin::PublicCoin& pubCoin, CValidationState& state)
      99             : {
     100           0 :     CBigNum publicZerocoin;
     101           0 :     std::vector<unsigned char> vchZeroMint;
     102           0 :     vchZeroMint.insert(vchZeroMint.end(), txout.scriptPubKey.begin() + SCRIPT_OFFSET,
     103           0 :                        txout.scriptPubKey.begin() + txout.scriptPubKey.size());
     104           0 :     publicZerocoin.setvch(vchZeroMint);
     105             : 
     106           0 :     libzerocoin::CoinDenomination denomination = libzerocoin::AmountToZerocoinDenomination(txout.nValue);
     107           0 :     LogPrint(BCLog::LEGACYZC, "%s : denomination %d for pubcoin %s\n", __func__, denomination, publicZerocoin.GetHex());
     108           0 :     if (denomination == libzerocoin::ZQ_ERROR)
     109           0 :         return state.DoS(100, error("%s: txout.nValue is not correct", __func__));
     110             : 
     111           0 :     libzerocoin::PublicCoin checkPubCoin(Params().GetConsensus().Zerocoin_Params(false), publicZerocoin, denomination);
     112           0 :     pubCoin = checkPubCoin;
     113             : 
     114           0 :     return true;
     115             : }
     116             : 
     117             : // TODO: do not create g_coinspends_cache if the node passed the last zc checkpoint.
     118         479 : class CoinSpendCache {
     119             : private:
     120             :     mutable Mutex cs;
     121             :     std::map<CScript, libzerocoin::CoinSpend> cache_coinspend;
     122             :     std::map<CScript, PublicCoinSpend> cache_public_coinspend;
     123             : 
     124             :     template<typename T>
     125           0 :     Optional<T> Get(const CScript& in, const std::map<CScript, T>& map) const {
     126           0 :         LOCK(cs);
     127           0 :         auto it = map.find(in);
     128           0 :         return it != map.end() ? Optional<T>{it->second} : nullopt;
     129             :     }
     130             : 
     131             : public:
     132           0 :     void Add(const CScript& in, libzerocoin::CoinSpend& spend) { WITH_LOCK(cs, cache_coinspend.emplace(in, spend)); }
     133           0 :     void AddPub(const CScript& in, PublicCoinSpend& spend) { WITH_LOCK(cs, cache_public_coinspend.emplace(in, spend)); }
     134             : 
     135           0 :     Optional<libzerocoin::CoinSpend> Get(const CScript& in) const { return Get<libzerocoin::CoinSpend>(in, cache_coinspend); }
     136           0 :     Optional<PublicCoinSpend> GetPub(const CScript& in) const { return Get<PublicCoinSpend>(in, cache_public_coinspend); }
     137          19 :     void Clear() {
     138          19 :         LOCK(cs);
     139          19 :         cache_coinspend.clear();
     140          19 :         cache_public_coinspend.clear();
     141          19 :     }
     142             : };
     143             : std::unique_ptr<CoinSpendCache> g_coinspends_cache = std::make_unique<CoinSpendCache>();
     144             : 
     145             : namespace ZPIVModule {
     146             : 
     147             :     // Return stream of CoinSpend from tx input scriptsig
     148           0 :     CDataStream ScriptSigToSerializedSpend(const CScript& scriptSig)
     149             :     {
     150           0 :         std::vector<char, zero_after_free_allocator<char> > data;
     151             :         // skip opcode and data-len
     152           0 :         uint8_t byteskip = ((uint8_t) scriptSig[1] + 2);
     153           0 :         data.insert(data.end(), scriptSig.begin() + byteskip, scriptSig.end());
     154           0 :         return CDataStream(data, SER_NETWORK, PROTOCOL_VERSION);
     155             :     }
     156             : 
     157           0 :     PublicCoinSpend parseCoinSpend(const CTxIn &in)
     158             :     {
     159           0 :         libzerocoin::ZerocoinParams *params = Params().GetConsensus().Zerocoin_Params(false);
     160           0 :         CDataStream serializedCoinSpend = ScriptSigToSerializedSpend(in.scriptSig);
     161           0 :         return PublicCoinSpend(params, serializedCoinSpend);
     162             :     }
     163             : 
     164           0 :     bool parseCoinSpend(const CTxIn &in, const CTransaction &tx, const CTxOut &prevOut, PublicCoinSpend &publicCoinSpend) {
     165           0 :         if (auto op = g_coinspends_cache->GetPub(in.scriptSig)) {
     166           0 :             publicCoinSpend = *op;
     167           0 :             return true;
     168             :         }
     169             : 
     170           0 :         if (!in.IsZerocoinPublicSpend() || !prevOut.IsZerocoinMint())
     171           0 :             return error("%s: invalid argument/s", __func__);
     172             : 
     173           0 :         PublicCoinSpend spend = parseCoinSpend(in);
     174           0 :         spend.outputIndex = in.prevout.n;
     175           0 :         spend.txHash = in.prevout.hash;
     176           0 :         CMutableTransaction txNew(tx);
     177           0 :         txNew.vin.clear();
     178           0 :         spend.setTxOutHash(txNew.GetHash());
     179             : 
     180             :         // Check prev out now
     181           0 :         CValidationState state;
     182           0 :         if (!TxOutToPublicCoin(prevOut, spend.pubCoin, state))
     183           0 :             return error("%s: cannot get mint from output", __func__);
     184             : 
     185           0 :         spend.setDenom(spend.pubCoin.getDenomination());
     186           0 :         publicCoinSpend = spend;
     187           0 :         g_coinspends_cache->AddPub(in.scriptSig, publicCoinSpend);
     188           0 :         return true;
     189             :     }
     190             : 
     191           0 :     libzerocoin::CoinSpend TxInToZerocoinSpend(const CTxIn& txin)
     192             :     {
     193           0 :         if (auto op = g_coinspends_cache->Get(txin.scriptSig)) return *op;
     194           0 :         CDataStream serializedCoinSpend = ScriptSigToSerializedSpend(txin.scriptSig);
     195           0 :         libzerocoin::CoinSpend spend(serializedCoinSpend);
     196           0 :         g_coinspends_cache->Add(txin.scriptSig, spend);
     197           0 :         return spend;
     198             :     }
     199             : 
     200           0 :     bool validateInput(const CTxIn &in, const CTxOut &prevOut, const CTransaction &tx, PublicCoinSpend &publicSpend) {
     201             :         // Now prove that the commitment value opens to the input
     202           0 :         if (!parseCoinSpend(in, tx, prevOut, publicSpend)) {
     203             :             return false;
     204             :         }
     205           0 :         if (libzerocoin::ZerocoinDenominationToAmount(
     206           0 :                 libzerocoin::IntToZerocoinDenomination(in.nSequence)) != prevOut.nValue) {
     207           0 :             return error("PublicCoinSpend validateInput :: input nSequence different to prevout value");
     208             :         }
     209           0 :         return publicSpend.Verify();
     210             :     }
     211             : 
     212           0 :     bool ParseZerocoinPublicSpend(const CTxIn &txIn, const CTransaction& tx, CValidationState& state, PublicCoinSpend& publicSpend)
     213             :     {
     214           0 :         CTxOut prevOut;
     215           0 :         if(!GetOutput(txIn.prevout.hash, txIn.prevout.n ,state, prevOut)){
     216           0 :             return state.DoS(100, error("%s: public zerocoin spend prev output not found, prevTx %s, index %d",
     217           0 :                                         __func__, txIn.prevout.hash.GetHex(), txIn.prevout.n));
     218             :         }
     219           0 :         if (!ZPIVModule::parseCoinSpend(txIn, tx, prevOut, publicSpend)) {
     220           0 :             return state.Invalid(error("%s: invalid public coin spend parse %s\n", __func__,
     221           0 :                                        tx.GetHash().GetHex()), REJECT_INVALID, "bad-txns-invalid-zpiv");
     222             :         }
     223             :         return true;
     224             :     }
     225             : 
     226          19 :     void CleanCoinSpendsCache()
     227             :     {
     228          19 :         g_coinspends_cache->Clear();
     229          19 :     }
     230             : }

Generated by: LCOV version 1.14