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 : }
|