From 626ff2fa2cf62247f6b3c874eba3d73cfcc2f63f Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 11 Jul 2012 01:18:15 +0200 Subject: [PATCH] Verify SigOp counts when verifying Blocks. --- .../java/com/google/bitcoin/core/Block.java | 29 ++++++++++ .../java/com/google/bitcoin/core/Script.java | 57 +++++++++++++++++++ .../com/google/bitcoin/core/Transaction.java | 13 +++++ 3 files changed, 99 insertions(+) diff --git a/core/src/main/java/com/google/bitcoin/core/Block.java b/core/src/main/java/com/google/bitcoin/core/Block.java index f7660a21..d9f858dd 100644 --- a/core/src/main/java/com/google/bitcoin/core/Block.java +++ b/core/src/main/java/com/google/bitcoin/core/Block.java @@ -52,6 +52,19 @@ public class Block extends Message { static final long ALLOWED_TIME_DRIFT = 2 * 60 * 60; // Same value as official client. + /** + * A constant shared by the entire network: how large in bytes a block is allowed to be. One day we may have to + * upgrade everyone to change this, so Bitcoin can continue to grow. For now it exists as an anti-DoS measure to + * avoid somebody creating a titanically huge but valid block and forcing everyone to download/store it forever. + */ + public static final int MAX_BLOCK_SIZE = 1 * 1000 * 1000; + /** + * A "sigop" is a signature verification operation. Because they're expensive we also impose a separate limit on + * the number in a block to prevent somebody mining a huge block that has way more sigops than normal, so is very + * expensive/slow to verify. + */ + public static final int MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50; + /** A value for difficultyTarget (nBits) that allows half of all possible hash solutions. Used in unit testing. */ static final long EASIEST_DIFFICULTY_TARGET = 0x207fFFFFL; @@ -569,6 +582,21 @@ public class Block extends Message { if (time > currentTime + ALLOWED_TIME_DRIFT) throw new VerificationException("Block too far in future"); } + + private void checkSigOps() throws VerificationException { + // Check there aren't too many signature verifications in the block. This is an anti-DoS measure, see the + // comments for MAX_BLOCK_SIGOPS. + int sigOps = 0; + for (Transaction tx : transactions) { + try { + sigOps += tx.getSigOpCount(); + } catch (ScriptException e) { + throw new VerificationException("Unreadable script in transaction"); + } + } + if (sigOps > MAX_BLOCK_SIGOPS) + throw new VerificationException("Block had too many Signature Operations"); + } private void checkMerkleRoot() throws VerificationException { Sha256Hash calculatedRoot = calculateMerkleRoot(); @@ -683,6 +711,7 @@ public class Block extends Message { maybeParseTransactions(); checkTransactions(); checkMerkleRoot(); + checkSigOps(); } /** diff --git a/core/src/main/java/com/google/bitcoin/core/Script.java b/core/src/main/java/com/google/bitcoin/core/Script.java index 5c30a361..94dad397 100644 --- a/core/src/main/java/com/google/bitcoin/core/Script.java +++ b/core/src/main/java/com/google/bitcoin/core/Script.java @@ -195,6 +195,11 @@ public class Script { // The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...] List chunks; private final NetworkParameters params; + + // Only for internal use + private Script() { + params = null; + } /** * Construct a Script using the given network parameters and a range of the programBytes array. @@ -694,4 +699,56 @@ public class Script { throw new RuntimeException(e); } } + + ////////////////////// Interface used during verification of transactions/blocks //////////////////////////////// + + private static int getSigOpCount(List chunks, boolean accurate) throws ScriptException { + int sigOps = 0; + int lastOpCode = OP_INVALIDOPCODE; + for (ScriptChunk chunk : chunks) { + if (chunk.isOpCode) { + int opcode = 0xFF & chunk.data[0]; + switch (opcode) { + case OP_CHECKSIG: + case OP_CHECKSIGVERIFY: + sigOps++; + break; + case OP_CHECKMULTISIG: + case OP_CHECKMULTISIGVERIFY: + if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16) + sigOps += getOpNValue(lastOpCode); + else + sigOps += 20; + default: + break; + } + lastOpCode = opcode; + } + } + return sigOps; + } + + /** + * Convince method to get the int value of OP_N + */ + private static int getOpNValue(int opcode) throws ScriptException { + if (opcode == OP_0) + return 0; + if (opcode < OP_1 || opcode > OP_16) // This should absolutely never happen + throw new ScriptException("getOpNValue called on non OP_N opcode"); + return opcode + 1 - OP_1; + } + + /** + * Gets the count of regular SigOps in the script program (counting multisig ops as 20) + */ + public static int getSigOpCount(byte[] program) throws ScriptException { + Script script = new Script(); + try { + script.parse(program, 0, program.length); + } catch (ScriptException e) { + // Ignore errors and count up to the parse-able length + } + return getSigOpCount(script.chunks, false); + } } diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index 9f3acbec..f7209322 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -869,4 +869,17 @@ public class Transaction extends ChildMessage implements Serializable { maybeParse(); out.defaultWriteObject(); } + + /** + * Gets the count of regular SigOps in this transactions + */ + public int getSigOpCount() throws ScriptException { + maybeParse(); + int sigOps = 0; + for (TransactionInput input : inputs) + sigOps += Script.getSigOpCount(input.getScriptBytes()); + for (TransactionOutput output : outputs) + sigOps += Script.getSigOpCount(output.getScriptBytes()); + return sigOps; + } }