[BROKEN] Add CA validation

This commit is contained in:
Steven Roose 2019-03-19 20:39:56 +00:00
parent d53479c9ff
commit 0b5066143d
No known key found for this signature in database
GPG key ID: 7FC91380BB4CE800
23 changed files with 712 additions and 124 deletions

View file

@ -41,7 +41,7 @@ private:
//! The queue of elements to be processed.
//! As the order of booleans doesn't matter, it is used as a LIFO (stack)
std::vector<T> queue;
std::vector<T*> queue;
//! The number of workers (including the master) that are idle.
int nIdle;
@ -66,7 +66,7 @@ private:
bool Loop(bool fMaster = false)
{
boost::condition_variable& cond = fMaster ? condMaster : condWorker;
std::vector<T> vChecks;
std::vector<T*> vChecks;
vChecks.reserve(nBatchSize);
unsigned int nNow = 0;
bool fOk = true;
@ -105,20 +105,18 @@ private:
// * Try to account for idle jobs which will instantly start helping.
// * Don't do batches smaller than 1 (duh), or larger than nBatchSize.
nNow = std::max(1U, std::min(nBatchSize, (unsigned int)queue.size() / (nTotal + nIdle + 1)));
vChecks.resize(nNow);
for (unsigned int i = 0; i < nNow; i++) {
// We want the lock on the mutex to be as short as possible, so swap jobs from the global
// queue to the local batch vector instead of copying.
vChecks[i].swap(queue.back());
queue.pop_back();
}
vChecks.clear();
vChecks.insert(vChecks.end(), queue.end() - nNow, queue.end());
queue.resize(queue.size() - nNow);
// Check whether we need to do work at all
fOk = fAllOk;
}
// execute work
for (T& check : vChecks)
for (T* check : vChecks) {
if (fOk)
fOk = check();
fOk = (*check)();
delete check;
}
vChecks.clear();
} while (true);
}
@ -143,13 +141,10 @@ public:
}
//! Add a batch of checks to the queue
void Add(std::vector<T>& vChecks)
void Add(const std::vector<T*> vChecks)
{
boost::unique_lock<boost::mutex> lock(mutex);
for (T& check : vChecks) {
queue.push_back(T());
check.swap(queue.back());
}
queue.insert(queue.end(), vChecks.begin(), vChecks.end());
nTodo += vChecks.size();
if (vChecks.size() == 1)
condWorker.notify_one();
@ -159,6 +154,7 @@ public:
~CCheckQueue()
{
assert(queue.empty());
}
};
@ -171,14 +167,14 @@ template <typename T>
class CCheckQueueControl
{
private:
CCheckQueue<T> * const pqueue;
CCheckQueue<T>* const pqueue;
bool fDone;
public:
CCheckQueueControl() = delete;
CCheckQueueControl(const CCheckQueueControl&) = delete;
CCheckQueueControl& operator=(const CCheckQueueControl&) = delete;
explicit CCheckQueueControl(CCheckQueue<T> * const pqueueIn) : pqueue(pqueueIn), fDone(false)
explicit CCheckQueueControl(CCheckQueue<T>* const pqueueIn) : pqueue(pqueueIn), fDone(false)
{
// passed queue is supposed to be unused, or nullptr
if (pqueue != nullptr) {
@ -195,7 +191,7 @@ public:
return fRet;
}
void Add(std::vector<T>& vChecks)
void Add(std::vector<T*> vChecks)
{
if (pqueue != nullptr)
pqueue->Add(vChecks);

View file

@ -24,6 +24,30 @@ public:
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;
}
}
return MoneyRange(totalFee);
}
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;
@ -105,3 +129,297 @@ static bool VerifyIssuanceAmount(secp256k1_pedersen_commitment& value_commit, se
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());
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);
}

View file

@ -14,6 +14,12 @@
#include <uint256.h>
// Check if explicit TX fees overflow or are negative
bool HasValidFee(const CTransaction& tx);
// Compute the fee from the explicit fee outputs. Must call HasValidFee first
CAmountMap GetFeeMap(const CTransaction& tx);
/**
* ELEMENTS:
* Closure representing one verification, either script or range checks.
@ -83,5 +89,9 @@ public:
ScriptError QueueCheck(std::vector<CCheck*>* queue, CCheck* check);
bool VerifyAmounts(const std::vector<CTxOut>& inputs, const CTransaction& tx, std::vector<CCheck*>* pvChecks, const bool cacheStore);
bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees);
#endif // BITCOIN_CONFIDENTIAL_VALIDATION_H

View file

@ -190,15 +190,19 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
return state.DoS(100, false, REJECT_INVALID, "bad-txns-oversize");
// Check for negative or overflow output values
CAmount nValueOut = 0;
CAmount nValueOutExplicit = 0;
for (const auto& txout : tx.vout)
{
if (txout.nValue < 0)
if (!txout.nValue.IsValid())
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-amount-invalid");
if (!txout.nValue.IsExplicit())
continue;
if (txout.nValue.GetAmount() < 0)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-negative");
if (txout.nValue > MAX_MONEY)
if (txout.nValue.GetAmount() > MAX_MONEY)
return state.DoS(100, false, REJECT_INVALID, "bad-txns-vout-toolarge");
nValueOut += txout.nValue;
if (!MoneyRange(nValueOut))
nValueOutExplicit += txout.nValue.GetAmount();
if (!MoneyRange(nValueOutExplicit))
return state.DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
@ -216,6 +220,12 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
{
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, false, REJECT_INVALID, "bad-cb-length");
for (unsigned int i = 0; i < tx.vout.size(); i++) {
if (tx.vout[i].IsFee()) {
return state.DoS(100, false, REJECT_INVALID, "bad-cb-fee");
}
}
}
else
{
@ -228,7 +238,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state, bool fChe
}
namespace Consensus {
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent)
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks)
{
// are the actual inputs available?
if (!inputs.HaveInputs(tx)) {
@ -236,6 +246,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
strprintf("%s: inputs missing/spent", __func__));
}
std::vector<CTxOut> spent_inputs;
CAmount nValueIn = 0;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
const COutPoint &prevout = tx.vin[i].prevout;
@ -243,7 +254,7 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
// Check existence and validity of pegin witness
std::string err;
if (tx.witness.vtxinwit.size() <= i || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, prevout, err, true)) {
return state.DoS(0, false, REJECT_PEGIN, "bad-pegin-witness");
return state.DoS(0, false, REJECT_PEGIN, "bad-pegin-witness", false, err);
}
std::pair<uint256, COutPoint> pegin = std::make_pair(uint256(tx.witness.vtxinwit[i].m_pegin_witness.stack[2]), prevout);
if (inputs.IsPeginSpent(pegin)) {
@ -256,11 +267,12 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
setPeginsSpent.insert(pegin);
// Tally the input amount.
const CTxOut out = GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness);
if (!MoneyRange(out.nValue)) {
spent_inputs.push_back(GetPeginOutputFromWitness(tx.witness.vtxinwit[i].m_pegin_witness));
const CTxOut& out = spent_inputs.back();
nValueIn += out.nValue.GetAmount(); // Non-explicit already filtered by IsValidPeginWitness
if (!MoneyRange(out.nValue.GetAmount())) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-pegin-inputvalue-outofrange");
}
nValueIn += out.nValue;
} else {
const Coin& coin = inputs.AccessCoin(prevout);
assert(!coin.IsSpent());
@ -271,29 +283,40 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
REJECT_INVALID, "bad-txns-premature-spend-of-coinbase",
strprintf("tried to spend coinbase at depth %d", nSpendHeight - coin.nHeight));
}
// Check for negative or overflow input values
nValueIn += coin.out.nValue;
if (!MoneyRange(coin.out.nValue) || !MoneyRange(nValueIn)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputvalues-outofrange");
spent_inputs.push_back(coin.out);
if (coin.out.nValue.IsExplicit()) {
nValueIn += coin.out.nValue.GetAmount();
}
}
}
//TODO(rebase) you need to replace these two blocks with the `VerifyAmounts` and `HasValidFee` methods
const CAmount value_out = tx.GetValueOut();
if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
if (g_con_elementsmode) {
// Tally transaction fees
if (!HasValidFee(tx)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
// Verify that amounts add up.
if (fScriptChecks && !VerifyAmounts(spent_inputs, tx, pvChecks, cacheStore)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-ne-out", false, "value in != value out");
}
fee_map += GetFeeMap(tx);
} else {
const CAmount value_out = tx.GetValueOutMap()[CAsset()];
if (nValueIn < value_out) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-in-belowout", false,
strprintf("value in (%s) < value out (%s)", FormatMoney(nValueIn), FormatMoney(value_out)));
}
// Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
fee_map[CAsset()] += txfee_aux;
}
// Tally transaction fees
const CAmount txfee_aux = nValueIn - value_out;
if (!MoneyRange(txfee_aux)) {
return state.DoS(100, false, REJECT_INVALID, "bad-txns-fee-outofrange");
}
txfee = txfee_aux;
return true;
}
}// namespace Consensus

View file

@ -7,6 +7,7 @@
#include <amount.h>
#include <confidential_validation.h>
#include <set>
#include <stdint.h>
#include <vector>
@ -27,10 +28,10 @@ namespace Consensus {
/**
* Check whether all inputs of this transaction are valid (no double spends and amounts)
* This does not modify the UTXO set. This does not check scripts and sigs.
* @param[out] txfee Set to the transaction fee if successful.
* @param[out] fee_map Set to the transaction fee if successful.
* Preconditions: tx.IsCoinBase() is false.
*/
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmount& txfee, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent);
bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set<std::pair<uint256, COutPoint>>& setPeginsSpent, std::vector<CCheck*> *pvChecks, const bool cacheStore, bool fScriptChecks);
} // namespace Consensus
/** Auxiliary functions for transaction validation (ideally should not be exposed) */

View file

@ -108,11 +108,14 @@ static inline int64_t GetTransactionInputWeight(const CTransaction& tx, const si
{
// scriptWitness size is added here because witnesses and txins are split up in segwit serialization.
assert(tx.witness.vtxinwit.size() > nIn);
//TODO(rebase) only count CA/CT witnesses when g_con_elementsmode is true
// ELEMENTS: This is only used for change size calculation in wallet, assert if
// anything is unexpected for this call e.g. issuances, rangeproofs
assert(tx.witness.vtxinwit[nIn].vchIssuanceAmountRangeproof.empty());
assert(tx.witness.vtxinwit[nIn].vchInflationKeysRangeproof.empty());
return ::GetSerializeSize(tx.vin[nIn], PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1)
+ ::GetSerializeSize(tx.vin[nIn], PROTOCOL_VERSION)
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].scriptWitness.stack, PROTOCOL_VERSION)
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].m_pegin_witness.stack, PROTOCOL_VERSION);
+ ::GetSerializeSize(tx.witness.vtxinwit[nIn].scriptWitness.stack, PROTOCOL_VERSION);
}
#endif // BITCOIN_CONSENSUS_VALIDATION_H

View file

@ -1279,6 +1279,9 @@ bool AppInitMain()
InitSignatureCache();
InitScriptExecutionCache();
InitRangeproofCache();
InitSurjectionproofCache();
LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads);
if (nScriptCheckThreads) {

View file

@ -76,7 +76,7 @@ static bool verify_flags(unsigned int flags)
return (flags & ~(bitcoinconsensus_SCRIPT_FLAGS_VERIFY_ALL)) == 0;
}
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CAmount amount,
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CConfidentialValue amount,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
@ -102,12 +102,20 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
}
}
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
{
CAmount am(amount);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
try {
TxInputStream stream(SER_NETWORK, PROTOCOL_VERSION, amount, amountLen);
CConfidentialValue am;
stream >> am;
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
}
@ -119,7 +127,7 @@ int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned i
return set_error(err, bitcoinconsensus_ERR_AMOUNT_REQUIRED);
}
CAmount am(0);
CConfidentialValue am(0);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, nIn, flags, err);
}

View file

@ -68,7 +68,8 @@ EXPORT_SYMBOL int bitcoinconsensus_verify_script(const unsigned char *scriptPubK
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
EXPORT_SYMBOL int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen,
const unsigned char *amount, unsigned int amountLen,
const unsigned char *txTo , unsigned int txToLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err);

View file

@ -1519,6 +1519,11 @@ public:
::Serialize(s, (int)0);
else
::Serialize(s, txTo.vin[nInput].nSequence);
// Serialize the asset issuance object
if (!txTo.vin[nInput].assetIssuance.IsNull()) {
assert(g_con_elementsmode);
::Serialize(s, txTo.vin[nInput].assetIssuance);
}
}
/** Serialize an output of txTo */
@ -1571,6 +1576,19 @@ uint256 GetSequenceHash(const T& txTo)
return ss.GetHash();
}
template <class T>
uint256 GetIssuanceHash(const T& txTo)
{
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txin : txTo.vin) {
if (txin.assetIssuance.IsNull())
ss << (unsigned char)0;
else
ss << txin.assetIssuance;
}
return ss.GetHash();
}
template <class T>
uint256 GetOutputsHash(const T& txTo)
{
@ -1590,6 +1608,7 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo)
if (txTo.HasWitness()) {
hashPrevouts = GetPrevoutHash(txTo);
hashSequence = GetSequenceHash(txTo);
hashIssuance = GetIssuanceHash(txTo);
hashOutputs = GetOutputsHash(txTo);
ready = true;
}
@ -1600,13 +1619,14 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
{
assert(nIn < txTo.vin.size());
if (sigversion == SigVersion::WITNESS_V0) {
uint256 hashPrevouts;
uint256 hashSequence;
uint256 hashIssuance;
uint256 hashOutputs;
const bool cacheready = cache && cache->ready;
@ -1618,6 +1638,9 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
hashSequence = cacheready ? cache->hashSequence : GetSequenceHash(txTo);
}
if (!(nHashType & SIGHASH_ANYONECANPAY)) {
hashIssuance = cacheready ? cache->hashIssuance : GetIssuanceHash(txTo);
}
if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo);
@ -1633,13 +1656,24 @@ uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn
// Input prevouts/nSequence (none/all, depending on flags)
ss << hashPrevouts;
ss << hashSequence;
if (g_con_elementsmode) {
ss << hashIssuance;
}
// The input being signed (replacing the scriptSig with scriptCode + amount)
// The prevout may already be contained in hashPrevout, and the nSequence
// may already be contain in hashSequence.
ss << txTo.vin[nIn].prevout;
ss << scriptCode;
ss << amount;
if (g_con_elementsmode) {
ss << amount;
} else {
ss << amount.GetAmount();
}
ss << txTo.vin[nIn].nSequence;
if (!txTo.vin[nIn].assetIssuance.IsNull()) {
assert(g_con_elementsmode);
ss << txTo.vin[nIn].assetIssuance;
}
// Outputs (none/one/all, depending on flags)
ss << hashOutputs;
// Locktime

View file

@ -125,7 +125,7 @@ bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned i
struct PrecomputedTransactionData
{
uint256 hashPrevouts, hashSequence, hashOutputs;
uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance;
bool ready = false;
template <class T>
@ -143,7 +143,7 @@ static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32;
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CConfidentialValue& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
class BaseSignatureChecker
{
@ -172,15 +172,15 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
private:
const T* txTo;
unsigned int nIn;
const CAmount amount;
const CConfidentialValue amount;
const PrecomputedTransactionData* txdata;
protected:
virtual bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
public:
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckSig(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
bool CheckSequence(const CScriptNum& nSequence) const override;

View file

@ -69,6 +69,7 @@ IsMineResult IsMineInner(const CKeyStore& keystore, const CScript& scriptPubKey,
case TX_NONSTANDARD:
case TX_NULL_DATA:
case TX_WITNESS_UNKNOWN:
case TX_FEE:
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();

View file

@ -20,6 +20,9 @@
#include <uint256.h>
#include <vector>
// IsUnspendable() compatibility
extern bool g_con_elementsmode;
// Maximum number of bytes pushable to the stack
static const unsigned int MAX_SCRIPT_ELEMENT_SIZE = 520;
@ -570,7 +573,8 @@ public:
*/
bool IsUnspendable() const
{
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE) ||
(g_con_elementsmode && size() == 0 /* Elements rule for fee outputs */);
}
void clear()

View file

@ -68,7 +68,11 @@ typedef enum ScriptError_t
SCRIPT_ERR_OP_CODESEPARATOR,
SCRIPT_ERR_SIG_FINDANDDELETE,
SCRIPT_ERR_ERROR_COUNT
SCRIPT_ERR_ERROR_COUNT,
// ELEMENTS:
SCRIPT_ERR_RANGEPROOF,
SCRIPT_ERR_PEDERSEN_TALLY
} ScriptError;
#define SCRIPT_ERR_LAST SCRIPT_ERR_ERROR_COUNT

View file

@ -41,6 +41,14 @@ public:
CSHA256().Write(nonce.begin(), 32).Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
}
// ELEMENTS:
void ComputeEntry(uint256& entry, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment) {
CSHA256().Write(nonce.begin(), nonce.size()).Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Finalize(entry.begin());
}
void ComputeEntry(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& proof, const std::vector<unsigned char>& commitment) {
CSHA256().Write(nonce.begin(), nonce.size()).Write(hash.begin(), 32).Write(proof.data(), proof.size()).Write(commitment.data(), commitment.size()).Finalize(entry.begin());
}
bool
Get(const uint256& entry, const bool erase)
{
@ -66,6 +74,11 @@ public:
* signatureCache could be made local to VerifySignature.
*/
static CSignatureCache signatureCache;
// ELEMENTS:
static CSignatureCache rangeProofCache;
static CSignatureCache surjectionProofCache;
} // namespace
// To be called once in AppInitMain/BasicTestingSetup to initialize the
@ -92,3 +105,102 @@ bool CachingTransactionSignatureChecker::VerifySignature(const std::vector<unsig
signatureCache.Set(entry);
return true;
}
//
// ELEMENTS CACHES
// To be called once in AppInit2/TestingSetup to initialize the rangeproof cache
void InitRangeproofCache()
{
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
// setup_bytes creates the minimum possible cache (2 elements).
size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
size_t nElems = rangeProofCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zu requested for rangeproof cache, able to store %zu elements\n",
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
}
// To be called once in AppInit2/TestingSetup to initialize the surjectionrproof cache
void InitSurjectionproofCache()
{
// nMaxCacheSize is unsigned. If -maxsigcachesize is set to zero,
// setup_bytes creates the minimum possible cache (2 elements).
size_t nMaxCacheSize = std::min(std::max((int64_t)0, gArgs.GetArg("-maxsigcachesize", DEFAULT_MAX_SIG_CACHE_SIZE)), MAX_MAX_SIG_CACHE_SIZE) * ((size_t) 1 << 20);
size_t nElems = surjectionProofCache.setup_bytes(nMaxCacheSize);
LogPrintf("Using %zu MiB out of %zu requested for surjectionproof cache, able to store %zu elements\n",
(nElems*sizeof(uint256)) >>20, nMaxCacheSize>>20, nElems);
}
bool CachingRangeProofChecker::VerifyRangeProof(const std::vector<unsigned char>& vchRangeProof, const std::vector<unsigned char>& vchValueCommitment, const std::vector<unsigned char>& vchAssetCommitment, const CScript& scriptPubKey, const secp256k1_context* secp256k1_ctx_verify_amounts) const
{
uint256 entry;
rangeProofCache.ComputeEntry(entry, vchRangeProof, vchValueCommitment);
if (rangeProofCache.Get(entry, !store)) {
return true;
}
if (vchRangeProof.size() == 0) {
return false;
}
uint64_t min_value, max_value;
secp256k1_pedersen_commitment commit;
if (secp256k1_pedersen_commitment_parse(secp256k1_ctx_verify_amounts, &commit, &vchValueCommitment[0]) != 1)
return false;
secp256k1_generator tag;
if (secp256k1_generator_parse(secp256k1_ctx_verify_amounts, &tag, &vchAssetCommitment[0]) != 1)
return false;
if (!secp256k1_rangeproof_verify(secp256k1_ctx_verify_amounts, &min_value, &max_value, &commit, vchRangeProof.data(), vchRangeProof.size(), scriptPubKey.size() ? &scriptPubKey.front() : NULL, scriptPubKey.size(), &tag)) {
return false;
}
// An rangeproof is not valid if the output is spendable but the minimum number
// is 0. This is to prevent people passing 0-value tokens around, or conjuring
// reissuance tokens from nothing then attempting to reissue an asset.
// ie reissuance doesn't require revealing value of reissuance output
// Issuances proofs are always "unspendable" as they commit to an empty script.
if (min_value == 0 && !scriptPubKey.IsUnspendable()) {
return false;
}
if (store) {
rangeProofCache.Set(entry);
}
return true;
}
bool CachingSurjectionProofChecker::VerifySurjectionProof(secp256k1_surjectionproof& proof, std::vector<secp256k1_generator>& vTags, secp256k1_generator& gen, const secp256k1_context* secp256k1_ctx_verify_amounts, const uint256& wtxid) const
{
// Serialize proof
std::vector<unsigned char> vchproof;
size_t proof_len = secp256k1_surjectionproof_serialized_size(secp256k1_ctx_verify_amounts, &proof);
vchproof.resize(proof_len);
assert(secp256k1_surjectionproof_serialize(secp256k1_ctx_verify_amounts, vchproof.data(), &proof_len, &proof) == 1);
// wtxid commits to all data including surj targets
// we need to specify the proof and output asset point to be unique
uint256 entry;
surjectionProofCache.ComputeEntry(entry, wtxid, vchproof, std::vector<unsigned char>(std::begin(gen.data), std::end(gen.data)));
if (surjectionProofCache.Get(entry, !store)) {
return true;
}
if (secp256k1_surjectionproof_verify(secp256k1_ctx_verify_amounts, &proof, vTags.data(), vTags.size(), &gen) != 1) {
return false;
}
if (store) {
surjectionProofCache.Set(entry);
}
return true;
}
// END ELEMENTS
//

View file

@ -8,6 +8,9 @@
#include <script/interpreter.h>
#include <secp256k1.h>
#include <secp256k1_rangeproof.h>
#include <secp256k1_surjectionproof.h>
#include <vector>
// DoS prevention: limit cache size to 32MB (over 1000000 entries on 64-bit
@ -46,11 +49,46 @@ private:
bool store;
public:
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
bool VerifySignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
};
void InitSignatureCache();
//
// ELEMENTS:
class CachingRangeProofChecker
{
private:
bool store;
public:
CachingRangeProofChecker(bool storeIn){
store = storeIn;
};
bool VerifyRangeProof(const std::vector<unsigned char>& vchRangeProof, const std::vector<unsigned char>& vchValueCommitment, const std::vector<unsigned char>& vchAssetCommitment, const CScript& scriptPubKey, const secp256k1_context* ctx) const;
};
class CachingSurjectionProofChecker
{
private:
bool store;
public:
CachingSurjectionProofChecker(bool storeIn){
store = storeIn;
};
bool VerifySurjectionProof(secp256k1_surjectionproof& proof, std::vector<secp256k1_generator>& vTags, secp256k1_generator& gen, const secp256k1_context* ctx, const uint256& wtxid) const;
};
void InitRangeproofCache();
void InitSurjectionproofCache();
// END ELEMENTS
//
#endif // BITCOIN_SCRIPT_SIGCACHE_H

View file

@ -15,7 +15,7 @@
typedef std::vector<unsigned char> valtype;
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
@ -419,7 +419,7 @@ void SignatureData::MergeSignatureData(SignatureData sigdata)
signatures.insert(std::make_move_iterator(sigdata.signatures.begin()), std::make_move_iterator(sigdata.signatures.end()));
}
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType)
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CConfidentialValue& amount, int nHashType)
{
assert(nIn < txTo.vin.size());
txTo.witness.vtxinwit.resize(txTo.vin.size());

View file

@ -82,11 +82,11 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator {
const CMutableTransaction* txTo;
unsigned int nIn;
int nHashType;
CAmount amount;
CConfidentialValue amount;
const MutableTransactionSignatureChecker checker;
public:
MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL);
MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CConfidentialValue& amountIn, int nHashTypeIn = SIGHASH_ALL);
const BaseSignatureChecker& Checker() const override { return checker; }
bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override;
};
@ -703,7 +703,7 @@ struct PartiallySignedTransaction
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, SignatureData& sigdata, unsigned int additional_flags=0);
/** Produce a script signature for a transaction. */
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CConfidentialValue& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */

View file

@ -55,6 +55,7 @@ const char* GetTxnOutputType(txnouttype t)
case TX_WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
case TX_WITNESS_UNKNOWN: return "witness_unknown";
case TX_TRUE: return "true";
case TX_FEE: return "fee";
}
return nullptr;
}
@ -113,6 +114,11 @@ txnouttype Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned
return TX_TRUE;
}
// Fee outputs are for elements-style transactions only
if (g_con_elementsmode && scriptPubKey == CScript()) {
return TX_FEE;
}
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
// it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
if (scriptPubKey.IsPayToScriptHash())

View file

@ -68,6 +68,8 @@ enum txnouttype
TX_WITNESS_V0_KEYHASH,
TX_WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
TX_TRUE, // For testing purposes only
// ELEMENTS:
TX_FEE,
};
class CNoDestination {

View file

@ -404,6 +404,7 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
vTxHashes.emplace_back(tx.GetWitnessHash(), newit);
newit->vTxHashesIdx = vTxHashes.size() - 1;
// ELEMENTS:
typedef std::pair<uint256, COutPoint> PeginPair;
for(const PeginPair& it : entry.setPeginsSpent) {
std::pair<std::map<std::pair<uint256, COutPoint>, uint256>::iterator, bool> ret = mapPeginsSpentToTxid.insert(std::make_pair(it, tx.GetHash()));
@ -651,9 +652,9 @@ static void CheckInputsAndUpdateCoins(const CTxMemPoolEntry& entry, CCoinsViewCa
{
CTransaction tx = entry.GetTx();
CValidationState state;
CAmount txfee = 0;
CAmountMap fee_map;
std::set<std::pair<uint256, COutPoint> > setPeginsSpent;
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, txfee, setPeginsSpent);
bool fCheckResult = tx.IsCoinBase() || Consensus::CheckTxInputs(tx, state, mempoolDuplicate, spendheight, fee_map, setPeginsSpent, NULL, false, true);
assert(fCheckResult);
UpdateCoins(tx, mempoolDuplicate, 1000000);

View file

@ -314,7 +314,7 @@ static void FindFilesToPrune(std::set<int>& setFilesToPrune, uint64_t nPruneAfte
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs,
bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore,
PrecomputedTransactionData& txdata,
std::vector<CScriptCheck> *pvChecks = nullptr);
std::vector<CCheck*> *pvChecks = nullptr);
static FILE* OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
bool CheckFinalTx(const CTransaction &tx, int flags)
@ -709,8 +709,8 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
if (!CheckSequenceLocks(tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
return state.DoS(0, false, REJECT_NONSTANDARD, "non-BIP68-final");
CAmount nFees = 0;
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), nFees, setPeginsSpent)) {
CAmountMap fee_map;
if (!Consensus::CheckTxInputs(tx, state, view, GetSpendHeight(view), fee_map, setPeginsSpent, NULL, true, true)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
}
@ -724,6 +724,9 @@ static bool AcceptToMemoryPoolWorker(const CChainParams& chainparams, CTxMemPool
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, view, STANDARD_SCRIPT_VERIFY_FLAGS);
// We only consider policyAsset
CAmount nFees = fee_map[policyAsset];
// nModifiedFees includes any fee deltas from PrioritiseTransaction
CAmount nModifiedFees = nFees;
pool.ApplyDelta(hash, nModifiedFees);
@ -1412,7 +1415,7 @@ void InitScriptExecutionCache() {
*
* Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp
*/
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CCheck*> *pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{
if (!tx.IsCoinBase())
{
@ -1465,11 +1468,9 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// spent being checked as a part of CScriptCheck.
// Verify signature
CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata);
if (pvChecks) {
pvChecks->push_back(CScriptCheck());
check.swap(pvChecks->back());
} else if (!check()) {
CCheck* check = new CScriptCheck(coin.out, tx, i, flags, cacheSigStore, &txdata);
ScriptError serror = QueueCheck(pvChecks, check);
if (serror != SCRIPT_ERR_OK) {
if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
// Check whether the failure was caused by a
// non-mandatory script verification check, such as
@ -1480,7 +1481,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
CScriptCheck check2(coin.out, tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
if (check2()) {
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(serror)));
}
}
// Failures of other flags indicate a transaction that is
@ -1490,7 +1491,7 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
// as to the correct behavior - we may want to continue
// peering with non-upgraded nodes even after soft-fork
// super-majority signaling has occurred.
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(serror)));
}
}
@ -1608,7 +1609,13 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, cons
undo.nHeight = alternate.nHeight;
undo.fCoinBase = alternate.fCoinBase;
} else {
return DISCONNECT_FAILED; // adding output for transaction without known metadata
// ELEMENTS:
// If we're connecting genesis outputs, it's probably actually just
// a genesis output, let it through. N.B. The case where it's a corrupted
// txundo from per-tx db will not be caught!
if (!Params().GetConsensus().connect_genesis_outputs) {
return DISCONNECT_FAILED; // adding output for transaction without known metadata
}
}
}
// The potential_overwrite parameter to AddCoin is only allowed to be false if we know for
@ -1634,6 +1641,15 @@ int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out, cons
return fClean ? DISCONNECT_OK : DISCONNECT_UNCLEAN;
}
// We don't want to compare things that are not stored in utxo db, specifically
// the nonce commitment which has no consensus meaning for spending conditions
static bool TxOutDBEntryIsSame(const CTxOut& block_txout, const CTxOut& txdb_txout)
{
return txdb_txout.nValue == block_txout.nValue &&
txdb_txout.nAsset == block_txout.nAsset &&
txdb_txout.scriptPubKey == block_txout.scriptPubKey;
}
/** Undo the effects of this block (with given index) on the UTXO set represented by coins.
* When FAILED is returned, view is left in an indeterminate state. */
DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockIndex* pindex, CCoinsViewCache& view)
@ -1664,7 +1680,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI
COutPoint out(hash, o);
Coin coin;
bool is_spent = view.SpendCoin(out, &coin);
if (!is_spent || tx.vout[o] != coin.out || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) {
if (!is_spent || !TxOutDBEntryIsSame(tx.vout[o], coin.out) || pindex->nHeight != coin.nHeight || is_coinbase != coin.fCoinBase) {
fClean = false; // transaction output mismatch
}
}
@ -1743,7 +1759,7 @@ static bool WriteUndoDataForBlock(const CBlockUndo& blockundo, CValidationState&
return true;
}
static CCheckQueue<CScriptCheck> scriptcheckqueue(128);
static CCheckQueue<CCheck> scriptcheckqueue(128);
void ThreadScriptCheck() {
RenameThread("bitcoin-scriptch");
@ -1920,7 +1936,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
const CScript& mandatory_coinbase_destination = chainparams.GetConsensus().mandatory_coinbase_destination;
if (mandatory_coinbase_destination != CScript()) {
for (auto& txout : block.vtx[0]->vout) {
if (txout.scriptPubKey != mandatory_coinbase_destination && txout.nValue != 0) {
bool mustPay = !txout.nValue.IsExplicit() || txout.nValue.GetAmount() != 0;
if (mustPay && txout.scriptPubKey != mandatory_coinbase_destination) {
return state.DoS(100, error("ConnectBlock(): Coinbase outputs didn't match required scriptPubKey"),
REJECT_INVALID, "bad-coinbase-txos");
}
@ -2061,10 +2078,10 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
CBlockUndo blockundo;
CCheckQueueControl<CScriptCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : nullptr);
CCheckQueueControl<CCheck> control(fScriptChecks && nScriptCheckThreads ? &scriptcheckqueue : nullptr);
std::vector<int> prevheights;
CAmount nFees = 0;
CAmountMap fee_map;
int nInputs = 0;
int64_t nSigOpsCost = 0;
blockundo.vtxundo.reserve(block.vtx.size() - 1);
@ -2083,16 +2100,17 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
if (!tx.IsCoinBase())
{
CAmount txfee = 0;
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, txfee,
setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent)) {
std::vector<CCheck*> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!Consensus::CheckTxInputs(tx, state, view, pindex->nHeight, fee_map,
setPeginsSpent == NULL ? setPeginsSpentDummy : *setPeginsSpent,
nScriptCheckThreads ? &vChecks : NULL, fCacheResults, fScriptChecks)) {
return error("%s: Consensus::CheckTxInputs: %s, %s", __func__, tx.GetHash().ToString(), FormatStateMessage(state));
}
nFees += txfee;
if (!MoneyRange(nFees)) {
return state.DoS(100, error("%s: accumulated fee in the block out of range.", __func__),
REJECT_INVALID, "bad-txns-accumulated-fee-outofrange");
}
control.Add(vChecks);
if (!MoneyRange(fee_map))
return state.DoS(100, error("ConnectBlock(): total block reward overflowed"), REJECT_INVALID, "bad-blockreward-outofrange");
// Check that transaction is BIP68 final
// BIP68 lock checks (as opposed to nLockTime checks) must
@ -2124,7 +2142,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
txdata.emplace_back(tx);
if (!tx.IsCoinBase())
{
std::vector<CScriptCheck> vChecks;
std::vector<CCheck*> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
if (!CheckInputs(tx, state, view, fScriptChecks, flags, fCacheResults, fCacheResults, txdata[i], nScriptCheckThreads ? &vChecks : nullptr))
return error("ConnectBlock(): CheckInputs on %s failed with %s",
@ -2137,16 +2155,19 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl
blockundo.vtxundo.push_back(CTxUndo());
}
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
}
int64_t nTime3 = GetTimeMicros(); nTimeConnect += nTime3 - nTime2;
LogPrint(BCLog::BENCH, " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs (%.2fms/blk)]\n", (unsigned)block.vtx.size(), MILLI * (nTime3 - nTime2), MILLI * (nTime3 - nTime2) / block.vtx.size(), nInputs <= 1 ? 0 : MILLI * (nTime3 - nTime2) / (nInputs-1), nTimeConnect * MICRO, nTimeConnect * MILLI / nBlocksTotal);
CAmount blockReward = nFees + GetBlockSubsidy(pindex->nHeight, chainparams.GetConsensus());
if (block.vtx[0]->GetValueOut() > blockReward)
return state.DoS(100,
error("ConnectBlock(): coinbase pays too much (actual=%d vs limit=%d)",
block.vtx[0]->GetValueOut(), blockReward),
REJECT_INVALID, "bad-cb-amount");
CAmountMap block_reward = fee_map;
block_reward[consensusParams.subsidy_asset] += GetBlockSubsidy(pindex->nHeight, consensusParams);
if (!MoneyRange(block_reward))
return state.DoS(100, error("ConnectBlock(): total block reward overflowed"), REJECT_INVALID, "bad-blockreward-outofrange");
if (!VerifyCoinbaseAmount(*(block.vtx[0]), block_reward)) {
return state.DoS(100, error("ConnectBlock(): coinbase pays too much (limit=%d)",
block_reward[consensusParams.subsidy_asset]), REJECT_INVALID, "bad-cb-amount");
}
if (!control.Wait())
return state.DoS(100, error("%s: CheckQueue failed", __func__), REJECT_INVALID, "block-validation-failed");
@ -3332,10 +3353,21 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc
std::vector<unsigned char> ret(32, 0x00);
if (consensusParams.vDeployments[Consensus::DEPLOYMENT_SEGWIT].nTimeout != 0) {
if (commitpos == -1) {
// ELEMENTS: Shim in blank coinbase output for witness output hash
// Previous iterations of CA could have allowed witness data
// in coinbase transactions, and this witness data must be committed
// to here.
//
// Is No-op in Bitcoin
CMutableTransaction tx0(*block.vtx[0]);
tx0.vout.push_back(CTxOut());
block.vtx[0] = MakeTransactionRef(std::move(tx0));
// END
uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr);
CHash256().Write(witnessroot.begin(), 32).Write(ret.data(), 32).Finalize(witnessroot.begin());
CTxOut out;
out.nValue = 0;
out.nAsset = policyAsset;
out.scriptPubKey.resize(38);
out.scriptPubKey[0] = OP_RETURN;
out.scriptPubKey[1] = 0x24;
@ -3346,7 +3378,9 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc
memcpy(&out.scriptPubKey[6], witnessroot.begin(), 32);
commitment = std::vector<unsigned char>(out.scriptPubKey.begin(), out.scriptPubKey.end());
CMutableTransaction tx(*block.vtx[0]);
tx.vout.push_back(out);
// Elements: replace shimmed output with real coinbase rather than push
tx.vout.back() = out;
// END
block.vtx[0] = MakeTransactionRef(std::move(tx));
}
}
@ -3536,8 +3570,9 @@ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, c
return state.DoS(100, false, REJECT_INVALID, "bad-witness-nonce-size", true, strprintf("%s : invalid witness reserved value size", __func__));
}
CHash256().Write(hashWitness.begin(), 32).Write(&block.vtx[0]->witness.vtxinwit[0].scriptWitness.stack[0][0], 32).Finalize(hashWitness.begin());
uint256 committedWitness(std::vector<unsigned char>(&block.vtx[0]->vout[commitpos].scriptPubKey[6], &block.vtx[0]->vout[commitpos].scriptPubKey[6+32]));
if (memcmp(hashWitness.begin(), &block.vtx[0]->vout[commitpos].scriptPubKey[6], 32)) {
return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch", __func__));
return state.DoS(100, false, REJECT_INVALID, "bad-witness-merkle-match", true, strprintf("%s : witness merkle commitment mismatch: calculated: %s found commitment: %s", __func__, hashWitness.GetHex(), committedWitness.GetHex()));
}
fHaveWitness = true;
}

View file

@ -12,6 +12,7 @@
#include <amount.h>
#include <coins.h>
#include <confidential_validation.h>
#include <fs.h>
#include <protocol.h> // For CMessageHeader::MessageStartChars
#include <policy/feerate.h>
@ -365,7 +366,7 @@ bool CheckSequenceLocks(const CTransaction &tx, int flags, LockPoints* lp = null
* Closure representing one script verification
* Note that this stores references to the spending transaction
*/
class CScriptCheck
class CScriptCheck : public CCheck
{
private:
CTxOut m_tx_out;
@ -373,27 +374,14 @@ private:
unsigned int nIn;
unsigned int nFlags;
bool cacheStore;
ScriptError error;
PrecomputedTransactionData *txdata;
public:
CScriptCheck(): ptxTo(nullptr), nIn(0), nFlags(0), cacheStore(false), error(SCRIPT_ERR_UNKNOWN_ERROR) {}
CScriptCheck(): ptxTo(nullptr), nIn(0), nFlags(0), cacheStore(false) {}
CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn) :
m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), error(SCRIPT_ERR_UNKNOWN_ERROR), txdata(txdataIn) { }
m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn) { }
bool operator()();
void swap(CScriptCheck &check) {
std::swap(ptxTo, check.ptxTo);
std::swap(m_tx_out, check.m_tx_out);
std::swap(nIn, check.nIn);
std::swap(nFlags, check.nFlags);
std::swap(cacheStore, check.cacheStore);
std::swap(error, check.error);
std::swap(txdata, check.txdata);
}
ScriptError GetScriptError() const { return error; }
};
/** Initializes the script-execution cache */