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(¶ms->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 : }
|