diff options
| author | Bitflash <btf01@proton.me> | 2026-07-28 15:27:57 -0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-07-28 15:27:57 -0300 |
| commit | 389e9966758125f4e20d059a02396e1577b6367c (patch) | |
| tree | 6a32feac1c5549eeab1e4b3557f50929e7244d6c /src | |
| parent | 63764708903cf93476b0fa43580e23ff75ffceba (diff) | |
main: cap signature operations per block (#20)
MAX_SIZE bounds a block's bytes, not what it costs to check. Every
OP_CHECKSIG is an elliptic-curve verification, and blocks may be 32 MB
here, so one packed with signature operations takes every node minutes of
CPU to reject -- while at this chain's difficulty it stays cheap to mine.
There was no counter at all.
CheckBlock now sums signature operations across the block and rejects
past 20000, the figure Bitcoin settled on. OP_CHECKMULTISIG counts as 20
since it may verify that many.
Consensus change. Real blocks here carry one or two operations each --
coinbase pay-to-pubkey and standard pay-to-pubkey-hash spends -- so the
ceiling is four orders of magnitude above anything in the chain, but it
still needs verifying against the full history before merge.
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cpp | 10 | ||||
| -rw-r--r-- | src/main.h | 15 | ||||
| -rw-r--r-- | src/script.h | 23 |
3 files changed, 48 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp index 17e446e..a75bf54 100644 --- a/src/main.cpp +++ b/src/main.cpp | |||
| @@ -1261,6 +1261,16 @@ bool CBlock::CheckBlock() const | |||
| 1261 | if (!tx.CheckTransaction()) | 1261 | if (!tx.CheckTransaction()) |
| 1262 | return error("CheckBlock() : CheckTransaction failed"); | 1262 | return error("CheckBlock() : CheckTransaction failed"); |
| 1263 | 1263 | ||
| 1264 | // Cap the work a block can demand. MAX_SIZE bounds the bytes, not the cost: | ||
| 1265 | // every signature operation is an elliptic-curve verification, and at this | ||
| 1266 | // chain's difficulty a block packed with them is affordable to mine and | ||
| 1267 | // expensive for everyone else to reject. | ||
| 1268 | unsigned int nSigOps = 0; | ||
| 1269 | foreach(const CTransaction& tx, vtx) | ||
| 1270 | nSigOps += tx.GetSigOpCount(); | ||
| 1271 | if (nSigOps > MAX_BLOCK_SIGOPS) | ||
| 1272 | return error("CheckBlock() : out-of-bounds signature operation count"); | ||
| 1273 | |||
| 1264 | // Reject blocks carrying the same transaction twice (CVE-2012-2459). | 1274 | // Reject blocks carrying the same transaction twice (CVE-2012-2459). |
| 1265 | // | 1275 | // |
| 1266 | // BuildMerkleTree duplicates the last hash when a level has an odd width, | 1276 | // BuildMerkleTree duplicates the last hash when a level has an odd width, |
| @@ -18,6 +18,11 @@ static const unsigned int MAX_SIZE = 0x02000000; | |||
| 18 | // Ceiling on transactions held waiting for a parent that has not arrived. | 18 | // Ceiling on transactions held waiting for a parent that has not arrived. |
| 19 | // They cost a peer nothing to send and are never validated, only stored. | 19 | // They cost a peer nothing to send and are never validated, only stored. |
| 20 | static const unsigned int MAX_ORPHAN_TRANSACTIONS = 100; | 20 | static const unsigned int MAX_ORPHAN_TRANSACTIONS = 100; |
| 21 | // Signature operations allowed in one block. Size alone does not bound what a | ||
| 22 | // block costs to validate: every OP_CHECKSIG is an elliptic-curve verification, | ||
| 23 | // so a block within the size limit could still hold millions of them and take | ||
| 24 | // the network minutes of CPU to reject. Same value Bitcoin settled on. | ||
| 25 | static const unsigned int MAX_BLOCK_SIGOPS = 20000; | ||
| 21 | static const int64 COIN = 100000000; | 26 | static const int64 COIN = 100000000; |
| 22 | // Total Bitflash emission: identical to Bitcoin (21 million). | 27 | // Total Bitflash emission: identical to Bitcoin (21 million). |
| 23 | // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in | 28 | // MAX_MONEY guards against the value overflow bug (CVE-2010-5139), which in |
| @@ -503,6 +508,16 @@ public: | |||
| 503 | return (vin.size() == 1 && vin[0].prevout.IsNull()); | 508 | return (vin.size() == 1 && vin[0].prevout.IsNull()); |
| 504 | } | 509 | } |
| 505 | 510 | ||
| 511 | unsigned int GetSigOpCount() const | ||
| 512 | { | ||
| 513 | unsigned int n = 0; | ||
| 514 | foreach(const CTxIn& txin, vin) | ||
| 515 | n += txin.scriptSig.GetSigOpCount(); | ||
| 516 | foreach(const CTxOut& txout, vout) | ||
| 517 | n += txout.scriptPubKey.GetSigOpCount(); | ||
| 518 | return n; | ||
| 519 | } | ||
| 520 | |||
| 506 | bool CheckTransaction() const | 521 | bool CheckTransaction() const |
| 507 | { | 522 | { |
| 508 | // Basic checks that don't depend on any context | 523 | // Basic checks that don't depend on any context |
diff --git a/src/script.h b/src/script.h index 27c1b13..26bb65a 100644 --- a/src/script.h +++ b/src/script.h | |||
| @@ -536,6 +536,29 @@ public: | |||
| 536 | } | 536 | } |
| 537 | 537 | ||
| 538 | 538 | ||
| 539 | // Signature operations in this script. Each one costs every validating | ||
| 540 | // node an elliptic-curve verification, so blocks are capped on this count | ||
| 541 | // and not only on their size -- a byte of OP_CHECKSIG is far more | ||
| 542 | // expensive to check than a byte of anything else. | ||
| 543 | unsigned int GetSigOpCount() const | ||
| 544 | { | ||
| 545 | unsigned int n = 0; | ||
| 546 | const_iterator pc = begin(); | ||
| 547 | opcodetype opcode; | ||
| 548 | vector<unsigned char> vchPushValue; | ||
| 549 | while (pc < end()) | ||
| 550 | { | ||
| 551 | if (!GetOp(pc, opcode, vchPushValue)) | ||
| 552 | break; | ||
| 553 | if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY) | ||
| 554 | n++; | ||
| 555 | else if (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY) | ||
| 556 | n += 20; | ||
| 557 | } | ||
| 558 | return n; | ||
| 559 | } | ||
| 560 | |||
| 561 | |||
| 539 | void FindAndDelete(const CScript& b) | 562 | void FindAndDelete(const CScript& b) |
| 540 | { | 563 | { |
| 541 | iterator pc = begin(); | 564 | iterator pc = begin(); |