Skip to content

Commit

Permalink
BatchVerify: Add basic schnorr sig batch verification in ConnectBlock()
Browse files Browse the repository at this point in the history
  • Loading branch information
fjahr committed Feb 27, 2024
1 parent f9a71e3 commit f2bf48a
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 8 deletions.
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ BITCOIN_CORE_H = \
attributes.h \
banman.h \
base58.h \
batchverify.h \
bech32.h \
bip324.h \
blockencodings.h \
Expand Down Expand Up @@ -678,6 +679,7 @@ libbitcoin_common_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_common_a_SOURCES = \
addresstype.cpp \
base58.cpp \
batchverify.cpp \
bech32.cpp \
chainparams.cpp \
coins.cpp \
Expand Down
47 changes: 47 additions & 0 deletions src/batchverify.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <batchverify.h>
#include <logging.h>
#include <pubkey.h>
#include <random.h>
#include <sync.h>

#include <secp256k1.h>
#include <secp256k1_batch.h>
#include <secp256k1_schnorrsig_batch.h>

BatchSchnorrVerifier::BatchSchnorrVerifier() {
unsigned char rnd[16];
GetRandBytes(rnd);
// This is the maximum number of scalar-point pairs on the batch for which
// Strauss' algorithm, which is used in the secp256k1 implementation, is
// still efficient.
const size_t max_batch_size{106};
secp256k1_batch* batch{secp256k1_batch_create(secp256k1_context_static, max_batch_size, rnd)};
m_batch = batch;
}

BatchSchnorrVerifier::~BatchSchnorrVerifier() {
(void)secp256k1_batch_destroy(secp256k1_context_static, m_batch);
}

bool BatchSchnorrVerifier::Add(const Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) {
LOCK(m_batch_mutex);
if (secp256k1_batch_usable(secp256k1_context_static, m_batch) == 0) {
LogPrintf("ERROR: BatchSchnorrVerifier m_batch unusable\n");
return false;
}

secp256k1_xonly_pubkey pubkey_parsed;
(void)secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey_parsed, pubkey.data());

return secp256k1_batch_add_schnorrsig(secp256k1_context_static, m_batch, sig.data(), sighash.begin(), 32, &pubkey_parsed);
}

bool BatchSchnorrVerifier::Verify() {
LOCK(m_batch_mutex);
return secp256k1_batch_verify(secp256k1_context_static, m_batch);
}
27 changes: 27 additions & 0 deletions src/batchverify.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_BATCHVERIFY_H
#define BITCOIN_BATCHVERIFY_H

#include <pubkey.h>
#include <sync.h>

#include <secp256k1_batch.h>

class BatchSchnorrVerifier {
private:
secp256k1_batch* m_batch GUARDED_BY(m_batch_mutex);
mutable Mutex m_batch_mutex;

public:
BatchSchnorrVerifier();
~BatchSchnorrVerifier();

bool Add(const Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) EXCLUSIVE_LOCKS_REQUIRED(!m_batch_mutex);
bool Verify() EXCLUSIVE_LOCKS_REQUIRED(!m_batch_mutex);
};

#endif // BITCOIN_BATCHVERIFY_H
15 changes: 15 additions & 0 deletions src/script/sigcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <script/sigcache.h>

#include <batchverify.h>
#include <common/system.h>
#include <logging.h>
#include <pubkey.h>
Expand Down Expand Up @@ -127,3 +128,17 @@ bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsig
if (store) signatureCache.Set(entry);
return true;
}

bool BatchingCachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
{
uint256 entry;
signatureCache.ComputeEntrySchnorr(entry, sighash, sig, pubkey);
if (signatureCache.Get(entry, !GetStore())) return true;

return m_batch->Add(sig, pubkey, sighash);
}

bool BatchingCachingTransactionSignatureChecker::VerifySchnorrSignatureBatch() const
{
return m_batch->Verify();
}
15 changes: 15 additions & 0 deletions src/script/sigcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#ifndef BITCOIN_SCRIPT_SIGCACHE_H
#define BITCOIN_SCRIPT_SIGCACHE_H

#include <batchverify.h>
#include <script/interpreter.h>
#include <span.h>
#include <util/hasher.h>
Expand All @@ -26,6 +27,8 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker
bool store;

public:
bool GetStore() const { return store; }

CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {}

bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
Expand All @@ -34,4 +37,16 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker

[[nodiscard]] bool InitSignatureCache(size_t max_size_bytes);

class BatchingCachingTransactionSignatureChecker : public CachingTransactionSignatureChecker
{
private:
BatchSchnorrVerifier* m_batch;

public:
BatchingCachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn, BatchSchnorrVerifier* batchIn) : CachingTransactionSignatureChecker(txToIn, nInIn, amountIn, storeIn, txdataIn), m_batch(batchIn) {}

bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
bool VerifySchnorrSignatureBatch() const;
};

#endif // BITCOIN_SCRIPT_SIGCACHE_H
3 changes: 2 additions & 1 deletion src/test/txvalidationcache_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct Dersig100Setup : public TestChain100Setup {
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
std::vector<CScriptCheck>* pvChecks) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
std::vector<CScriptCheck>* pvChecks,
BatchSchnorrVerifier* batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main);

BOOST_AUTO_TEST_SUITE(txvalidationcache_tests)

Expand Down
25 changes: 20 additions & 5 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <validation.h>

#include <arith_uint256.h>
#include <batchverify.h>
#include <chain.h>
#include <checkqueue.h>
#include <clientversion.h>
Expand Down Expand Up @@ -136,7 +137,8 @@ const CBlockIndex* Chainstate::FindForkInGlobalIndex(const CBlockLocator& locato
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
std::vector<CScriptCheck>* pvChecks = nullptr)
std::vector<CScriptCheck>* pvChecks = nullptr,
BatchSchnorrVerifier* batch = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);

bool CheckFinalTxAtTip(const CBlockIndex& active_chain_tip, const CTransaction& tx)
Expand Down Expand Up @@ -1861,6 +1863,9 @@ void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txund
bool CScriptCheck::operator()() {
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
if (m_batch) {
return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, BatchingCachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata, m_batch), &error);
}
return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata), &error);
}

Expand Down Expand Up @@ -1908,7 +1913,8 @@ bool InitScriptExecutionCache(size_t max_size_bytes)
bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
const CCoinsViewCache& inputs, unsigned int flags, bool cacheSigStore,
bool cacheFullScriptStore, PrecomputedTransactionData& txdata,
std::vector<CScriptCheck>* pvChecks)
std::vector<CScriptCheck>* pvChecks,
BatchSchnorrVerifier* batch)
{
if (tx.IsCoinBase()) return true;

Expand Down Expand Up @@ -1952,7 +1958,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
// spent being checked as a part of CScriptCheck.

// Verify signature
CScriptCheck check(txdata.m_spent_outputs[i], tx, i, flags, cacheSigStore, &txdata);
CScriptCheck check(txdata.m_spent_outputs[i], tx, i, flags, cacheSigStore, &txdata, batch);
if (pvChecks) {
pvChecks->emplace_back(std::move(check));
} else if (!check()) {
Expand All @@ -1966,7 +1972,7 @@ bool CheckInputScripts(const CTransaction& tx, TxValidationState& state,
// non-upgraded nodes by banning CONSENSUS-failing
// data providers.
CScriptCheck check2(txdata.m_spent_outputs[i], tx, i,
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata, batch);
if (check2())
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
}
Expand Down Expand Up @@ -2391,6 +2397,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
int nInputs = 0;
int64_t nSigOpsCost = 0;
blockundo.vtxundo.reserve(block.vtx.size() - 1);

BatchSchnorrVerifier batch{};

for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const CTransaction &tx = *(block.vtx[i]);
Expand Down Expand Up @@ -2442,7 +2451,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
std::vector<CScriptCheck> vChecks;
bool fCacheResults = fJustCheck; /* Don't cache results if we're actually connecting blocks (still consult the cache, though) */
TxValidationState tx_state;
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr)) {
if (fScriptChecks && !CheckInputScripts(tx, tx_state, view, flags, fCacheResults, fCacheResults, txsdata[i], parallel_script_checks ? &vChecks : nullptr, &batch)) {
// Any transaction validation failure in ConnectBlock is a block consensus failure
state.Invalid(BlockValidationResult::BLOCK_CONSENSUS,
tx_state.GetRejectReason(), tx_state.GetDebugMessage());
Expand Down Expand Up @@ -2476,6 +2485,12 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state,
LogPrintf("ERROR: %s: CheckQueue failed\n", __func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-validation-failed");
}

if (!batch.Verify()) {
LogPrintf("ERROR: %s: Batch verification failed\n", __func__);
return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "block-batch-verify-failed");
}

const auto time_4{SteadyClock::now()};
time_verify += time_4 - time_2;
LogPrint(BCLog::BENCH, " - Verify %u txins: %.2fms (%.3fms/txin) [%.2fs (%.2fms/blk)]\n", nInputs - 1,
Expand Down
6 changes: 4 additions & 2 deletions src/validation.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <arith_uint256.h>
#include <attributes.h>
#include <batchverify.h>
#include <chain.h>
#include <checkqueue.h>
#include <kernel/chain.h>
Expand Down Expand Up @@ -339,10 +340,11 @@ class CScriptCheck
bool cacheStore;
ScriptError error{SCRIPT_ERR_UNKNOWN_ERROR};
PrecomputedTransactionData *txdata;
BatchSchnorrVerifier* m_batch;

public:
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), txdata(txdataIn) { }
CScriptCheck(const CTxOut& outIn, const CTransaction& txToIn, unsigned int nInIn, unsigned int nFlagsIn, bool cacheIn, PrecomputedTransactionData* txdataIn, BatchSchnorrVerifier* batchIn = nullptr) :
m_tx_out(outIn), ptxTo(&txToIn), nIn(nInIn), nFlags(nFlagsIn), cacheStore(cacheIn), txdata(txdataIn), m_batch(batchIn) { }

CScriptCheck(const CScriptCheck&) = delete;
CScriptCheck& operator=(const CScriptCheck&) = delete;
Expand Down

0 comments on commit f2bf48a

Please sign in to comment.