diff --git a/src/checkqueue.h b/src/checkqueue.h index 978e23a7c4..68c4d78407 100644 --- a/src/checkqueue.h +++ b/src/checkqueue.h @@ -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 queue; + std::vector 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 vChecks; + std::vector 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& vChecks) + void Add(const std::vector vChecks) { boost::unique_lock 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 class CCheckQueueControl { private: - CCheckQueue * const pqueue; + CCheckQueue* const pqueue; bool fDone; public: CCheckQueueControl() = delete; CCheckQueueControl(const CCheckQueueControl&) = delete; CCheckQueueControl& operator=(const CCheckQueueControl&) = delete; - explicit CCheckQueueControl(CCheckQueue * const pqueueIn) : pqueue(pqueueIn), fDone(false) + explicit CCheckQueueControl(CCheckQueue* 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& vChecks) + void Add(std::vector vChecks) { if (pqueue != nullptr) pqueue->Add(vChecks); diff --git a/src/confidential_validation.cpp b/src/confidential_validation.cpp index c06b489ec3..39a73bca71 100644 --- a/src/confidential_validation.cpp +++ b/src/confidential_validation.cpp @@ -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& inputs, const CTransaction& tx, std::vector* checks, const bool store_result) { + assert(!tx.IsCoinBase()); + assert(inputs.size() == tx.vin.size()); + + std::vector vData; + std::vector 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 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 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); +} diff --git a/src/confidential_validation.h b/src/confidential_validation.h index b7f26d0b7d..5a667a89e3 100644 --- a/src/confidential_validation.h +++ b/src/confidential_validation.h @@ -14,6 +14,12 @@ #include +// 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* queue, CCheck* check); +bool VerifyAmounts(const std::vector& inputs, const CTransaction& tx, std::vector* pvChecks, const bool cacheStore); + +bool VerifyCoinbaseAmount(const CTransaction& tx, const CAmountMap& mapFees); + #endif // BITCOIN_CONFIDENTIAL_VALIDATION_H diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index 5fbf5153d3..d3a711d3e1 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -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>& setPeginsSpent) +bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set>& setPeginsSpent, std::vector *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 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 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 diff --git a/src/consensus/tx_verify.h b/src/consensus/tx_verify.h index 2830535fdc..767531e88e 100644 --- a/src/consensus/tx_verify.h +++ b/src/consensus/tx_verify.h @@ -7,6 +7,7 @@ #include +#include #include #include #include @@ -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>& setPeginsSpent); +bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoinsViewCache& inputs, int nSpendHeight, CAmountMap& fee_map, std::set>& setPeginsSpent, std::vector *pvChecks, const bool cacheStore, bool fScriptChecks); } // namespace Consensus /** Auxiliary functions for transaction validation (ideally should not be exposed) */ diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 8d244ee823..70d9fd2a07 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -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 diff --git a/src/init.cpp b/src/init.cpp index 10a8f63191..0a8c2c9616 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1279,6 +1279,9 @@ bool AppInitMain() InitSignatureCache(); InitScriptExecutionCache(); + InitRangeproofCache(); + InitSurjectionproofCache(); + LogPrintf("Using %u threads for script verification\n", nScriptCheckThreads); if (nScriptCheckThreads) { diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp index 33844a54ec..408957a6bc 100644 --- a/src/script/bitcoinconsensus.cpp +++ b/src/script/bitcoinconsensus.cpp @@ -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); } diff --git a/src/script/bitcoinconsensus.h b/src/script/bitcoinconsensus.h index c5dceac848..bb4f1e5143 100644 --- a/src/script/bitcoinconsensus.h +++ b/src/script/bitcoinconsensus.h @@ -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); diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 372f83154f..01be38e85a 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -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 +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 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 -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 diff --git a/src/script/interpreter.h b/src/script/interpreter.h index a27789fc9e..8801bd52af 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -125,7 +125,7 @@ bool CheckSignatureEncoding(const std::vector &vchSig, unsigned i struct PrecomputedTransactionData { - uint256 hashPrevouts, hashSequence, hashOutputs; + uint256 hashPrevouts, hashSequence, hashOutputs, hashIssuance; bool ready = false; template @@ -143,7 +143,7 @@ static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32; static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20; template -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& 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& scriptSig, const std::vector& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override; bool CheckLockTime(const CScriptNum& nLockTime) const override; bool CheckSequence(const CScriptNum& nSequence) const override; diff --git a/src/script/ismine.cpp b/src/script/ismine.cpp index 746a4ecb71..1673a88c11 100644 --- a/src/script/ismine.cpp +++ b/src/script/ismine.cpp @@ -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(); diff --git a/src/script/script.h b/src/script/script.h index bb7a0fe12c..c6abd04e4c 100644 --- a/src/script/script.h +++ b/src/script/script.h @@ -20,6 +20,9 @@ #include #include +// 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() diff --git a/src/script/script_error.h b/src/script/script_error.h index 400f63ff0f..e04d437019 100644 --- a/src/script/script_error.h +++ b/src/script/script_error.h @@ -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 diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp index 68f0542294..f9e1718a34 100644 --- a/src/script/sigcache.cpp +++ b/src/script/sigcache.cpp @@ -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& proof, const std::vector& 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& proof, const std::vector& 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>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& vchRangeProof, const std::vector& vchValueCommitment, const std::vector& 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& vTags, secp256k1_generator& gen, const secp256k1_context* secp256k1_ctx_verify_amounts, const uint256& wtxid) const +{ + + // Serialize proof + std::vector 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(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 +// diff --git a/src/script/sigcache.h b/src/script/sigcache.h index 807b61b542..a9014aa614 100644 --- a/src/script/sigcache.h +++ b/src/script/sigcache.h @@ -8,6 +8,9 @@ #include