PIVX Core  5.6.99
P2P Digital Currency
zerocoin_verify.cpp
Go to the documentation of this file.
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 static bool CheckZerocoinSpend(const CTransactionRef _tx, CValidationState& state)
18 {
19  const CTransaction& tx = *_tx;
20  //max needed non-mint outputs should be 2 - one for redemption address and a possible 2nd for change
21  if (tx.vout.size() > 2) {
22  int outs = 0;
23  for (const CTxOut& out : tx.vout) {
24  if (out.IsZerocoinMint())
25  continue;
26  outs++;
27  }
28  if (outs > 2 && !tx.IsCoinStake())
29  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  CMutableTransaction txTemp;
34  for (const CTxOut& out : tx.vout) {
35  txTemp.vout.push_back(out);
36  }
37  uint256 hashTxOut = txTemp.GetHash();
38 
39  bool fValidated = false;
40  const Consensus::Params& consensus = Params().GetConsensus();
41  std::set<CBigNum> serials;
42  CAmount nTotalRedeemed = 0;
43  for (const CTxIn& txin : tx.vin) {
44 
45  //only check txin that is a zcspend
46  bool isPublicSpend = txin.IsZerocoinPublicSpend();
47  if (!txin.IsZerocoinSpend() && !isPublicSpend)
48  continue;
49 
50  libzerocoin::CoinSpend newSpend;
51  CTxOut prevOut;
52  if (isPublicSpend) {
53  if(!GetOutput(txin.prevout.hash, txin.prevout.n, state, prevOut)){
54  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  libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
57  PublicCoinSpend publicSpend(params);
58  if (!ZPIVModule::parseCoinSpend(txin, tx, prevOut, publicSpend)){
59  return state.DoS(100, error("%s: public zerocoin spend parse failed", __func__));
60  }
61  newSpend = publicSpend;
62  } else {
63  newSpend = ZPIVModule::TxInToZerocoinSpend(txin);
64  }
65 
66  //check that the denomination is valid
67  if (newSpend.getDenomination() == libzerocoin::ZQ_ERROR)
68  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  if (newSpend.getDenomination() != txin.nSequence)
72  return state.DoS(100, error("%s: Zerocoinspend nSequence denomination does not match CoinSpend", __func__));
73 
74  //make sure the txout has not changed
75  if (newSpend.getTxOutHash() != hashTxOut)
76  return state.DoS(100, error("%s: Zerocoinspend does not use the same txout that was used in the SoK", __func__));
77 
78  if (isPublicSpend) {
79  libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
80  PublicCoinSpend ret(params);
81  if (!ZPIVModule::validateInput(txin, prevOut, tx, ret)){
82  return state.DoS(100, error("%s: public zerocoin spend did not verify", __func__));
83  }
84  }
85 
86  if (serials.count(newSpend.getCoinSerialNumber()))
87  return state.DoS(100, error("%s: Zerocoinspend serial is used twice in the same tx", __func__));
88  serials.insert(newSpend.getCoinSerialNumber());
89 
90  //make sure that there is no over redemption of coins
91  nTotalRedeemed += libzerocoin::ZerocoinDenominationToAmount(newSpend.getDenomination());
92  fValidated = true;
93  }
94 
95  if (!tx.IsCoinStake() && nTotalRedeemed < tx.GetValueOut()) {
96  LogPrintf("%s: redeemed = %s , spend = %s \n", __func__, FormatMoney(nTotalRedeemed), FormatMoney(tx.GetValueOut()));
97  return state.DoS(100, error("%s: Transaction spend more than was redeemed in zerocoins", __func__));
98  }
99 
100  return fValidated;
101 }
102 
104 {
105  if (Params().NetworkIDString() != CBaseChainParams::MAIN)
106  return false;
107 
108  return nHeight <= Params().GetConsensus().height_last_ZC_WrappedSerials;
109 }
110 
111 bool CheckPublicCoinSpendEnforced(int blockHeight, bool isPublicSpend)
112 {
113  if (Params().GetConsensus().NetworkUpgradeActive(blockHeight, Consensus::UPGRADE_ZC_PUBLIC)) {
114  // reject old coin spend
115  if (!isPublicSpend) {
116  return error("%s: failed to add block with older zc spend version", __func__);
117  }
118 
119  } else {
120  if (isPublicSpend) {
121  return error("%s: failed to add block, public spend enforcement not activated", __func__);
122  }
123  }
124  return true;
125 }
126 
127 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  const bool fZerocoinEnforced = (nHeight >= consensus.ZC_HeightStart);
131  const bool fPublicSpendEnforced = consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_ZC_PUBLIC);
132  const bool fRejectMintsAndPrivateSpends = !isMined || !fZerocoinEnforced || fPublicSpendEnforced;
133  const bool fRejectPublicSpends = !isMined || !fPublicSpendEnforced || consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_0);
134 
135  const bool hasPrivateSpendInputs = !tx->vin.empty() && tx->vin[0].IsZerocoinSpend();
136  const bool hasPublicSpendInputs = !tx->vin.empty() && tx->vin[0].IsZerocoinPublicSpend();
137  const std::string& txId = tx->GetHash().ToString();
138 
139  int nSpendCount{0};
140  for (const CTxIn& in : tx->vin) {
141  if (in.IsZerocoinSpend()) {
142  if (fRejectMintsAndPrivateSpends)
143  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  if (!hasPrivateSpendInputs)
146  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  if (++nSpendCount > consensus.ZC_MaxSpendsPerTx)
149  return state.DoS(100, error("%s: zerocoin spend tx %s has more than %d inputs",
150  __func__, txId, consensus.ZC_MaxSpendsPerTx), REJECT_INVALID, "bad-txns-zc-private-spend-max-inputs");
151 
152  } else if (in.IsZerocoinPublicSpend()) {
153  if (fRejectPublicSpends)
154  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  if (!hasPublicSpendInputs)
157  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  if (++nSpendCount > consensus.ZC_MaxPublicSpendsPerTx)
160  return state.DoS(100, error("%s: zerocoin spend tx %s has more than %d inputs",
161  __func__, txId, consensus.ZC_MaxPublicSpendsPerTx), REJECT_INVALID, "bad-txns-zc-public-spend-max-inputs");
162 
163  } else {
164  // this is a transparent input
165  if (hasPrivateSpendInputs || hasPublicSpendInputs)
166  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  if (hasPrivateSpendInputs || hasPublicSpendInputs) {
172  if (!CheckZerocoinSpend(tx, state))
173  return false; // failure reason logged in validation state
174  }
175 
176  for (const CTxOut& o : tx->vout) {
177  if (o.IsZerocoinMint() && fRejectMintsAndPrivateSpends) {
178  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  return true;
184 }
185 
186 bool IsSerialInBlockchain(const CBigNum& bnSerial, int& nHeightTx)
187 {
188  uint256 txHash;
189  // if not in zerocoinDB then its not in the blockchain
190  if (!zerocoinDB->ReadCoinSpend(bnSerial, txHash))
191  return false;
192 
193  // Now get the chain tx
194  CTransactionRef tx;
195  uint256 hashBlock;
196  if (!GetTransaction(txHash, tx, hashBlock, true))
197  return false;
198 
199  if (hashBlock.IsNull() || !mapBlockIndex.count(hashBlock)) {
200  return false;
201  }
202 
203  CBlockIndex* pindex = mapBlockIndex[hashBlock];
204  if (!chainActive.Contains(pindex)) {
205  return false;
206  }
207 
208  nHeightTx = pindex->nHeight;
209  return true;
210 }
211 
212 bool ContextualCheckZerocoinSpend(const CTransaction& tx, const libzerocoin::CoinSpend* spend, int nHeight)
213 {
214  if(!ContextualCheckZerocoinSpendNoSerialCheck(tx, spend, nHeight)){
215  return false;
216  }
217 
218  //Reject serial's that are already in the blockchain
219  int nHeightTx = 0;
220  if (IsSerialInBlockchain(spend->getCoinSerialNumber(), nHeightTx))
221  return error("%s : zPIV spend with serial %s is already in block %d\n", __func__,
222  spend->getCoinSerialNumber().GetHex(), nHeightTx);
223 
224  return true;
225 }
226 
228 {
229  const Consensus::Params& consensus = Params().GetConsensus();
230  //Check to see if the zPIV is properly signed
231  if (consensus.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_ZC_V2)) {
232  try {
233  if (!spend->HasValidSignature())
234  return error("%s: V2 zPIV spend does not have a valid signature\n", __func__);
235  } catch (const libzerocoin::InvalidSerialException& e) {
236  // Check if we are in the range of the attack
238  return error("%s: Invalid serial detected, txid %s, in block %d\n", __func__, tx.GetHash().GetHex(), nHeight);
239  else
240  LogPrintf("%s: Invalid serial detected within range in block %d\n", __func__, nHeight);
241  }
242 
244  if (tx.IsCoinStake())
245  expectedType = libzerocoin::SpendType::STAKE;
246  if (spend->getSpendType() != expectedType) {
247  return error("%s: trying to spend zPIV without the correct spend type. txid=%s\n", __func__,
248  tx.GetHash().GetHex());
249  }
250  }
251 
252  bool fUseV1Params = spend->getCoinVersion() < libzerocoin::PUBKEY_VERSION;
253 
254  //Reject serial's that are not in the acceptable value range
255  if (!spend->HasValidSerial(consensus.Zerocoin_Params(fUseV1Params))) {
256  // Up until this block our chain was not checking serials correctly..
258  return error("%s : zPIV spend with serial %s from tx %s is not in valid range\n", __func__,
259  spend->getCoinSerialNumber().GetHex(), tx.GetHash().GetHex());
260  else
261  LogPrintf("%s:: HasValidSerial :: Invalid serial detected within range in block %d\n", __func__, nHeight);
262  }
263 
264 
265  return true;
266 }
267 
269  const CTransaction& tx, int chainHeight,
270  CValidationState& state,
271  std::vector<std::pair<CBigNum, uint256>>& vSpendsRet)
272 {
273  for (const CTxIn& txIn : tx.vin) {
274  bool isPublicSpend = txIn.IsZerocoinPublicSpend();
275  bool isPrivZerocoinSpend = txIn.IsZerocoinSpend();
276  if (!isPrivZerocoinSpend && !isPublicSpend)
277  continue;
278 
279  // Check enforcement
280  if (!CheckPublicCoinSpendEnforced(chainHeight, isPublicSpend)) {
281  return false;
282  }
283 
284  if (isPublicSpend) {
285  libzerocoin::ZerocoinParams* params = consensus.Zerocoin_Params(false);
286  PublicCoinSpend publicSpend(params);
287  if (!ZPIVModule::ParseZerocoinPublicSpend(txIn, tx, state, publicSpend)) {
288  return false;
289  }
290  //queue for db write after the 'justcheck' section has concluded
291  if (!ContextualCheckZerocoinSpend(tx, &publicSpend, chainHeight)) {
292  state.DoS(100, error("%s: failed to add block %s with invalid public zc spend", __func__,
293  tx.GetHash().GetHex()), REJECT_INVALID);
294  return false;
295  }
296  vSpendsRet.emplace_back(publicSpend.getCoinSerialNumber(), tx.GetHash());
297  } else {
299  //queue for db write after the 'justcheck' section has concluded
300  if (!ContextualCheckZerocoinSpend(tx, &spend, chainHeight)) {
301  return state.DoS(100, error("%s: failed to add block %s with invalid zerocoinspend", __func__,
302  tx.GetHash().GetHex()), REJECT_INVALID);
303  }
304  vSpendsRet.emplace_back(spend.getCoinSerialNumber(), tx.GetHash());
305  }
306  }
307  return !vSpendsRet.empty();
308 }
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
const CChainParams & Params()
Return the currently selected parameters.
uint256 hash
Definition: transaction.h:35
uint32_t n
Definition: transaction.h:36
static const std::string MAIN
Chain name strings.
C++ wrapper for BIGNUM.
Definition: bignum.h:35
std::string GetHex() const
Definition: bignum.cpp:321
The block chain is a tree shaped structure starting with the genesis block at the root,...
Definition: chain.h:139
int nHeight
height of the entry in the chain. The genesis block has height 0
Definition: chain.h:151
bool Contains(const CBlockIndex *pindex) const
Efficiently check whether a block is present in this chain.
Definition: chain.h:435
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:72
The basic transaction that is broadcasted on the network and contained in blocks.
Definition: transaction.h:244
std::vector< CTxIn > vin
Definition: transaction.h:270
const uint256 & GetHash() const
Definition: transaction.h:301
CAmount GetValueOut() const
bool IsCoinStake() const
std::vector< CTxOut > vout
Definition: transaction.h:271
An input of a transaction.
Definition: transaction.h:94
bool IsZerocoinSpend() const
Definition: transaction.cpp:43
uint32_t nSequence
Definition: transaction.h:98
bool IsZerocoinPublicSpend() const
Definition: transaction.cpp:48
COutPoint prevout
Definition: transaction.h:96
An output of a transaction.
Definition: transaction.h:137
bool IsZerocoinMint() const
Definition: transaction.cpp:83
Capture information about block/transaction validation.
Definition: validation.h:24
bool DoS(int level, bool ret=false, unsigned int chRejectCodeIn=0, std::string strRejectReasonIn="", bool corruptionIn=false, const std::string &strDebugMessageIn="")
Definition: validation.h:39
bool IsNull() const
Definition: uint256.h:36
std::string GetHex() const
Definition: uint256.cpp:21
The complete proof needed to spend a zerocoin.
Definition: CoinSpend.h:79
const CBigNum & getCoinSerialNumber() const
Definition: CoinSpend.h:86
CoinDenomination getDenomination() const
Definition: CoinSpend.h:87
uint256 getTxOutHash() const
Definition: CoinSpend.h:89
bool HasValidSerial(ZerocoinParams *params) const
Definition: CoinSpend.cpp:40
SpendType getSpendType() const
Definition: CoinSpend.h:95
int getCoinVersion() const
Definition: CoinSpend.h:93
bool HasValidSignature() const
Definition: CoinSpend.cpp:46
256-bit opaque blob.
Definition: uint256.h:138
@ UPGRADE_V5_0
Definition: params.h:36
@ UPGRADE_ZC_V2
Definition: params.h:31
@ UPGRADE_ZC_PUBLIC
Definition: params.h:33
bool validateInput(const CTxIn &in, const CTxOut &prevOut, const CTransaction &tx, PublicCoinSpend &publicSpend)
Definition: zpivmodule.cpp:200
libzerocoin::CoinSpend TxInToZerocoinSpend(const CTxIn &txin)
Definition: zpivmodule.cpp:191
bool ParseZerocoinPublicSpend(const CTxIn &txIn, const CTransaction &tx, CValidationState &state, PublicCoinSpend &publicSpend)
Definition: zpivmodule.cpp:212
PublicCoinSpend parseCoinSpend(const CTxIn &in)
Definition: zpivmodule.cpp:157
CAmount ZerocoinDenominationToAmount(const CoinDenomination &denomination)
A mutable version of CTransaction.
Definition: transaction.h:409
uint256 GetHash() const
Compute the hash of this CMutableTransaction.
Definition: transaction.cpp:96
std::vector< CTxOut > vout
Definition: transaction.h:411
Parameters that influence chain consensus.
Definition: params.h:171
int ZC_MaxSpendsPerTx
Definition: params.h:253
int ZC_HeightStart
Definition: params.h:258
int ZC_MaxPublicSpendsPerTx
Definition: params.h:252
libzerocoin::ZerocoinParams * Zerocoin_Params(bool useModulusV1) const
Definition: params.h:260
int height_last_ZC_WrappedSerials
Definition: params.h:206
bool NetworkUpgradeActive(int nHeight, Consensus::UpgradeIndex idx) const
Returns true if the given network upgrade is active as of the given block height.
Definition: params.cpp:12
bool error(const char *fmt, const Args &... args)
Definition: system.h:77
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:456
bool NetworkUpgradeActive(int nHeight, const Consensus::Params &params, Consensus::UpgradeIndex idx)
Returns true if the given network upgrade is active as of the given block height.
Definition: upgrades.cpp:107
std::string FormatMoney(const CAmount &n, bool fPlus)
Money parsing/formatting utilities.
std::unique_ptr< CZerocoinDB > zerocoinDB
Global variable that points to the zerocoin database (protected by cs_main)
Definition: validation.cpp:208
bool GetTransaction(const uint256 &hash, CTransactionRef &txOut, uint256 &hashBlock, bool fAllowSlow, CBlockIndex *blockIndex)
Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock.
Definition: validation.cpp:671
BlockMap mapBlockIndex
Definition: validation.cpp:82
bool GetOutput(const uint256 &hash, unsigned int index, CValidationState &state, CTxOut &out)
Retrieve an output (from memory pool, or from disk, if possible)
Definition: validation.cpp:656
CChain chainActive
The currently-connected chain of blocks (protected by cs_main).
Definition: validation.cpp:84
bool ParseAndValidateZerocoinSpends(const Consensus::Params &consensus, const CTransaction &tx, int chainHeight, CValidationState &state, std::vector< std::pair< CBigNum, uint256 >> &vSpendsRet)
bool IsSerialInBlockchain(const CBigNum &bnSerial, int &nHeightTx)
bool ContextualCheckZerocoinSpend(const CTransaction &tx, const libzerocoin::CoinSpend *spend, int nHeight)
bool ContextualCheckZerocoinTx(const CTransactionRef &tx, CValidationState &state, const Consensus::Params &consensus, int nHeight, bool isMined)
bool isBlockBetweenFakeSerialAttackRange(int nHeight)
bool ContextualCheckZerocoinSpendNoSerialCheck(const CTransaction &tx, const libzerocoin::CoinSpend *spend, int nHeight)
bool CheckPublicCoinSpendEnforced(int blockHeight, bool isPublicSpend)