diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index bd568342da..c1e92f2ecb 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -43,8 +43,6 @@ public: return; /* Explicit value */ case 1: - /* Trust-me! asset generation */ - case 0xff: vchCommitment.resize(nExplicitSize); break; /* Confidential commitment */ @@ -79,8 +77,7 @@ public: bool IsValid() const { - return IsNull() || IsExplicit() || IsCommitment() - || (vchCommitment.size()==nExplicitSize && vchCommitment[0]==0xff); + return IsNull() || IsExplicit() || IsCommitment(); } friend bool operator==(const CConfidentialCommitment& a, const CConfidentialCommitment& b) diff --git a/src/script/script.h b/src/script/script.h index fa4dd176a5..21264b9584 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -657,6 +657,8 @@ public: * Returns whether the script is guaranteed to fail at execution, * regardless of the initial stack. This allows outputs to be pruned * instantly when entering the UTXO set. This includes fee outputs. + * + * This is consensus-critical because it is called by VerifyAmounts(). */ bool IsUnspendable() const { diff --git a/src/validation.cpp b/src/validation.cpp index 514c08b25e..43aac6e3bf 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -697,6 +697,50 @@ size_t GetNumIssuances(const CTransaction& tx) return numIssuances; } +// Helper function for VerifyAmount(), not exported +static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& commit, secp256k1_generator& gen, + const CAsset& asset, const CConfidentialValue& value, const std::vector& vchRangeproof, + std::vector* pvChecks, const bool cacheStore) +{ + // This is used to add in the explicit values + unsigned char explBlinds[32]; + memset(explBlinds, 0, sizeof(explBlinds)); + int ret; + + assert(value.IsValid()); + + // Generate asset generator + ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, asset.begin()); + assert(ret == 1); + + // Build value commitment + if (value.IsExplicit()) { + if (!MoneyRange(value.GetAmount()) || value.GetAmount() == 0) { + return false; + } + + ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, value.GetAmount(), &gen); + // The explBlinds are all 0, and the amount is not 0. So secp256k1_pedersen_commit does not fail. + assert(ret == 1); + } + else { + assert(value.IsCommitment()); + // Verify range proof + std::vector vchAssetCommitment(CConfidentialAsset::nExplicitSize); + secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &vchAssetCommitment[0], &gen); + if (QueueCheck(pvChecks, new CRangeCheck(&value, vchRangeproof, vchAssetCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) { + return false; + } + + // Here we have value.IsCommitment() == true + if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &value.vchCommitment[0]) != 1) { + return false; + } + } + + return true; +} + bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::vector* pvChecks, const bool cacheStore) { assert(!tx.IsCoinBase()); @@ -749,6 +793,7 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve if (!MoneyRange(val.GetAmount())) return false; + // Fails if val.GetAmount() == 0 if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1) return false; } else if (val.IsCommitment()) { @@ -787,8 +832,7 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve // Null nAmount is considered explicit 0, so just check for commitment CalculateReissuanceToken(assetTokenID, entropy, issuance.nAmount.IsCommitment()); } else { - //Re-issuance - + // Re-issuance // hashAssetIdentifier doubles as the entropy on reissuance CalculateAsset(assetID, issuance.assetEntropy); CalculateReissuanceToken(assetTokenID, issuance.assetEntropy, issuance.nAmount.IsCommitment()); @@ -805,92 +849,51 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve } // Process issuance of asset - if (!issuance.nAmount.IsNull()) { - // Generate asset generator and add to list of surjection targets - ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetID.begin()); - assert(ret == 1); - CConfidentialAsset issuanceAsset; - issuanceAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize); - secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &issuanceAsset.vchCommitment[0], &gen); - targetGenerators.push_back(gen); - - // Build value commitment and add to tally - if (issuance.nAmount.IsExplicit()) { - if (!MoneyRange(issuance.nAmount.GetAmount())) { - return false; - } - - if (issuance.nAmount.GetAmount() == 0) { - continue; - } - - if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nAmount.GetAmount(), &gen) != 1) { - return false; - } - } - else if (issuance.nAmount.IsCommitment()) { - if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nAmount.vchCommitment[0]) != 1) { - return false; - } - } else { - return false; - } - - vData.push_back(commit); - vpCommitsIn.push_back(p); - p++; - - // Rangecheck must be done for blinded amount - if (issuance.nAmount.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nAmount, tx.wit.vtxinwit[i].vchIssuanceAmountRangeproof, issuanceAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) { - return false; - } - } - - // Only initial issuance can have reissuance tokens - if (issuance.assetBlindingNonce.IsNull() && !issuance.nInflationKeys.IsNull()) { - - ret = secp256k1_generator_generate(secp256k1_ctx_verify_amounts, &gen, assetTokenID.begin()); - assert(ret == 1); - CConfidentialAsset tokenAsset(assetTokenID); - tokenAsset.vchCommitment.resize(CConfidentialAsset::nCommittedSize); - secp256k1_generator_serialize(secp256k1_ctx_verify_amounts, &tokenAsset.vchCommitment[0], &gen); - - targetGenerators.push_back(gen); - - if (issuance.nInflationKeys.IsExplicit()) { - if (!MoneyRange(issuance.nInflationKeys.GetAmount())) { - return false; - } - - if (issuance.nInflationKeys.GetAmount() == 0) { - continue; - } - - if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, issuance.nInflationKeys.GetAmount(), &gen) != 1) { - return false; - } - } - else if (issuance.nInflationKeys.IsCommitment()) { - if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &issuance.nInflationKeys.vchCommitment[0]) != 1) { - return false; - } - } else { - return false; - } - - vData.push_back(commit); - vpCommitsIn.push_back(p); - p++; - - if (issuance.nInflationKeys.IsCommitment() && QueueCheck(pvChecks, new CRangeCheck(&issuance.nInflationKeys, tx.wit.vtxinwit[i].vchInflationKeysRangeproof, tokenAsset.vchCommitment, CScript(), cacheStore)) != SCRIPT_ERR_OK) { - return false; - } - } else if (!issuance.nInflationKeys.IsNull()) { - // Token amount field must be null for reissuance + if (!issuance.nAmount.IsValid()) { return false; } + if (!issuance.nAmount.IsNull()) { + if (i >= tx.wit.vtxinwit.size()) { + return false; + } + if (!VerifyIssuanceAmount(commit, gen, assetID, issuance.nAmount, tx.wit.vtxinwit[i].vchIssuanceAmountRangeproof, pvChecks, cacheStore)) { + return false; + } + targetGenerators.push_back(gen); + vData.push_back(commit); + vpCommitsIn.push_back(p); + p++; + } + + if (!issuance.nAmount.IsValid()) { + return false; + } + + // 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; + } + + if (i >= tx.wit.vtxinwit.size()) { + return false; + } + if (!VerifyIssuanceAmount(commit, gen, assetTokenID, issuance.nInflationKeys, tx.wit.vtxinwit[i].vchInflationKeysRangeproof, pvChecks, cacheStore)) { + return false; + } + targetGenerators.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; @@ -924,12 +927,14 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve continue; } else { // No spendable 0-value outputs + // Reason: A spendable output of 0 reissuance tokens would allow reissuance without reissuance tokens. return false; } } - if (secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen) != 1) - return false; + ret = secp256k1_pedersen_commit(secp256k1_ctx_verify_amounts, &commit, explBlinds, val.GetAmount(), &gen); + // The explBlinds 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) @@ -973,11 +978,12 @@ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::ve } } + // Surjection proofs for (size_t i = 0; i < tx.vout.size(); i++) { const CConfidentialAsset& asset = tx.vout[i].nAsset; const CTxOutWitness* ptxoutwit = tx.wit.vtxoutwit.size() <= i? NULL: &tx.wit.vtxoutwit[i]; - //No need for surjective proof + // No need for surjection proof if (asset.IsExplicit()) { if (ptxoutwit && !ptxoutwit->vchSurjectionproof.empty()) { return false; diff --git a/src/validation.h b/src/validation.h index 0a1542ccc3..61d3425b8d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -399,11 +399,14 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins /** * Verify the transaction's outputs spend exactly what its inputs provide, plus some excess amount. * + * This also checks rangeproofs, surjection proofs, and issuances of assets and re-issuance tokens. + * The function assumes that IsValidPeginWitness() returns true on all peg-in inputs. + * * @param[in] view CCoinsViewCache to find necessary outputs * @param[in] tx transaction for which we are checking totals - * @param[in] pvChecks multithreaded rangeproof and commitment checker - * @param[in] cacheStore signal if rangeproof verification should be cached - * @return True if totals are identical + * @param[in] pvChecks multithreaded rangeproof, surjection proof and commitment checker + * @param[in] cacheStore signal if rangeproof and surjection proof verification should be cached + * @return True if verification was not aborted and totals are identical */ bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, std::vector* pvChecks = NULL, const bool cacheStore = false);