Skip to content

Commit b14e739

Browse files
committed
Improve error reporting from VerifyAmounts
The famous "bad-txns-in-ne-out" error that could mean anything!
1 parent 6802564 commit b14e739

File tree

5 files changed

+50
-42
lines changed

5 files changed

+50
-42
lines changed

src/confidential_validation.cpp

+39-38
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, se
131131
return true;
132132
}
133133

134-
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* checks, const bool store_result) {
134+
const std::string VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* checks, const bool store_result) {
135135
assert(!tx.IsCoinBase());
136136
assert(inputs.size() == tx.vin.size());
137137

@@ -161,34 +161,34 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
161161
const CConfidentialAsset& asset = inputs[i].nAsset;
162162

163163
if (val.IsNull() || asset.IsNull())
164-
return false;
164+
return "input val.IsNull or asset.IsNull";
165165

166166
if (asset.IsExplicit()) {
167167
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
168168
assert(ret != 0);
169169
}
170170
else if (asset.IsCommitment()) {
171171
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
172-
return false;
172+
return "input commitment parse failed";
173173
}
174174
else {
175-
return false;
175+
return "input asset not explicit not commitment";
176176
}
177177

178178
target_generators.push_back(gen);
179179

180180
if (val.IsExplicit()) {
181181
if (!MoneyRange(val.GetAmount()))
182-
return false;
182+
return "input value not in moneyrange";
183183

184184
// Fails if val.GetAmount() == 0
185185
if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explicit_blinds, val.GetAmount(), &gen) != 1)
186-
return false;
186+
return "input explicit value pedersen commit failed";
187187
} else if (val.IsCommitment()) {
188188
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
189-
return false;
189+
return "input commitment pedersen commit failed";
190190
} else {
191-
return false;
191+
return "input value not explicit not commitment";
192192
}
193193

194194
vData.push_back(commit);
@@ -228,37 +228,38 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
228228
// Must check that prevout is the blinded issuance token
229229
// prevout's asset tag = assetTokenID + assetBlindingNonce
230230
if (secp256k1_generator_generate_blinded(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin(), issuance.assetBlindingNonce.begin()) != 1) {
231-
return false;
231+
return "reissuance generate blinded generator failed";
232232
}
233233
// Serialize the generator for direct comparison
234234
unsigned char derived_generator[33];
235235
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, derived_generator, &gen);
236236

237237
// Belt-and-suspenders: Check that asset commitment from issuance input is correct size
238238
if (asset.vchCommitment.size() != sizeof(derived_generator)) {
239-
return false;
239+
return "reissuance asset commitment size is not derived generator size";
240240
}
241241

242-
// We have already checked the outputs' generator commitment for general validity, so directly compare serialized bytes
242+
// We have already checked the outputs' generator commitment for general validity,
243+
// so directly compare serialized bytes
243244
if (memcmp(asset.vchCommitment.data(), derived_generator, sizeof(derived_generator))) {
244-
return false;
245+
return "reissuance asset commitment not equal to derived generator";
245246
}
246247
}
247248

248249
// Process issuance of asset
249250

250251
if (!issuance.nAmount.IsValid()) {
251-
return false;
252+
return "issuance amount not valid";
252253
}
253254
if (!issuance.nAmount.IsNull()) {
254255
// Note: This check disallows issuances in transactions with *no* witness data.
255256
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
256257
// to `VerifyIssuanceAmount` instead.
257258
if (i >= tx.witness.vtxinwit.size()) {
258-
return false;
259+
return "no witness for issuance input";
259260
}
260261
if (!VerifyIssuanceAmount(commit, gen, assetID, issuance.nAmount, tx.witness.vtxinwit[i].vchIssuanceAmountRangeproof, checks, store_result)) {
261-
return false;
262+
return "VerifyIssuanceAmount failed";
262263
}
263264
target_generators.push_back(gen);
264265
vData.push_back(commit);
@@ -269,22 +270,22 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
269270
// Process issuance of reissuance tokens
270271

271272
if (!issuance.nInflationKeys.IsValid()) {
272-
return false;
273+
return "issuance of reissuance tokens: inflatioinskeys invalid";
273274
}
274275
if (!issuance.nInflationKeys.IsNull()) {
275276
// Only initial issuance can have reissuance tokens
276277
if (!issuance.assetBlindingNonce.IsNull()) {
277-
return false;
278+
return "only initial issuance can have reissuance tokens";
278279
}
279280

280281
// Note: This check disallows issuances in transactions with *no* witness data.
281282
// This can be relaxed in a future update as a HF by passing in an empty rangeproof
282283
// to `VerifyIssuanceAmount` instead.
283284
if (i >= tx.witness.vtxinwit.size()) {
284-
return false;
285+
return "no witness for reissuance input";
285286
}
286287
if (!VerifyIssuanceAmount(commit, gen, assetTokenID, issuance.nInflationKeys, tx.witness.vtxinwit[i].vchInflationKeysRangeproof, checks, store_result)) {
287-
return false;
288+
return "reissuance VerifyIssuanceAmount failed";
288289
}
289290
target_generators.push_back(gen);
290291
vData.push_back(commit);
@@ -298,35 +299,35 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
298299
const CConfidentialValue& val = tx.vout[i].nValue;
299300
const CConfidentialAsset& asset = tx.vout[i].nAsset;
300301
if (!asset.IsValid())
301-
return false;
302+
return "output asset invalid";
302303
if (!val.IsValid())
303-
return false;
304+
return "output value invalid";
304305
if (!tx.vout[i].nNonce.IsValid())
305-
return false;
306+
return "output nonce invalid";
306307

307308
if (asset.IsExplicit()) {
308309
ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.GetAsset().begin());
309310
assert(ret != 0);
310311
}
311312
else if (asset.IsCommitment()) {
312313
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
313-
return false;
314+
return "output asset commitment parse failed";
314315
}
315316
else {
316-
return false;
317+
return "output asset is not explicit not commitment";
317318
}
318319

319320
if (val.IsExplicit()) {
320321
if (!MoneyRange(val.GetAmount()))
321-
return false;
322+
return "output value out of money range";
322323

323324
if (val.GetAmount() == 0) {
324325
if (tx.vout[i].scriptPubKey.IsUnspendable()) {
325326
continue;
326327
} else {
327328
// No spendable 0-value outputs
328329
// Reason: A spendable output of 0 reissuance tokens would allow reissuance without reissuance tokens.
329-
return false;
330+
return "attempted spendable 0-value output";
330331
}
331332
}
332333

@@ -336,9 +337,9 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
336337
}
337338
else if (val.IsCommitment()) {
338339
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &val.vchCommitment[0]) != 1)
339-
return false;
340+
return "output value pedersen commit parse failed";
340341
} else {
341-
return false;
342+
return "output value is not explicit not commitment";
342343
}
343344

344345
vData.push_back(commit);
@@ -348,7 +349,7 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
348349

349350
// Check balance
350351
if (QueueCheck(checks, new CBalanceCheck(vData, vpCommitsIn, vpCommitsOut)) != SCRIPT_ERR_OK) {
351-
return false;
352+
return "CBalanceCheck failed";
352353
}
353354

354355
// Range proofs
@@ -360,7 +361,7 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
360361
if (val.IsExplicit())
361362
{
362363
if (ptxoutwit && !ptxoutwit->vchRangeproof.empty())
363-
return false;
364+
return "explicit output value non-empty range proof";
364365
continue;
365366
}
366367
if (asset.IsExplicit()) {
@@ -369,10 +370,10 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
369370
secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &vchAssetCommitment[0], &gen);
370371
}
371372
if (!ptxoutwit) {
372-
return false;
373+
return "blinded output value without output witness";
373374
}
374375
if (QueueCheck(checks, new CRangeCheck(&val, ptxoutwit->vchRangeproof, vchAssetCommitment, tx.vout[i].scriptPubKey, store_result)) != SCRIPT_ERR_OK) {
375-
return false;
376+
return "CRangeCheck failed";
376377
}
377378
}
378379

@@ -384,25 +385,25 @@ bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, st
384385
// No need for surjection proof
385386
if (asset.IsExplicit()) {
386387
if (ptxoutwit && !ptxoutwit->vchSurjectionproof.empty()) {
387-
return false;
388+
return "explicit output asset non-empty surjection proof";
388389
}
389390
continue;
390391
}
391392
if (!ptxoutwit)
392-
return false;
393+
return "blinded output value without output witness";
393394
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &gen, &asset.vchCommitment[0]) != 1)
394-
return false;
395+
return "output asset generator parse failed";
395396

396397
secp256k1_surjectionproof proof;
397398
if (secp256k1_surjectionproof_parse(secp256k1_ctx_verify_amounts, &proof, &ptxoutwit->vchSurjectionproof[0], ptxoutwit->vchSurjectionproof.size()) != 1)
398-
return false;
399+
return "output asset surjection proof failed";
399400

400401
if (QueueCheck(checks, new CSurjectionCheck(proof, target_generators, gen, wtxid, store_result)) != SCRIPT_ERR_OK) {
401-
return false;
402+
return "CSurjectionCheck failed";
402403
}
403404
}
404405

405-
return true;
406+
return "";
406407
}
407408

408409
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees) {

src/confidential_validation.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class CSurjectionCheck : public CCheck
8989

9090
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check);
9191

92-
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* pvChecks, const bool cacheStore);
92+
const std::string VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* pvChecks, const bool cacheStore);
9393

9494
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees);
9595

src/consensus/tx_verify.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,12 @@ bool Consensus::CheckTxInputs(const CTransaction& tx, TxValidationState& state,
251251
}
252252

253253
// Verify that amounts add up.
254-
if (fScriptChecks && !VerifyAmounts(spent_inputs, tx, pvChecks, cacheStore)) {
255-
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-in-ne-out", "value in != value out");
254+
if (fScriptChecks) {
255+
const std::string amounts_check_result = VerifyAmounts(spent_inputs, tx, pvChecks, cacheStore);
256+
if (amounts_check_result.length() != 0) {
257+
/* return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-in-ne-out", "value in != value out"); */
258+
return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-in-ne-out", amounts_check_result);
259+
}
256260
}
257261
fee_map += GetFeeMap(tx);
258262
if (!MoneyRange(fee_map)) {

src/rpc/rawtransaction.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,7 @@ static RPCHelpMan testmempoolaccept()
10751075
result_inner.pushKV("reject-reason", "missing-inputs");
10761076
} else {
10771077
result_inner.pushKV("reject-reason", state.GetRejectReason());
1078+
result_inner.pushKV("error", state.ToString());
10781079
}
10791080
}
10801081
rpc_result.push_back(result_inner);

src/wallet/wallet.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -2091,7 +2091,9 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
20912091
}
20922092

20932093
CTransaction tx_tmp(tx);
2094-
if (!VerifyAmounts(inputs_utxos, tx_tmp, nullptr, false)) {
2094+
const std::string verify_amounts_result = VerifyAmounts(inputs_utxos, tx_tmp, nullptr, false);
2095+
if (verify_amounts_result.length() != 0) {
2096+
WalletLogPrintf("VerifyAmounts error: %s", verify_amounts_result);
20952097
return TransactionError::VALUE_IMBALANCE;
20962098
}
20972099
}

0 commit comments

Comments
 (0)