Skip to content

Commit 8978984

Browse files
authored
Merge pull request #7 from twostack/genesis-sync
Modular Arithmetic and Big Integers
2 parents e984534 + cf33424 commit 8978984

File tree

3 files changed

+74
-79
lines changed

3 files changed

+74
-79
lines changed

build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ plugins {
66
}
77

88
group 'org.twostack'
9-
version '1.4.2-SNAPSHOT'
9+
version '1.5.0-SNAPSHOT'
1010

1111
repositories {
1212
mavenCentral()

src/main/java/org/twostack/bitcoin4j/script/Interpreter.java

+73-34
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import javax.annotation.Nullable;
3535
import java.io.IOException;
36+
import java.math.BigDecimal;
3637
import java.math.BigInteger;
3738
import java.nio.ByteBuffer;
3839
import java.nio.ByteOrder;
@@ -50,16 +51,35 @@ public class Interpreter {
5051

5152
private static final Logger log = LoggerFactory.getLogger(Script.class);
5253

53-
// public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
54+
// Maximum script number length after Genesis
55+
//consensus.h in node client
56+
/** 1KB */
57+
public static final int ONE_KILOBYTE = 1000;
58+
59+
// public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
5460
public static final long MAX_SCRIPT_ELEMENT_SIZE = 2147483647; // 2Gigabytes after Genesis - (2^31 -1)
55-
private static final int MAX_OPS_PER_SCRIPT = 201;
61+
// private static final int MAX_OPS_PER_SCRIPT = 201;
62+
63+
// Maximum number of non-push operations per script after GENESIS
64+
// Maximum number of non-push operations per script before GENESIS
65+
private static final long MAX_OPS_PER_SCRIPT_BEFORE_GENESIS = 500;
66+
67+
// Maximum number of non-push operations per script after GENESIS
68+
private static long UINT32_MAX = 4294967295L;
69+
private static final long MAX_OPS_PER_SCRIPT_AFTER_GENESIS = UINT32_MAX;
70+
5671
private static final int MAX_STACK_SIZE = 1000;
5772
private static final int DEFAULT_MAX_NUM_ELEMENT_SIZE = 4;
5873
private static final int MAX_PUBKEYS_PER_MULTISIG = 20;
5974
private static final int MAX_SCRIPT_SIZE = 10000;
6075
public static final int SIG_SIZE = 75;
6176
/** Max number of sigops allowed in a standard p2sh redeem script */
6277
public static final int MAX_P2SH_SIGOPS = 15;
78+
79+
// Maximum script number length after Genesis
80+
public static final int MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS = 750 * ONE_KILOBYTE;
81+
82+
public static final int MAX_SCRIPT_NUM_LENGTH_BEFORE_GENESIS = 4;
6383
public static final int DEFAULT_SCRIPT_NUM_LENGTH_POLICY_AFTER_GENESIS = 250 * 1024;
6484

6585
public static final int MAX_SCRIPT_ELEMENT_SIZE_BEFORE_GENESIS = 520;
@@ -207,6 +227,9 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
207227
int opCount = 0;
208228
int lastCodeSepLocation = 0;
209229
final boolean enforceMinimal = verifyFlags.contains(VerifyFlag.MINIMALDATA);
230+
final boolean utxoAfterGenesis = verifyFlags.contains(VerifyFlag.UTXO_AFTER_GENESIS);
231+
final int maxScriptNumLength = getMaxScriptNumLength(utxoAfterGenesis);
232+
210233

211234
LinkedList<byte[]> altstack = new LinkedList<>();
212235
LinkedList<Boolean> ifStack = new LinkedList<>();
@@ -228,7 +251,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
228251
// Note how OP_RESERVED does not count towards the opcode limit.
229252
if (opcode > OP_16) {
230253
opCount++;
231-
if (opCount > DEFAULT_SCRIPT_NUM_LENGTH_POLICY_AFTER_GENESIS)
254+
if (!isValidMaxOpsPerScript(opCount, utxoAfterGenesis))
232255
throw new ScriptException(ScriptError.SCRIPT_ERR_OP_COUNT, "More script operations than is allowed");
233256
}
234257

@@ -445,7 +468,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
445468
case OP_ROLL:
446469
if (stack.size() < 1)
447470
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_PICK/OP_ROLL on an empty stack");
448-
long val = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)).longValue();
471+
long val = castToBigInteger(stack.pollLast(),maxScriptNumLength, verifyFlags.contains(VerifyFlag.MINIMALDATA)).longValue();
449472
if (val < 0 || val >= stack.size())
450473
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "OP_PICK/OP_ROLL attempted to get data deeper than stack size");
451474
Iterator<byte[]> itPICK = stack.descendingIterator();
@@ -500,7 +523,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
500523
if (stack.size() < 2)
501524
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Invalid stack operation.");
502525

503-
int numSize = castToBigInteger(stack.pollLast(), enforceMinimal).intValue();
526+
int numSize = castToBigInteger(stack.pollLast(), maxScriptNumLength,enforceMinimal).intValue();
504527

505528
if (!verifyFlags.contains(VerifyFlag.UTXO_AFTER_GENESIS) && numSize > MAX_SCRIPT_ELEMENT_SIZE_BEFORE_GENESIS)
506529
throw new ScriptException(ScriptError.SCRIPT_ERR_PUSH_SIZE, "Push value size limit exceeded.");
@@ -538,7 +561,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
538561
if (stack.size() < 2)
539562
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Invalid stack operation.");
540563

541-
BigInteger biSplitPos = castToBigInteger(stack.pollLast(), enforceMinimal);
564+
BigInteger biSplitPos = castToBigInteger(stack.pollLast(), maxScriptNumLength,enforceMinimal);
542565

543566
//sanity check in case we aren't enforcing minimal number encoding
544567
//we will check that the biSplitPos value can be safely held in an int
@@ -571,7 +594,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
571594
byte[] binBytes = stack.pollLast();
572595
byte[] numBytes = Utils.minimallyEncodeLE(binBytes);
573596

574-
if (!Utils.checkMinimallyEncodedLE(numBytes, DEFAULT_MAX_NUM_ELEMENT_SIZE))
597+
if (!Utils.checkMinimallyEncodedLE(numBytes, maxScriptNumLength))
575598
throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_NUMBER_RANGE, "Given operand is not a number within the valid range [-2^31...2^31]");
576599

577600
stack.addLast(numBytes);
@@ -705,7 +728,7 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
705728
case OP_0NOTEQUAL:
706729
if (stack.size() < 1)
707730
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted a numeric op on an empty stack");
708-
BigInteger numericOPnum = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
731+
BigInteger numericOPnum = castToBigInteger(stack.pollLast(), maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
709732

710733
switch (opcode) {
711734
case OP_1ADD:
@@ -756,8 +779,8 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
756779
case OP_MAX:
757780
if (stack.size() < 2)
758781
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted a numeric op on a stack with size < 2");
759-
BigInteger numericOPnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
760-
BigInteger numericOPnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
782+
BigInteger numericOPnum2 = castToBigInteger(stack.pollLast(),maxScriptNumLength, verifyFlags.contains(VerifyFlag.MINIMALDATA));
783+
BigInteger numericOPnum1 = castToBigInteger(stack.pollLast(),maxScriptNumLength, verifyFlags.contains(VerifyFlag.MINIMALDATA));
761784

762785
BigInteger numericOPresult;
763786
switch (opcode) {
@@ -784,23 +807,16 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
784807

785808
/**
786809
* BigInteger doesn't behave the way we want for modulo operations. Firstly it's
787-
* always garunteed to return a +ve result. Secondly it will throw an exception
788-
* if the 2nd operand is negative. So we'll convert the values to longs and use native
789-
* modulo. When we expand the number limits to arbitrary length we will likely need
790-
* a new BigNum implementation to handle this correctly.
810+
* always guaranteed to return a +ve result. Secondly it will throw an exception
811+
* if the 2nd operand is negative.
812+
* Instead we will use the BigDecimal to perform modular arithmetic, then convert
813+
* back to BigInteger
791814
*/
792-
long lOp1 = numericOPnum1.longValue();
793-
if (!BigInteger.valueOf(lOp1).equals(numericOPnum1)) {
794-
//in case the value is larger than a long can handle we need to crash and burn.
795-
throw new RuntimeException("Cannot handle large negative operand for modulo operation");
796-
}
797-
long lOp2 = numericOPnum2.longValue();
798-
if (!BigInteger.valueOf(lOp2).equals(numericOPnum2)) {
799-
//in case the value is larger than a long can handle we need to crash and burn.
800-
throw new RuntimeException("Cannot handle large negative operand for modulo operation");
801-
}
802-
long lOpResult = lOp1 % lOp2;
803-
numericOPresult = BigInteger.valueOf(lOpResult);
815+
816+
BigDecimal bd1 = new BigDecimal(numericOPnum1);
817+
BigDecimal bd2 = new BigDecimal(numericOPnum2);
818+
819+
numericOPresult = bd1.remainder(bd2).toBigInteger();
804820

805821
break;
806822

@@ -873,18 +889,18 @@ public static void executeScript(@Nullable Transaction txContainingThis, long in
873889
case OP_NUMEQUALVERIFY:
874890
if (stack.size() < 2)
875891
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_NUMEQUALVERIFY on a stack with size < 2");
876-
BigInteger OPNUMEQUALVERIFYnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
877-
BigInteger OPNUMEQUALVERIFYnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
892+
BigInteger OPNUMEQUALVERIFYnum2 = castToBigInteger(stack.pollLast(), maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
893+
BigInteger OPNUMEQUALVERIFYnum1 = castToBigInteger(stack.pollLast(), maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
878894

879895
if (!OPNUMEQUALVERIFYnum1.equals(OPNUMEQUALVERIFYnum2))
880896
throw new ScriptException(ScriptError.SCRIPT_ERR_NUMEQUALVERIFY, "OP_NUMEQUALVERIFY failed");
881897
break;
882898
case OP_WITHIN:
883899
if (stack.size() < 3)
884900
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_WITHIN on a stack with size < 3");
885-
BigInteger OPWITHINnum3 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
886-
BigInteger OPWITHINnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
887-
BigInteger OPWITHINnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA));
901+
BigInteger OPWITHINnum3 = castToBigInteger(stack.pollLast(),maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
902+
BigInteger OPWITHINnum2 = castToBigInteger(stack.pollLast(),maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
903+
BigInteger OPWITHINnum1 = castToBigInteger(stack.pollLast(),maxScriptNumLength,verifyFlags.contains(VerifyFlag.MINIMALDATA));
888904
if (OPWITHINnum2.compareTo(OPWITHINnum1) <= 0 && OPWITHINnum1.compareTo(OPWITHINnum3) < 0)
889905
stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.ONE, false)));
890906
else
@@ -1438,17 +1454,20 @@ private static int executeMultiSig(Transaction txContainingThis, int index, Scri
14381454
|| verifyFlags.contains(VerifyFlag.DERSIG)
14391455
|| verifyFlags.contains(VerifyFlag.LOW_S);
14401456
final boolean enforceMinimal = verifyFlags.contains(VerifyFlag.MINIMALDATA);
1457+
final boolean utxoAfterGenesis = verifyFlags.contains(VerifyFlag.UTXO_AFTER_GENESIS);
14411458

14421459
if (stack.size() < 1)
14431460
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < 2");
14441461

1445-
int pubKeyCount = castToBigInteger(stack.pollLast(), enforceMinimal).intValue();
1462+
int pubKeyCount = castToBigInteger(stack.pollLast(), getMaxScriptNumLength(utxoAfterGenesis), enforceMinimal).intValue();
14461463
if (pubKeyCount < 0 || (!verifyFlags.contains(VerifyFlag.UTXO_AFTER_GENESIS) && pubKeyCount > 20)
14471464
|| (verifyFlags.contains(VerifyFlag.UTXO_AFTER_GENESIS) && pubKeyCount > Integer.MAX_VALUE))
14481465
throw new ScriptException(ScriptError.SCRIPT_ERR_PUBKEY_COUNT, "OP_CHECKMULTISIG(VERIFY) with pubkey count out of range");
14491466
opCount += pubKeyCount;
1450-
if (opCount > DEFAULT_SCRIPT_NUM_LENGTH_POLICY_AFTER_GENESIS)
1467+
1468+
if (!isValidMaxOpsPerScript(opCount, utxoAfterGenesis))
14511469
throw new ScriptException(ScriptError.SCRIPT_ERR_CHECKMULTISIGVERIFY, "Total op (count > 250 * 1024) during OP_CHECKMULTISIG(VERIFY)");
1470+
14521471
if (stack.size() < pubKeyCount + 1)
14531472
throw new ScriptException(SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < num_of_pubkeys + 2");
14541473

@@ -1459,7 +1478,7 @@ private static int executeMultiSig(Transaction txContainingThis, int index, Scri
14591478
pubkeys.add(pubKey);
14601479
}
14611480

1462-
int sigCount = castToBigInteger(stack.pollLast(), enforceMinimal).intValue();
1481+
int sigCount = castToBigInteger(stack.pollLast(), getMaxScriptNumLength(utxoAfterGenesis), enforceMinimal).intValue();
14631482
if (sigCount < 0 || sigCount > pubKeyCount)
14641483
throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_COUNT, "OP_CHECKMULTISIG(VERIFY) with sig count out of range");
14651484
if (stack.size() < sigCount + 1)
@@ -1653,4 +1672,24 @@ private static void checkSequence(long nSequence, Transaction txContainingThis,
16531672
if (nSequenceMasked > txToSequenceMasked)
16541673
throw new ScriptException(ScriptError.SCRIPT_ERR_UNSATISFIED_LOCKTIME, "Relative locktime requirement not satisfied");
16551674
}
1675+
1676+
private static int getMaxScriptNumLength(boolean isGenesisEnabled) {
1677+
if (!isGenesisEnabled) {
1678+
return MAX_SCRIPT_NUM_LENGTH_BEFORE_GENESIS;
1679+
}
1680+
1681+
return MAX_SCRIPT_NUM_LENGTH_AFTER_GENESIS; // use new limit after genesis
1682+
}
1683+
1684+
private static long getMaxOpsPerScript(boolean isGenesisEnabled) {
1685+
if (!isGenesisEnabled) {
1686+
return MAX_OPS_PER_SCRIPT_BEFORE_GENESIS; // no changes before genesis
1687+
}
1688+
1689+
return MAX_OPS_PER_SCRIPT_AFTER_GENESIS; // use new limit after genesis
1690+
}
1691+
1692+
private static boolean isValidMaxOpsPerScript(int nOpCount, boolean isGenesisEnabled) {
1693+
return (nOpCount <= getMaxOpsPerScript(isGenesisEnabled));
1694+
}
16561695
}

src/test/java/org/twostack/bitcoin4j/script/ScriptTest.java

-44
Original file line numberDiff line numberDiff line change
@@ -257,50 +257,6 @@ public void dataDrivenScripts() throws Exception {
257257

258258

259259

260-
// @Test
261-
// public void dataDrivenValidScripts() throws Exception {
262-
// JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
263-
// "script_valid.json"), Charsets.UTF_8));
264-
// for (JsonNode test : json) {
265-
// Script scriptSig = parseScriptString(test.get(0).asText());
266-
// Script scriptPubKey = parseScriptString(test.get(1).asText());
267-
// Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
268-
// try {
269-
//
270-
//
271-
// Interpreter interp = new Interpreter();
272-
// interp.correctlySpends( scriptSig, scriptPubKey, new Transaction(), 0 , verifyFlags);
273-
//
274-
// } catch (ScriptException e) {
275-
// System.err.println(test);
276-
// System.err.flush();
277-
// throw e;
278-
// }
279-
// }
280-
// }
281-
//
282-
//
283-
// @Test
284-
// public void dataDrivenInvalidScripts() throws Exception {
285-
// JsonNode json = new ObjectMapper().readTree(new InputStreamReader(getClass().getResourceAsStream(
286-
// "script_invalid.json"), Charsets.UTF_8));
287-
// for (JsonNode test : json) {
288-
// try {
289-
// Script scriptSig = parseScriptString(test.get(0).asText());
290-
// Script scriptPubKey = parseScriptString(test.get(1).asText());
291-
// Set<VerifyFlag> verifyFlags = parseVerifyFlags(test.get(2).asText());
292-
//
293-
// Interpreter interp = new Interpreter();
294-
// interp.correctlySpends( scriptSig, scriptPubKey, new Transaction(), 0 , verifyFlags);
295-
//
296-
// System.err.println(test);
297-
// System.err.flush();
298-
// fail();
299-
// } catch (VerificationException e) {
300-
// // Expected.
301-
// }
302-
// }
303-
// }
304260

305261
@Test
306262
public void parseKnownAsm() throws IOException {

0 commit comments

Comments
 (0)