From e877750729b5ed9a4d320ddabc78ad7bb391b7c6 Mon Sep 17 00:00:00 2001 From: instagibbs Date: Wed, 6 Jul 2016 10:30:43 +0200 Subject: [PATCH] Move amount verification to main --- src/coins.cpp | 96 ----------------------------- src/coins.h | 9 --- src/test/blind_tests.cpp | 15 ++--- src/test/transaction_tests.cpp | 2 +- src/validation.cpp | 107 ++++++++++++++++++++++++++++++++- src/validation.h | 11 ++++ 6 files changed, 125 insertions(+), 115 deletions(-) diff --git a/src/coins.cpp b/src/coins.cpp index 79335d0ae5..01960370dc 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -8,8 +8,6 @@ #include "random.h" #include -#include -#include /** * calculate number of bytes for the bitmask, and its number of non-zero bytes @@ -328,100 +326,6 @@ const CTxOut &CCoinsViewCache::GetOutputFor(const CTxIn& input) const return coins->vout[input.prevout.n]; } -static secp256k1_context* secp256k1_ctx_verify_amounts = NULL; - -class Secp256k1Ctx -{ -public: - Secp256k1Ctx() { - assert(secp256k1_ctx_verify_amounts == NULL); - secp256k1_ctx_verify_amounts = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); - secp256k1_pedersen_context_initialize(secp256k1_ctx_verify_amounts); - secp256k1_rangeproof_context_initialize(secp256k1_ctx_verify_amounts); - assert(secp256k1_ctx_verify_amounts != NULL); - } - - ~Secp256k1Ctx() { - assert(secp256k1_ctx_verify_amounts != NULL); - secp256k1_context_destroy(secp256k1_ctx_verify_amounts); - secp256k1_ctx_verify_amounts = NULL; - } -}; -static Secp256k1Ctx init_context_on_load; - -bool CCoinsViewCache::VerifyAmounts(const CTransaction& tx, const CAmount& excess) const -{ - CAmount nPlainAmount = excess; - std::vector vchData; - std::vector vpchCommitsIn, vpchCommitsOut; - bool fNullRangeproof = false; - vchData.resize(CTxOutValue::nCommitmentSize * (tx.vin.size() + tx.vout.size())); - unsigned char *p = vchData.data(); - if (!tx.IsCoinBase()) - { - for (size_t i = 0; i < tx.vin.size(); ++i) - { - const CTxOutValue& val = GetOutputFor(tx.vin[i]).nValue; - if (val.IsAmount()) { - nPlainAmount -= val.GetAmount(); - if (!MoneyRange(val.GetAmount()) || (!MoneyRange(nPlainAmount) && !MoneyRange(-nPlainAmount))) - return false; - } - else - { - assert(val.vchCommitment.size() == CTxOutValue::nCommitmentSize); - memcpy(p, &val.vchCommitment[0], CTxOutValue::nCommitmentSize); - vpchCommitsIn.push_back(p); - p += CTxOutValue::nCommitmentSize; - } - } - } - for (size_t i = 0; i < tx.vout.size(); ++i) - { - const CTxOutValue& val = tx.vout[i].nValue; - assert(val.vchCommitment.size() == CTxOutValue::nCommitmentSize); - if (val.vchNonceCommitment.size() > CTxOutValue::nCommitmentSize || val.vchRangeproof.size() > 5000) - return false; - if (val.IsAmount()) { - nPlainAmount += val.GetAmount(); - if (!MoneyRange(val.GetAmount()) || (!MoneyRange(nPlainAmount) && !MoneyRange(-nPlainAmount))) - return false; - } - else - { - memcpy(p, &val.vchCommitment[0], CTxOutValue::nCommitmentSize); - vpchCommitsOut.push_back(p); - p += CTxOutValue::nCommitmentSize; - - if (val.vchRangeproof.empty()) - fNullRangeproof = true; - } - } - - // If there are no encrypted input or output values, we can do simple math - if (vpchCommitsIn.size() + vpchCommitsOut.size() == 0) - return (nPlainAmount == 0); - - if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpchCommitsIn.data(), vpchCommitsIn.size(), vpchCommitsOut.data(), vpchCommitsOut.size(), nPlainAmount)) - return false; - - // Rangeproof is optional in this case - if ((!vpchCommitsIn.empty()) && vpchCommitsOut.size() == 1 && nPlainAmount <= 0 && fNullRangeproof) - return true; - - uint64_t min_value, max_value; - for (size_t i = 0; i < tx.vout.size(); ++i) - { - const CTxOutValue& val = tx.vout[i].nValue; - if (val.IsAmount()) - continue; - if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &val.vchCommitment[0], val.vchRangeproof.data(), val.vchRangeproof.size())) - return false; - } - - return true; -} - bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const { if (!tx.IsCoinBase()) { diff --git a/src/coins.h b/src/coins.h index b52f7416db..ac75af9c64 100644 --- a/src/coins.h +++ b/src/coins.h @@ -464,15 +464,6 @@ public: //! Calculate the size of the cache (in bytes) size_t DynamicMemoryUsage() const; - /** - * Verify the transaction's outputs spend exactly what its inputs provide, plus some excess amount. - * - * @param[in] tx transaction for which we are checking totals - * @param[in] excess additional amount to consider as input value (eg fees), can be negative - * @return True if totals are identical - */ - bool VerifyAmounts(const CTransaction& tx, const CAmount& excess) const; - //! Check whether all prevouts of the transaction are present in the UTXO set represented by this view bool HaveInputs(const CTransaction& tx) const; diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index a286749996..b03bbcfb7e 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -7,6 +7,7 @@ #include "coins.h" #include "uint256.h" #include "wallet/wallet.h" +#include "validation.h" #include "test/test_bitcoin.h" @@ -64,7 +65,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx3.vout.resize(1); tx3.vout[0].nValue = 100; tx3.nTxFee = 22; - BOOST_CHECK(cache.VerifyAmounts(tx3, tx3.nTxFee)); + BOOST_CHECK(VerifyAmounts(cache, tx3, tx3.nTxFee)); // Try to blind with a single output, which fails as its blinding factor ends up being zero. std::vector input_blinds; @@ -84,7 +85,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) BOOST_CHECK(BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx3)); BOOST_CHECK(!tx3.vout[0].nValue.IsAmount()); BOOST_CHECK(!tx3.vout[1].nValue.IsAmount()); - BOOST_CHECK(cache.VerifyAmounts(tx3, tx3.nTxFee)); + BOOST_CHECK(VerifyAmounts(cache, tx3, tx3.nTxFee)); CAmount unblinded_amount; BOOST_CHECK(UnblindOutput(key2, tx3.vout[0], unblinded_amount, blind3) == 0); @@ -99,7 +100,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) in3->vout[1] = tx3.vout[1]; tx3.nTxFee--; - BOOST_CHECK(!cache.VerifyAmounts(tx3, tx3.nTxFee)); + BOOST_CHECK(!VerifyAmounts(cache, tx3, tx3.nTxFee)); } { @@ -114,7 +115,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx4.vout[0].nValue = 30; tx4.vout[1].nValue = 40; tx4.nTxFee = 100 + 111 - 30 - 40; - BOOST_CHECK(!cache.VerifyAmounts(tx4, tx4.nTxFee)); // Spends a blinded coin with no blinded outputs to compensate. + BOOST_CHECK(!VerifyAmounts(cache, tx4, tx4.nTxFee)); // Spends a blinded coin with no blinded outputs to compensate. std::vector input_blinds; std::vector output_blinds; @@ -141,7 +142,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx4.vout[1].nValue = 40; tx4.vout[2].nValue = 50; tx4.nTxFee = 100 + 111 - 30 - 40 - 50; - BOOST_CHECK(!cache.VerifyAmounts(tx4, tx4.nTxFee)); // Spends a blinded coin with no blinded outputs to compensate. + BOOST_CHECK(!VerifyAmounts(cache, tx4, tx4.nTxFee)); // Spends a blinded coin with no blinded outputs to compensate. std::vector input_blinds; std::vector output_blinds; @@ -158,7 +159,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) BOOST_CHECK(!tx4.vout[0].nValue.IsAmount()); BOOST_CHECK(tx4.vout[1].nValue.IsAmount()); BOOST_CHECK(!tx4.vout[2].nValue.IsAmount()); - BOOST_CHECK(cache.VerifyAmounts(tx4, tx4.nTxFee)); + BOOST_CHECK(VerifyAmounts(cache, tx4, tx4.nTxFee)); #ifdef ENABLE_WALLET //This tests the wallet blinding caching functionality @@ -213,7 +214,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) in4->vout[2] = tx4.vout[2]; tx4.nTxFee--; - BOOST_CHECK(!cache.VerifyAmounts(tx4, tx4.nTxFee)); + BOOST_CHECK(!VerifyAmounts(cache, tx4, tx4.nTxFee)); } } diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 6176d9783d..ef95fd5f26 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -343,7 +343,7 @@ BOOST_AUTO_TEST_CASE(test_Get) t1.nTxFee = (50+21+22)*CENT - 90*CENT; BOOST_CHECK(AreInputsStandard(t1, coins)); - BOOST_CHECK(coins.VerifyAmounts(t1, t1.nTxFee)); + BOOST_CHECK(VerifyAmounts(coins, t1, t1.nTxFee)); } void CreateCreditAndSpend(const CKeyStore& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true) diff --git a/src/validation.cpp b/src/validation.cpp index 3c80739ea1..2a88bfdcec 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -10,6 +10,7 @@ #include "chainparams.h" #include "checkpoints.h" #include "checkqueue.h" +#include "coins.h" #include "consensus/consensus.h" #include "consensus/merkle.h" #include "consensus/validation.h" @@ -46,6 +47,8 @@ #include #include #include +#include +#include #if defined(NDEBUG) # error "Bitcoin cannot be compiled without assertions." @@ -542,6 +545,106 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe return true; } +//static Secp256k1Ctx init_context_on_load; +//extern secp256k1_context* secp256k1_bitcoin_verify_context; + +static secp256k1_context* secp256k1_ctx_verify_amounts = NULL; + +class Secp256k1Ctx +{ +public: + Secp256k1Ctx() { + assert(secp256k1_ctx_verify_amounts == NULL); + secp256k1_ctx_verify_amounts = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + secp256k1_pedersen_context_initialize(secp256k1_ctx_verify_amounts); + secp256k1_rangeproof_context_initialize(secp256k1_ctx_verify_amounts); + assert(secp256k1_ctx_verify_amounts != NULL); + } + + ~Secp256k1Ctx() { + assert(secp256k1_ctx_verify_amounts != NULL); + secp256k1_context_destroy(secp256k1_ctx_verify_amounts); + secp256k1_ctx_verify_amounts = NULL; + } +}; + + + +bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess) +{ + CAmount nPlainAmount = excess; + std::vector vchData; + std::vector vpchCommitsIn, vpchCommitsOut; + bool fNullRangeproof = false; + vchData.resize(CTxOutValue::nCommitmentSize * (tx.vin.size() + tx.vout.size())); + unsigned char *p = vchData.data(); + if (!tx.IsCoinBase()) + { + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const CTxOutValue& val = cache.GetOutputFor(tx.vin[i]).nValue; + if (val.IsAmount()) { + nPlainAmount -= val.GetAmount(); + if (!MoneyRange(val.GetAmount()) || (!MoneyRange(nPlainAmount) && !MoneyRange(-nPlainAmount))) + return false; + } + else + { + assert(val.vchCommitment.size() == CTxOutValue::nCommitmentSize); + memcpy(p, &val.vchCommitment[0], CTxOutValue::nCommitmentSize); + vpchCommitsIn.push_back(p); + p += CTxOutValue::nCommitmentSize; + } + } + } + for (size_t i = 0; i < tx.vout.size(); ++i) + { + const CTxOutValue& val = tx.vout[i].nValue; + assert(val.vchCommitment.size() == CTxOutValue::nCommitmentSize); + if (val.vchNonceCommitment.size() > CTxOutValue::nCommitmentSize || val.vchRangeproof.size() > 5000) + return false; + if (val.IsAmount()) { + nPlainAmount += val.GetAmount(); + if (!MoneyRange(val.GetAmount()) || (!MoneyRange(nPlainAmount) && !MoneyRange(-nPlainAmount))) + return false; + } + else + { + memcpy(p, &val.vchCommitment[0], CTxOutValue::nCommitmentSize); + vpchCommitsOut.push_back(p); + p += CTxOutValue::nCommitmentSize; + + if (val.vchRangeproof.empty()) + fNullRangeproof = true; + } + } + + // If there are no encrypted input or output values, we can do simple math + if (vpchCommitsIn.size() + vpchCommitsOut.size() == 0) + return (nPlainAmount == 0); + + if (!secp256k1_pedersen_verify_tally(secp256k1_ctx_verify_amounts, vpchCommitsIn.data(), vpchCommitsIn.size(), vpchCommitsOut.data(), vpchCommitsOut.size(), nPlainAmount)) + return false; + + // Rangeproof is optional in this case + if ((!vpchCommitsIn.empty()) && vpchCommitsOut.size() == 1 && nPlainAmount <= 0 && fNullRangeproof) + return true; + + uint64_t min_value, max_value; + for (size_t i = 0; i < tx.vout.size(); ++i) + { + const CTxOutValue& val = tx.vout[i].nValue; + if (val.IsAmount()) + continue; + if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &val.vchCommitment[0], val.vchRangeproof.data(), val.vchRangeproof.size())) + return false; + } + + return true; +} + + + void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age) { int expired = pool.Expire(GetTime() - age); if (expired != 0) @@ -1583,7 +1686,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins if (!MoneyRange(nFees)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange"); - if (!inputs.VerifyAmounts(tx, nTxFee)) + if (!VerifyAmounts(inputs, tx, nTxFee)) return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false, strprintf("value in (%s) < value out", FormatMoney(nValueIn))); @@ -2261,7 +2364,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus()); if (!MoneyRange(blockReward)) return state.DoS(100, error("ConnectBlock(): total block reward overflowed"), REJECT_INVALID, "bad-blockreward-outofrange"); - if (!view.VerifyAmounts(*(block.vtx[0]), -blockReward)) + if (!VerifyAmounts(view, *(block.vtx[0]), -blockReward)) return state.DoS(100, error("ConnectBlock(): coinbase pays too much (limit=%d)", blockReward), diff --git a/src/validation.h b/src/validation.h index 18bc3bb0dc..43198e5b4d 100644 --- a/src/validation.h +++ b/src/validation.h @@ -406,6 +406,17 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins } // namespace Consensus +/** + * Verify the transaction's outputs spend exactly what its inputs provide, plus some excess amount. + * + * @param[in] view CCoinsViewCache to find necessary outputs + * @param[in] tx transaction for which we are checking totals + * @param[in] excess additional amount to consider as input value (eg fees), can be negative + * @return True if totals are identical +*/ +bool VerifyAmounts(const CCoinsViewCache& cache, const CTransaction& tx, const CAmount& excess); + + /** * Check if transaction is final and can be included in a block with the * specified height and time. Consensus critical.