-
Notifications
You must be signed in to change notification settings - Fork 375
/
confidential_validation.cpp
436 lines (377 loc) · 16 KB
/
confidential_validation.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
#include <confidential_validation.h>
#include <issuance.h>
#include <pegins.h>
#include <script/sigcache.h>
#include <blind.h>
namespace {
static secp256k1_context *secp256k1_ctx_verify_amounts;
class CSecp256k1Init {
public:
CSecp256k1Init() {
assert(secp256k1_ctx_verify_amounts == NULL);
secp256k1_ctx_verify_amounts = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN);
assert(secp256k1_ctx_verify_amounts != NULL);
}
~CSecp256k1Init() {
assert(secp256k1_ctx_verify_amounts != NULL);
secp256k1_context_destroy(secp256k1_ctx_verify_amounts);
secp256k1_ctx_verify_amounts = NULL;
}
};
static CSecp256k1Init instance_of_csecp256k1;
}
bool HasValidFee(const CTransaction& tx) {
CAmountMap totalFee;
for (unsigned int i = 0; i < tx.vout.size(); i++) {
CAmount fee = 0;
if (tx.vout[i].IsFee()) {
fee = tx.vout[i].nValue.GetAmount();
if (fee == 0 || !MoneyRange(fee))
return false;
totalFee[tx.vout[i].nAsset.GetAsset()] += fee;
if (!MoneyRange(totalFee)) {
return false;
}
}
}
return true;
}
CAmountMap GetFeeMap(const CTransaction& tx) {
CAmountMap fee;
for (const CTxOut& txout : tx.vout) {
if (txout.IsFee()) {
fee[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
}
}
return fee;
}
bool CRangeCheck::operator()() {
if (val->IsExplicit()) {
return true;
}
if (!CachingRangeProofChecker(store).VerifyRangeProof(rangeproof, val->vchCommitment, assetCommitment, scriptPubKey, secp256k1_ctx_verify_amounts)) {
error = SCRIPT_ERR_RANGEPROOF;
return false;
}
return true;
};
bool CBalanceCheck::operator()() {
if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpCommitsIn.data(), vpCommitsIn.size(), vpCommitsOut.data(), vpCommitsOut.size())) {
error = SCRIPT_ERR_PEDERSEN_TALLY;
return false;
}
return true;
}
bool CSurjectionCheck::operator()() {
return CachingSurjectionProofChecker(store).VerifySurjectionProof(proof, vTags, gen, secp256k1_ctx_verify_amounts, wtxid);
}
// Destroys the check in the case of no queue, or passes its ownership to the queue.
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check) {
if (queue != NULL) {
queue->push_back(check);
return SCRIPT_ERR_OK;
}
bool success = (*check)();
ScriptError err = check->GetScriptError();
delete check;
return success ? SCRIPT_ERR_OK : err;
}
// Helper function for VerifyAmount(), not exported
static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, secp256k1_generator& asset_gen,
const CAsset& asset, const CConfidentialValue& value, const std::vector<unsigned char>& rangeproof,
std::vector<CCheck*>* checks, const bool store_result)
{
// This is used to add in the explicit values
unsigned char explicit_blinds[32];
memset(explicit_blinds, 0, sizeof(explicit_blinds));
int ret;
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &asset_gen, asset.begin());
assert(ret == 1);
// Build value commitment
if (value.IsExplicit()) {
if (!MoneyRange(value.GetAmount()) || value.GetAmount() == 0) {
return false;
}
if (!rangeproof.empty()) {
return false;
}
ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &value_commit, explicit_blinds, value.GetAmount(), &asset_gen);
// The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail.
assert(ret == 1);
} else if (value.IsCommitment()) {
// Verify range proof
std::vector<unsigned char> vchAssetCommitment(CConfidentialAsset::nExplicitSize);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, vchAssetCommitment.data(), &asset_gen);
if (QueueCheck(checks, new CRangeCheck(&value, rangeproof, vchAssetCommitment, CScript(), store_result)) != SCRIPT_ERR_OK) {
return false;
}
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &value_commit, value.vchCommitment.data()) != 1) {
return false;
}
} else {
return false;
}
return true;
}
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* checks, const bool store_result) {
assert(!tx.IsCoinBase());
assert(inputs.size() == tx.vin.size());
std::vector<secp256k1_pedersen_commitment> vData;
std::vector<secp256k1_pedersen_commitment *> vpCommitsIn, vpCommitsOut;
vData.reserve((tx.vin.size() + tx.vout.size() + GetNumIssuances(tx)));
secp256k1_pedersen_commitment *p = vData.data();
secp256k1_pedersen_commitment commit;
secp256k1_generator gen;
// This is used to add in the explicit values
unsigned char explicit_blinds[32] = {0};
int ret;
uint256 wtxid(tx.GetWitnessHash());
// This list is used to verify surjection proofs.
// Proofs must be constructed with the list being in
// order of input and non-null issuance pseudo-inputs, with
// input first, asset issuance second, reissuance token third.
std::vector<secp256k1_generator> target_generators;
target_generators.reserve(tx.vin.size() + GetNumIssuances(tx));
// Tally up value commitments, check balance
for (size_t i = 0; i < tx.vin.size(); ++i) {
const CConfidentialValue& val = inputs[i].nValue;
const CConfidentialAsset& asset = inputs[i].nAsset;
if (val.IsNull() || asset.IsNull())
return false;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
return false;
}
target_generators.push_back(gen);
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
return false;
// Fails if val.GetAmount() == 0
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explicit_blinds, val.GetAmount(), &gen) != 1)
return false;
} else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
return false;
} else {
return false;
}
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
// Each transaction input may have up to two "pseudo-inputs" to add to the LHS
// for (re)issuance and may require up to two rangeproof checks:
// blinded value of the new assets being made
// blinded value of the issuance tokens being made (only for initial issuance)
const CAssetIssuance& issuance = tx.vin[i].assetIssuance;
// No issuances to process, continue to next input
if (issuance.IsNull()) {
continue;
}
CAsset assetID;
CAsset assetTokenID;
// First construct the assets of the issuances and reissuance token
// These are calculated differently depending on if initial issuance or followup
// New issuance, compute the asset ids
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[i].prevout, issuance.assetEntropy);
CalculateAsset(assetID, entropy);
// Null nAmount is considered explicit 0, so just check for commitment
CalculateReissuanceToken(assetTokenID, entropy, issuance.nAmount.IsCommitment());
} else {
// Re-issuance
// hashAssetIdentifier doubles as the entropy on reissuance
CalculateAsset(assetID, issuance.assetEntropy);
CalculateReissuanceToken(assetTokenID, issuance.assetEntropy, issuance.nAmount.IsCommitment());
// Must check that prevout is the blinded issuance token
// prevout's asset tag = assetTokenID + assetBlindingNonce
if (secp256k1_generator_generate_blinded(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin(), issuance.assetBlindingNonce.begin()) != 1) {
return false;
}
// Serialize the generator for direct comparison
unsigned char derived_generator[33];
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, derived_generator, &gen);
// Belt-and-suspenders: Check that asset commitment from issuance input is correct size
if (asset.vchCommitment.size() != sizeof(derived_generator)) {
return false;
}
// We have already checked the outputs' generator commitment for general validity, so directly compare serialized bytes
if (memcmp(asset.vchCommitment.data(), derived_generator, sizeof(derived_generator))) {
return false;
}
}
// Process issuance of asset
if (!issuance.nAmount.IsValid()) {
return false;
}
if (!issuance.nAmount.IsNull()) {
// Note: This check disallows issuances in transactions with *no* witness data.
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
// to `VerifyIssuanceAmount` instead.
if (i >= tx.witness.vtxinwit.size()) {
return false;
}
if (!VerifyIssuanceAmount(commit, gen, assetID, issuance.nAmount, tx.witness.vtxinwit[i].vchIssuanceAmountRangeproof, checks, store_result)) {
return false;
}
target_generators.push_back(gen);
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
}
// Process issuance of reissuance tokens
if (!issuance.nInflationKeys.IsValid()) {
return false;
}
if (!issuance.nInflationKeys.IsNull()) {
// Only initial issuance can have reissuance tokens
if (!issuance.assetBlindingNonce.IsNull()) {
return false;
}
// Note: This check disallows issuances in transactions with *no* witness data.
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
// to `VerifyIssuanceAmount` instead.
if (i >= tx.witness.vtxinwit.size()) {
return false;
}
if (!VerifyIssuanceAmount(commit, gen, assetTokenID, issuance.nInflationKeys, tx.witness.vtxinwit[i].vchInflationKeysRangeproof, checks, store_result)) {
return false;
}
target_generators.push_back(gen);
vData.push_back(commit);
vpCommitsIn.push_back(p);
p++;
}
}
for (size_t i = 0; i < tx.vout.size(); ++i)
{
const CConfidentialValue& val = tx.vout[i].nValue;
const CConfidentialAsset& asset = tx.vout[i].nAsset;
if (!asset.IsValid())
return false;
if (!val.IsValid())
return false;
if (!tx.vout[i].nNonce.IsValid())
return false;
if (asset.IsExplicit()) {
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
}
else if (asset.IsCommitment()) {
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
}
else {
return false;
}
if (val.IsExplicit()) {
if (!MoneyRange(val.GetAmount()))
return false;
if (val.GetAmount() == 0) {
if (tx.vout[i].scriptPubKey.IsUnspendable()) {
continue;
} else {
// No spendable 0-value outputs
// Reason: A spendable output of 0 reissuance tokens would allow reissuance without reissuance tokens.
return false;
}
}
ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explicit_blinds, val.GetAmount(), &gen);
// The explicit_blinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail.
assert(ret == 1);
}
else if (val.IsCommitment()) {
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
return false;
} else {
return false;
}
vData.push_back(commit);
vpCommitsOut.push_back(p);
p++;
}
// Check balance
if (QueueCheck(checks, new CBalanceCheck(vData, vpCommitsIn, vpCommitsOut)) != SCRIPT_ERR_OK) {
return false;
}
// Range proofs
for (size_t i = 0; i < tx.vout.size(); i++) {
const CConfidentialValue& val = tx.vout[i].nValue;
const CConfidentialAsset& asset = tx.vout[i].nAsset;
std::vector<unsigned char> vchAssetCommitment = asset.vchCommitment;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
if (val.IsExplicit())
{
if (ptxoutwit && !ptxoutwit->vchRangeproof.empty())
return false;
continue;
}
if (asset.IsExplicit()) {
int ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
assert(ret != 0);
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &vchAssetCommitment[0], &gen);
}
if (!ptxoutwit) {
return false;
}
if (QueueCheck(checks, new CRangeCheck(&val, ptxoutwit->vchRangeproof, vchAssetCommitment, tx.vout[i].scriptPubKey, store_result)) != SCRIPT_ERR_OK) {
return false;
}
}
// Surjection proofs
for (size_t i = 0; i < tx.vout.size(); i++)
{
const CConfidentialAsset& asset = tx.vout[i].nAsset;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i? NULL: &tx.witness.vtxoutwit[i];
// No need for surjection proof
if (asset.IsExplicit()) {
if (ptxoutwit && !ptxoutwit->vchSurjectionproof.empty()) {
return false;
}
continue;
}
if (!ptxoutwit)
return false;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
return false;
secp256k1_surjectionproof proof;
if (secp256k1_surjectionproof_parse(secp256k1_ctx_verify_amounts, &proof, &ptxoutwit->vchSurjectionproof[0], ptxoutwit->vchSurjectionproof.size()) != 1)
return false;
if (QueueCheck(checks, new CSurjectionCheck(proof, target_generators, gen, wtxid, store_result)) != SCRIPT_ERR_OK) {
return false;
}
}
return true;
}
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees) {
assert(tx.IsCoinBase());
// Miner shouldn't be stuffing witness data
for (const auto& outwit : tx.witness.vtxoutwit) {
if (!outwit.IsNull()) {
return false;
}
}
CAmountMap remaining = mapFees;
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const CTxOut& out = tx.vout[i];
if (!out.nValue.IsExplicit() || !out.nAsset.IsExplicit()) {
return false;
}
if (!MoneyRange(out.nValue.GetAmount())) {
return false;
}
if (g_con_elementsmode &&
out.nValue.GetAmount() == 0 && !out.scriptPubKey.IsUnspendable()) {
return false;
}
remaining[out.nAsset.GetAsset()] -= out.nValue.GetAmount();
}
return MoneyRange(remaining);
}