From d8af6b4be7525459ad0f806bd1969151fdb3c0f6 Mon Sep 17 00:00:00 2001 From: Nicola Atzei Date: Tue, 11 Jul 2017 09:53:15 +0200 Subject: [PATCH] ScriptError: New error code for use in ScriptException. Script tests use this to assert for specific errors. This also adds MINIMALDATA tests from Bitcoin Core into script_tests.json. --- .../org/bitcoinj/core/ScriptException.java | 14 +- .../java/org/bitcoinj/core/Transaction.java | 3 +- .../org/bitcoinj/core/TransactionInput.java | 2 + .../bitcoinj/core/TransactionOutPoint.java | 4 +- .../main/java/org/bitcoinj/script/Script.java | 261 ++++++++++-------- .../java/org/bitcoinj/script/ScriptError.java | 106 +++++++ .../org/bitcoinj/core/TransactionTest.java | 2 +- .../java/org/bitcoinj/script/ScriptTest.java | 8 +- .../org/bitcoinj/script/script_tests.json | 97 +++++++ 9 files changed, 370 insertions(+), 127 deletions(-) create mode 100644 core/src/main/java/org/bitcoinj/script/ScriptError.java diff --git a/core/src/main/java/org/bitcoinj/core/ScriptException.java b/core/src/main/java/org/bitcoinj/core/ScriptException.java index d72bd430a..ad58dc9da 100644 --- a/core/src/main/java/org/bitcoinj/core/ScriptException.java +++ b/core/src/main/java/org/bitcoinj/core/ScriptException.java @@ -16,14 +16,24 @@ package org.bitcoinj.core; +import org.bitcoinj.script.ScriptError; + @SuppressWarnings("serial") public class ScriptException extends VerificationException { - public ScriptException(String msg) { + private final ScriptError err; + + public ScriptException(ScriptError err, String msg) { super(msg); + this.err = err; } - public ScriptException(String msg, Exception e) { + public ScriptException(ScriptError err, String msg, Exception e) { super(msg, e); + this.err = err; + } + + public ScriptError getError() { + return err; } } diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index 14ab317c0..4e18bd78c 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -21,6 +21,7 @@ import org.bitcoinj.core.TransactionConfidence.ConfidenceType; import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.script.ScriptError; import org.bitcoinj.script.ScriptOpCodes; import org.bitcoinj.signers.TransactionSigner; import org.bitcoinj.utils.ExchangeRate; @@ -827,7 +828,7 @@ public class Transaction extends ChildMessage { else if (scriptPubKey.isSentToAddress()) input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); else - throw new ScriptException("Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); return input; } diff --git a/core/src/main/java/org/bitcoinj/core/TransactionInput.java b/core/src/main/java/org/bitcoinj/core/TransactionInput.java index b915b6dd3..36db65cdd 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionInput.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionInput.java @@ -18,6 +18,7 @@ package org.bitcoinj.core; import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptError; import org.bitcoinj.wallet.DefaultRiskAnalysis; import org.bitcoinj.wallet.KeyBag; import org.bitcoinj.wallet.RedeemData; @@ -186,6 +187,7 @@ public class TransactionInput extends ChildMessage { public Address getFromAddress() throws ScriptException { if (isCoinBase()) { throw new ScriptException( + ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "This is a coinbase transaction which generates new coins. It does not have a from address."); } return getScriptSig().getFromAddress(params); diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java index 1e0b9f953..5392f72ab 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java @@ -148,7 +148,7 @@ public class TransactionOutPoint extends ChildMessage { byte[] pubkeyBytes = connectedScript.getPubKey(); return keyBag.findKeyFromPubKey(pubkeyBytes); } else { - throw new ScriptException("Could not understand form of connected output script: " + connectedScript); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Could not understand form of connected output script: " + connectedScript); } } @@ -174,7 +174,7 @@ public class TransactionOutPoint extends ChildMessage { byte[] scriptHash = connectedScript.getPubKeyHash(); return keyBag.findRedeemDataFromScriptHash(scriptHash); } else { - throw new ScriptException("Could not understand form of connected output script: " + connectedScript); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Could not understand form of connected output script: " + connectedScript); } } diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java index 83aef3db4..a1fd2cf0d 100644 --- a/core/src/main/java/org/bitcoinj/script/Script.java +++ b/core/src/main/java/org/bitcoinj/script/Script.java @@ -2,6 +2,7 @@ * Copyright 2011 Google Inc. * Copyright 2012 Matt Corallo. * Copyright 2014 Andreas Schildbach + * Copyright 2017 Nicola Atzei * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -190,16 +191,16 @@ public class Script { // Read some bytes of data, where how many is the opcode value itself. dataToRead = opcode; } else if (opcode == OP_PUSHDATA1) { - if (bis.available() < 1) throw new ScriptException("Unexpected end of script"); + if (bis.available() < 1) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script"); dataToRead = bis.read(); } else if (opcode == OP_PUSHDATA2) { // Read a short, then read that many bytes of data. - if (bis.available() < 2) throw new ScriptException("Unexpected end of script"); + if (bis.available() < 2) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script"); dataToRead = bis.read() | (bis.read() << 8); } else if (opcode == OP_PUSHDATA4) { // Read a uint32, then read that many bytes of data. // Though this is allowed, because its value cannot be > 520, it should never actually be used - if (bis.available() < 4) throw new ScriptException("Unexpected end of script"); + if (bis.available() < 4) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script"); dataToRead = ((long)bis.read()) | (((long)bis.read()) << 8) | (((long)bis.read()) << 16) | (((long)bis.read()) << 24); } @@ -208,7 +209,7 @@ public class Script { chunk = new ScriptChunk(opcode, null, startLocationInProgram); } else { if (dataToRead > bis.available()) - throw new ScriptException("Push of data element that is larger than remaining data"); + throw new ScriptException(ScriptError.SCRIPT_ERR_BAD_OPCODE, "Push of data element that is larger than remaining data"); byte[] data = new byte[(int)dataToRead]; checkState(dataToRead == 0 || bis.read(data, 0, (int)dataToRead) == dataToRead); chunk = new ScriptChunk(opcode, data, startLocationInProgram); @@ -273,7 +274,7 @@ public class Script { else if (isPayToScriptHash()) return chunks.get(1).data; else - throw new ScriptException("Script not in the standard scriptPubKey form"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not in the standard scriptPubKey form"); } /** @@ -286,7 +287,7 @@ public class Script { */ public byte[] getPubKey() throws ScriptException { if (chunks.size() != 2) { - throw new ScriptException("Script not of right size, expecting 2 but got " + chunks.size()); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not of right size, expecting 2 but got " + chunks.size()); } final ScriptChunk chunk0 = chunks.get(0); final byte[] chunk0data = chunk0.data; @@ -299,7 +300,7 @@ public class Script { // A large constant followed by an OP_CHECKSIG is the key. return chunk0data; } else { - throw new ScriptException("Script did not match expected form: " + this); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script did not match expected form: " + this); } } @@ -310,7 +311,7 @@ public class Script { */ public byte[] getCLTVPaymentChannelSenderPubKey() throws ScriptException { if (!isSentToCLTVPaymentChannel()) { - throw new ScriptException("Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); } return chunks.get(8).data; } @@ -322,16 +323,16 @@ public class Script { */ public byte[] getCLTVPaymentChannelRecipientPubKey() throws ScriptException { if (!isSentToCLTVPaymentChannel()) { - throw new ScriptException("Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); } return chunks.get(1).data; } public BigInteger getCLTVPaymentChannelExpiry() { if (!isSentToCLTVPaymentChannel()) { - throw new ScriptException("Script not a standard CHECKLOCKTIMEVERIFY transaction: " + this); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMEVERIFY transaction: " + this); } - return castToBigInteger(chunks.get(4).data, 5); + return castToBigInteger(chunks.get(4).data, 5, false); } /** @@ -366,7 +367,7 @@ public class Script { else if (forcePayToPubKey && isSentToRawPubKey()) return ECKey.fromPublicOnly(getPubKey()).toAddress(params); else - throw new ScriptException("Cannot cast this script to a pay-to-address type"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to a pay-to-address type"); } ////////////////////// Interface for writing scripts from scratch //////////////////////////////// @@ -454,7 +455,7 @@ public class Script { checkArgument(redeemScript != null, "Redeem script required to create P2SH input script"); return ScriptBuilder.createP2SHMultiSigInputScript(null, redeemScript); } else { - throw new ScriptException("Do not understand script type: " + this); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Do not understand script type: " + this); } } @@ -522,7 +523,7 @@ public class Script { */ public List getPubKeys() { if (!isSentToMultiSig()) - throw new ScriptException("Only usable for multisig scripts."); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Only usable for multisig scripts."); ArrayList result = Lists.newArrayList(); int numKeys = Script.decodeFromOpN(chunks.get(chunks.size() - 2).opcode); @@ -800,7 +801,7 @@ public class Script { } return false; } - + /** * Cast a script chunk to a BigInteger. * @@ -808,10 +809,8 @@ public class Script { * sizes. * @throws ScriptException if the chunk is longer than 4 bytes. */ - private static BigInteger castToBigInteger(byte[] chunk) throws ScriptException { - if (chunk.length > 4) - throw new ScriptException("Script attempted to use an integer larger than 4 bytes"); - return Utils.decodeMPI(Utils.reverseBytes(chunk), false); + private static BigInteger castToBigInteger(byte[] chunk, final boolean requireMinimal) throws ScriptException { + return castToBigInteger(chunk, 4, requireMinimal); } /** @@ -820,12 +819,32 @@ public class Script { * the normal maximum length does not apply (i.e. CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY). * * @param maxLength the maximum length in bytes. + * @param requireMinimal check if the number is encoded with the minimum possible number of bytes * @throws ScriptException if the chunk is longer than the specified maximum. */ - private static BigInteger castToBigInteger(final byte[] chunk, final int maxLength) throws ScriptException { + private static BigInteger castToBigInteger(final byte[] chunk, final int maxLength, final boolean requireMinimal) throws ScriptException { if (chunk.length > maxLength) - throw new ScriptException("Script attempted to use an integer larger than " - + maxLength + " bytes"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script attempted to use an integer larger than " + maxLength + " bytes"); + + if (requireMinimal && chunk.length > 0) { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if ((chunk[chunk.length - 1] & 0x7f) == 0) { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if (chunk.length <= 1 || (chunk[chunk.length - 2] & 0x80) == 0) { + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "non-minimally encoded script number"); + } + } + } + return Utils.decodeMPI(Utils.reverseBytes(chunk), false); } @@ -875,31 +894,39 @@ public class Script { if (!shouldExecute) continue; + // Check minimal push + if (verifyFlags.contains(VerifyFlag.MINIMALDATA) && !chunk.isShortestPossiblePushData()) + throw new ScriptException(ScriptError.SCRIPT_ERR_MINIMALDATA, "Script included a not minimal push operation."); + stack.add(new byte[] {}); } else if (!chunk.isOpCode()) { if (chunk.data.length > MAX_SCRIPT_ELEMENT_SIZE) - throw new ScriptException("Attempted to push a data string larger than 520 bytes"); + throw new ScriptException(ScriptError.SCRIPT_ERR_PUSH_SIZE, "Attempted to push a data string larger than 520 bytes"); if (!shouldExecute) continue; + // Check minimal push + if (verifyFlags.contains(VerifyFlag.MINIMALDATA) && !chunk.isShortestPossiblePushData()) + throw new ScriptException(ScriptError.SCRIPT_ERR_MINIMALDATA, "Script included a not minimal push operation."); + stack.add(chunk.data); } else { int opcode = chunk.opcode; if (opcode > OP_16) { opCount++; if (opCount > 201) - throw new ScriptException("More script operations than is allowed"); + throw new ScriptException(ScriptError.SCRIPT_ERR_OP_COUNT, "More script operations than is allowed"); } if (opcode == OP_VERIF || opcode == OP_VERNOTIF) - throw new ScriptException("Script included OP_VERIF or OP_VERNOTIF"); + throw new ScriptException(ScriptError.SCRIPT_ERR_BAD_OPCODE, "Script included OP_VERIF or OP_VERNOTIF"); if (opcode == OP_CAT || opcode == OP_SUBSTR || opcode == OP_LEFT || opcode == OP_RIGHT || opcode == OP_INVERT || opcode == OP_AND || opcode == OP_OR || opcode == OP_XOR || opcode == OP_2MUL || opcode == OP_2DIV || opcode == OP_MUL || opcode == OP_DIV || opcode == OP_MOD || opcode == OP_LSHIFT || opcode == OP_RSHIFT) - throw new ScriptException("Script included a disabled Script Op."); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISABLED_OPCODE, "Script included a disabled Script Op."); switch (opcode) { case OP_IF: @@ -908,7 +935,7 @@ public class Script { continue; } if (stack.size() < 1) - throw new ScriptException("Attempted OP_IF on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNBALANCED_CONDITIONAL, "Attempted OP_IF on an empty stack"); ifStack.add(castToBool(stack.pollLast())); continue; case OP_NOTIF: @@ -917,17 +944,17 @@ public class Script { continue; } if (stack.size() < 1) - throw new ScriptException("Attempted OP_NOTIF on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNBALANCED_CONDITIONAL, "Attempted OP_NOTIF on an empty stack"); ifStack.add(!castToBool(stack.pollLast())); continue; case OP_ELSE: if (ifStack.isEmpty()) - throw new ScriptException("Attempted OP_ELSE without OP_IF/NOTIF"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNBALANCED_CONDITIONAL, "Attempted OP_ELSE without OP_IF/NOTIF"); ifStack.add(!ifStack.pollLast()); continue; case OP_ENDIF: if (ifStack.isEmpty()) - throw new ScriptException("Attempted OP_ENDIF without OP_IF/NOTIF"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNBALANCED_CONDITIONAL, "Attempted OP_ENDIF without OP_IF/NOTIF"); ifStack.pollLast(); continue; } @@ -962,31 +989,31 @@ public class Script { break; case OP_VERIFY: if (stack.size() < 1) - throw new ScriptException("Attempted OP_VERIFY on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_VERIFY on an empty stack"); if (!castToBool(stack.pollLast())) - throw new ScriptException("OP_VERIFY failed"); + throw new ScriptException(ScriptError.SCRIPT_ERR_VERIFY, "OP_VERIFY failed"); break; case OP_RETURN: - throw new ScriptException("Script called OP_RETURN"); + throw new ScriptException(ScriptError.SCRIPT_ERR_OP_RETURN, "Script called OP_RETURN"); case OP_TOALTSTACK: if (stack.size() < 1) - throw new ScriptException("Attempted OP_TOALTSTACK on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_TOALTSTACK on an empty stack"); altstack.add(stack.pollLast()); break; case OP_FROMALTSTACK: if (altstack.size() < 1) - throw new ScriptException("Attempted OP_FROMALTSTACK on an empty altstack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_ALTSTACK_OPERATION, "Attempted OP_FROMALTSTACK on an empty altstack"); stack.add(altstack.pollLast()); break; case OP_2DROP: if (stack.size() < 2) - throw new ScriptException("Attempted OP_2DROP on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_2DROP on a stack with size < 2"); stack.pollLast(); stack.pollLast(); break; case OP_2DUP: if (stack.size() < 2) - throw new ScriptException("Attempted OP_2DUP on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_2DUP on a stack with size < 2"); Iterator it2DUP = stack.descendingIterator(); byte[] OP2DUPtmpChunk2 = it2DUP.next(); stack.add(it2DUP.next()); @@ -994,7 +1021,7 @@ public class Script { break; case OP_3DUP: if (stack.size() < 3) - throw new ScriptException("Attempted OP_3DUP on a stack with size < 3"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_3DUP on a stack with size < 3"); Iterator it3DUP = stack.descendingIterator(); byte[] OP3DUPtmpChunk3 = it3DUP.next(); byte[] OP3DUPtmpChunk2 = it3DUP.next(); @@ -1004,7 +1031,7 @@ public class Script { break; case OP_2OVER: if (stack.size() < 4) - throw new ScriptException("Attempted OP_2OVER on a stack with size < 4"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_2OVER on a stack with size < 4"); Iterator it2OVER = stack.descendingIterator(); it2OVER.next(); it2OVER.next(); @@ -1014,7 +1041,7 @@ public class Script { break; case OP_2ROT: if (stack.size() < 6) - throw new ScriptException("Attempted OP_2ROT on a stack with size < 6"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_2ROT on a stack with size < 6"); byte[] OP2ROTtmpChunk6 = stack.pollLast(); byte[] OP2ROTtmpChunk5 = stack.pollLast(); byte[] OP2ROTtmpChunk4 = stack.pollLast(); @@ -1030,7 +1057,7 @@ public class Script { break; case OP_2SWAP: if (stack.size() < 4) - throw new ScriptException("Attempted OP_2SWAP on a stack with size < 4"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_2SWAP on a stack with size < 4"); byte[] OP2SWAPtmpChunk4 = stack.pollLast(); byte[] OP2SWAPtmpChunk3 = stack.pollLast(); byte[] OP2SWAPtmpChunk2 = stack.pollLast(); @@ -1042,7 +1069,7 @@ public class Script { break; case OP_IFDUP: if (stack.size() < 1) - throw new ScriptException("Attempted OP_IFDUP on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_IFDUP on an empty stack"); if (castToBool(stack.getLast())) stack.add(stack.getLast()); break; @@ -1051,24 +1078,24 @@ public class Script { break; case OP_DROP: if (stack.size() < 1) - throw new ScriptException("Attempted OP_DROP on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_DROP on an empty stack"); stack.pollLast(); break; case OP_DUP: if (stack.size() < 1) - throw new ScriptException("Attempted OP_DUP on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_DUP on an empty stack"); stack.add(stack.getLast()); break; case OP_NIP: if (stack.size() < 2) - throw new ScriptException("Attempted OP_NIP on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_NIP on a stack with size < 2"); byte[] OPNIPtmpChunk = stack.pollLast(); stack.pollLast(); stack.add(OPNIPtmpChunk); break; case OP_OVER: if (stack.size() < 2) - throw new ScriptException("Attempted OP_OVER on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_OVER on a stack with size < 2"); Iterator itOVER = stack.descendingIterator(); itOVER.next(); stack.add(itOVER.next()); @@ -1076,10 +1103,10 @@ public class Script { case OP_PICK: case OP_ROLL: if (stack.size() < 1) - throw new ScriptException("Attempted OP_PICK/OP_ROLL on an empty stack"); - long val = castToBigInteger(stack.pollLast()).longValue(); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_PICK/OP_ROLL on an empty stack"); + long val = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)).longValue(); if (val < 0 || val >= stack.size()) - throw new ScriptException("OP_PICK/OP_ROLL attempted to get data deeper than stack size"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "OP_PICK/OP_ROLL attempted to get data deeper than stack size"); Iterator itPICK = stack.descendingIterator(); for (long i = 0; i < val; i++) itPICK.next(); @@ -1090,7 +1117,7 @@ public class Script { break; case OP_ROT: if (stack.size() < 3) - throw new ScriptException("Attempted OP_ROT on a stack with size < 3"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_ROT on a stack with size < 3"); byte[] OPROTtmpChunk3 = stack.pollLast(); byte[] OPROTtmpChunk2 = stack.pollLast(); byte[] OPROTtmpChunk1 = stack.pollLast(); @@ -1101,7 +1128,7 @@ public class Script { case OP_SWAP: case OP_TUCK: if (stack.size() < 2) - throw new ScriptException("Attempted OP_SWAP on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_SWAP on a stack with size < 2"); byte[] OPSWAPtmpChunk2 = stack.pollLast(); byte[] OPSWAPtmpChunk1 = stack.pollLast(); stack.add(OPSWAPtmpChunk2); @@ -1113,27 +1140,27 @@ public class Script { case OP_SUBSTR: case OP_LEFT: case OP_RIGHT: - throw new ScriptException("Attempted to use disabled Script Op."); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISABLED_OPCODE, "Attempted to use disabled Script Op."); case OP_SIZE: if (stack.size() < 1) - throw new ScriptException("Attempted OP_SIZE on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_SIZE on an empty stack"); stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.valueOf(stack.getLast().length), false))); break; case OP_INVERT: case OP_AND: case OP_OR: case OP_XOR: - throw new ScriptException("Attempted to use disabled Script Op."); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISABLED_OPCODE, "Attempted to use disabled Script Op."); case OP_EQUAL: if (stack.size() < 2) - throw new ScriptException("Attempted OP_EQUAL on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_EQUAL on a stack with size < 2"); stack.add(Arrays.equals(stack.pollLast(), stack.pollLast()) ? new byte[] {1} : new byte[] {}); break; case OP_EQUALVERIFY: if (stack.size() < 2) - throw new ScriptException("Attempted OP_EQUALVERIFY on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_EQUALVERIFY on a stack with size < 2"); if (!Arrays.equals(stack.pollLast(), stack.pollLast())) - throw new ScriptException("OP_EQUALVERIFY: non-equal data"); + throw new ScriptException(ScriptError.SCRIPT_ERR_EQUALVERIFY, "OP_EQUALVERIFY: non-equal data"); break; case OP_1ADD: case OP_1SUB: @@ -1142,8 +1169,8 @@ public class Script { case OP_NOT: case OP_0NOTEQUAL: if (stack.size() < 1) - throw new ScriptException("Attempted a numeric op on an empty stack"); - BigInteger numericOPnum = castToBigInteger(stack.pollLast()); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted a numeric op on an empty stack"); + BigInteger numericOPnum = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); switch (opcode) { case OP_1ADD: @@ -1179,7 +1206,7 @@ public class Script { break; case OP_2MUL: case OP_2DIV: - throw new ScriptException("Attempted to use disabled Script Op."); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISABLED_OPCODE, "Attempted to use disabled Script Op."); case OP_ADD: case OP_SUB: case OP_BOOLAND: @@ -1193,9 +1220,9 @@ public class Script { case OP_MIN: case OP_MAX: if (stack.size() < 2) - throw new ScriptException("Attempted a numeric op on a stack with size < 2"); - BigInteger numericOPnum2 = castToBigInteger(stack.pollLast()); - BigInteger numericOPnum1 = castToBigInteger(stack.pollLast()); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted a numeric op on a stack with size < 2"); + BigInteger numericOPnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); + BigInteger numericOPnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); BigInteger numericOPresult; switch (opcode) { @@ -1276,22 +1303,22 @@ public class Script { case OP_MOD: case OP_LSHIFT: case OP_RSHIFT: - throw new ScriptException("Attempted to use disabled Script Op."); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISABLED_OPCODE, "Attempted to use disabled Script Op."); case OP_NUMEQUALVERIFY: if (stack.size() < 2) - throw new ScriptException("Attempted OP_NUMEQUALVERIFY on a stack with size < 2"); - BigInteger OPNUMEQUALVERIFYnum2 = castToBigInteger(stack.pollLast()); - BigInteger OPNUMEQUALVERIFYnum1 = castToBigInteger(stack.pollLast()); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_NUMEQUALVERIFY on a stack with size < 2"); + BigInteger OPNUMEQUALVERIFYnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); + BigInteger OPNUMEQUALVERIFYnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); if (!OPNUMEQUALVERIFYnum1.equals(OPNUMEQUALVERIFYnum2)) - throw new ScriptException("OP_NUMEQUALVERIFY failed"); + throw new ScriptException(ScriptError.SCRIPT_ERR_NUMEQUALVERIFY, "OP_NUMEQUALVERIFY failed"); break; case OP_WITHIN: if (stack.size() < 3) - throw new ScriptException("Attempted OP_WITHIN on a stack with size < 3"); - BigInteger OPWITHINnum3 = castToBigInteger(stack.pollLast()); - BigInteger OPWITHINnum2 = castToBigInteger(stack.pollLast()); - BigInteger OPWITHINnum1 = castToBigInteger(stack.pollLast()); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_WITHIN on a stack with size < 3"); + BigInteger OPWITHINnum3 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); + BigInteger OPWITHINnum2 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); + BigInteger OPWITHINnum1 = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)); if (OPWITHINnum2.compareTo(OPWITHINnum1) <= 0 && OPWITHINnum1.compareTo(OPWITHINnum3) < 0) stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.ONE, false))); else @@ -1299,7 +1326,7 @@ public class Script { break; case OP_RIPEMD160: if (stack.size() < 1) - throw new ScriptException("Attempted OP_RIPEMD160 on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_RIPEMD160 on an empty stack"); RIPEMD160Digest digest = new RIPEMD160Digest(); byte[] dataToHash = stack.pollLast(); digest.update(dataToHash, 0, dataToHash.length); @@ -1309,7 +1336,7 @@ public class Script { break; case OP_SHA1: if (stack.size() < 1) - throw new ScriptException("Attempted OP_SHA1 on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_SHA1 on an empty stack"); try { stack.add(MessageDigest.getInstance("SHA-1").digest(stack.pollLast())); } catch (NoSuchAlgorithmException e) { @@ -1318,17 +1345,17 @@ public class Script { break; case OP_SHA256: if (stack.size() < 1) - throw new ScriptException("Attempted OP_SHA256 on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_SHA256 on an empty stack"); stack.add(Sha256Hash.hash(stack.pollLast())); break; case OP_HASH160: if (stack.size() < 1) - throw new ScriptException("Attempted OP_HASH160 on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_HASH160 on an empty stack"); stack.add(Utils.sha256hash160(stack.pollLast())); break; case OP_HASH256: if (stack.size() < 1) - throw new ScriptException("Attempted OP_SHA256 on an empty stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_SHA256 on an empty stack"); stack.add(Sha256Hash.hashTwice(stack.pollLast())); break; case OP_CODESEPARATOR: @@ -1350,21 +1377,21 @@ public class Script { if (!verifyFlags.contains(VerifyFlag.CHECKLOCKTIMEVERIFY)) { // not enabled; treat as a NOP2 if (verifyFlags.contains(VerifyFlag.DISCOURAGE_UPGRADABLE_NOPS)) { - throw new ScriptException("Script used a reserved opcode " + opcode); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, "Script used a reserved opcode " + opcode); } break; } - executeCheckLockTimeVerify(txContainingThis, (int) index, stack); + executeCheckLockTimeVerify(txContainingThis, (int) index, stack, verifyFlags); break; case OP_CHECKSEQUENCEVERIFY: if (!verifyFlags.contains(VerifyFlag.CHECKSEQUENCEVERIFY)) { // not enabled; treat as a NOP3 if (verifyFlags.contains(VerifyFlag.DISCOURAGE_UPGRADABLE_NOPS)) { - throw new ScriptException("Script used a reserved opcode " + opcode); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, "Script used a reserved opcode " + opcode); } break; } - executeCheckSequenceVerify(txContainingThis, (int) index, stack); + executeCheckSequenceVerify(txContainingThis, (int) index, stack, verifyFlags); break; case OP_NOP1: case OP_NOP4: @@ -1375,46 +1402,46 @@ public class Script { case OP_NOP9: case OP_NOP10: if (verifyFlags.contains(VerifyFlag.DISCOURAGE_UPGRADABLE_NOPS)) { - throw new ScriptException("Script used a reserved opcode " + opcode); + throw new ScriptException(ScriptError.SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS, "Script used a reserved opcode " + opcode); } break; default: - throw new ScriptException("Script used a reserved opcode " + opcode); + throw new ScriptException(ScriptError.SCRIPT_ERR_BAD_OPCODE, "Script used a reserved opcode " + opcode); } } if (stack.size() + altstack.size() > 1000 || stack.size() + altstack.size() < 0) - throw new ScriptException("Stack size exceeded range"); + throw new ScriptException(ScriptError.SCRIPT_ERR_STACK_SIZE, "Stack size exceeded range"); } if (!ifStack.isEmpty()) - throw new ScriptException("OP_IF/OP_NOTIF without OP_ENDIF"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNBALANCED_CONDITIONAL, "OP_IF/OP_NOTIF without OP_ENDIF"); } // This is more or less a direct translation of the code in Bitcoin Core - private static void executeCheckLockTimeVerify(Transaction txContainingThis, int index, LinkedList stack) throws ScriptException { + private static void executeCheckLockTimeVerify(Transaction txContainingThis, int index, LinkedList stack, Set verifyFlags) throws ScriptException { if (stack.size() < 1) - throw new ScriptException("Attempted OP_CHECKLOCKTIMEVERIFY on a stack with size < 1"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKLOCKTIMEVERIFY on a stack with size < 1"); // Thus as a special case we tell CScriptNum to accept up // to 5-byte bignums to avoid year 2038 issue. - final BigInteger nLockTime = castToBigInteger(stack.getLast(), 5); + final BigInteger nLockTime = castToBigInteger(stack.getLast(), 5, verifyFlags.contains(VerifyFlag.MINIMALDATA)); if (nLockTime.compareTo(BigInteger.ZERO) < 0) - throw new ScriptException("Negative locktime"); + throw new ScriptException(ScriptError.SCRIPT_ERR_NEGATIVE_LOCKTIME, "Negative locktime"); // There are two kinds of nLockTime, need to ensure we're comparing apples-to-apples if (!( ((txContainingThis.getLockTime() < Transaction.LOCKTIME_THRESHOLD) && (nLockTime.compareTo(Transaction.LOCKTIME_THRESHOLD_BIG)) < 0) || ((txContainingThis.getLockTime() >= Transaction.LOCKTIME_THRESHOLD) && (nLockTime.compareTo(Transaction.LOCKTIME_THRESHOLD_BIG)) >= 0)) ) - throw new ScriptException("Locktime requirement type mismatch"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNSATISFIED_LOCKTIME, "Locktime requirement type mismatch"); // Now that we know we're comparing apples-to-apples, the // comparison is a simple numeric one. if (nLockTime.compareTo(BigInteger.valueOf(txContainingThis.getLockTime())) > 0) - throw new ScriptException("Locktime requirement not satisfied"); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNSATISFIED_LOCKTIME, "Locktime requirement not satisfied"); // Finally the nLockTime feature can be disabled and thus // CHECKLOCKTIMEVERIFY bypassed if every txin has been @@ -1427,12 +1454,12 @@ public class Script { // inputs, but testing just this input minimizes the data // required to prove correct CHECKLOCKTIMEVERIFY execution. if (!txContainingThis.getInput(index).hasSequence()) - throw new ScriptException("Transaction contains a final transaction input for a CHECKLOCKTIMEVERIFY script."); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNSATISFIED_LOCKTIME, "Transaction contains a final transaction input for a CHECKLOCKTIMEVERIFY script."); } - private static void executeCheckSequenceVerify(Transaction txContainingThis, int index, LinkedList stack) throws ScriptException { + private static void executeCheckSequenceVerify(Transaction txContainingThis, int index, LinkedList stack, Set verifyFlags) throws ScriptException { if (stack.size() < 1) - throw new ScriptException("Attempted OP_CHECKLOCKTIMEVERIFY on a stack with size < 1"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKSEQUENCEVERIFY on a stack with size < 1"); // Note that elsewhere numeric opcodes are limited to // operands in the range -2**31+1 to 2**31-1, however it is @@ -1443,13 +1470,13 @@ public class Script { // Thus as a special case we tell CScriptNum to accept up // to 5-byte bignums, which are good until 2**39-1, well // beyond the 2**32-1 limit of the nSequence field itself. - final long nSequence = castToBigInteger(stack.getLast(), 5).longValue(); + final long nSequence = castToBigInteger(stack.getLast(), 5, verifyFlags.contains(VerifyFlag.MINIMALDATA)).longValue(); // In the rare event that the argument may be < 0 due to // some arithmetic being done first, you can always use // 0 MAX CHECKSEQUENCEVERIFY. if (nSequence < 0) - throw new ScriptException("Negative sequence"); + throw new ScriptException(ScriptError.SCRIPT_ERR_NEGATIVE_LOCKTIME, "Negative sequence"); // To provide for future soft-fork extensibility, if the // operand has the disabled lock-time flag set, @@ -1459,8 +1486,8 @@ public class Script { // Compare the specified sequence number with the input. if (!checkSequence(nSequence, txContainingThis, index)) - throw new ScriptException("Unsatisfied CHECKLOCKTIMEVERIFY lock time"); - } + throw new ScriptException(ScriptError.SCRIPT_ERR_UNSATISFIED_LOCKTIME, "Unsatisfied CHECKLOCKTIMEVERIFY lock time"); + } private static boolean checkSequence(long nSequence, Transaction txContainingThis, int index) { // Relative lock times are supported by comparing the passed @@ -1512,7 +1539,7 @@ public class Script { || verifyFlags.contains(VerifyFlag.DERSIG) || verifyFlags.contains(VerifyFlag.LOW_S); if (stack.size() < 2) - throw new ScriptException("Attempted OP_CHECKSIG(VERIFY) on a stack with size < 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKSIG(VERIFY) on a stack with size < 2"); byte[] pubKey = stack.pollLast(); byte[] sigBytes = stack.pollLast(); @@ -1550,7 +1577,7 @@ public class Script { stack.add(sigValid ? new byte[] {1} : new byte[] {}); else if (opcode == OP_CHECKSIGVERIFY) if (!sigValid) - throw new ScriptException("Script failed OP_CHECKSIGVERIFY"); + throw new ScriptException(ScriptError.SCRIPT_ERR_CHECKSIGVERIFY, "Script failed OP_CHECKSIGVERIFY"); } private static int executeMultiSig(Transaction txContainingThis, int index, Script script, LinkedList stack, @@ -1559,16 +1586,16 @@ public class Script { final boolean requireCanonical = verifyFlags.contains(VerifyFlag.STRICTENC) || verifyFlags.contains(VerifyFlag.DERSIG) || verifyFlags.contains(VerifyFlag.LOW_S); - if (stack.size() < 2) - throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < 2"); - int pubKeyCount = castToBigInteger(stack.pollLast()).intValue(); + if (stack.size() < 1) + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < 2"); + int pubKeyCount = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)).intValue(); if (pubKeyCount < 0 || pubKeyCount > 20) - throw new ScriptException("OP_CHECKMULTISIG(VERIFY) with pubkey count out of range"); + throw new ScriptException(ScriptError.SCRIPT_ERR_PUBKEY_COUNT, "OP_CHECKMULTISIG(VERIFY) with pubkey count out of range"); opCount += pubKeyCount; if (opCount > 201) - throw new ScriptException("Total op count > 201 during OP_CHECKMULTISIG(VERIFY)"); + throw new ScriptException(ScriptError.SCRIPT_ERR_OP_COUNT, "Total op count > 201 during OP_CHECKMULTISIG(VERIFY)"); if (stack.size() < pubKeyCount + 1) - throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < num_of_pubkeys + 2"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < num_of_pubkeys + 2"); LinkedList pubkeys = new LinkedList<>(); for (int i = 0; i < pubKeyCount; i++) { @@ -1576,11 +1603,11 @@ public class Script { pubkeys.add(pubKey); } - int sigCount = castToBigInteger(stack.pollLast()).intValue(); + int sigCount = castToBigInteger(stack.pollLast(), verifyFlags.contains(VerifyFlag.MINIMALDATA)).intValue(); if (sigCount < 0 || sigCount > pubKeyCount) - throw new ScriptException("OP_CHECKMULTISIG(VERIFY) with sig count out of range"); + throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_COUNT, "OP_CHECKMULTISIG(VERIFY) with sig count out of range"); if (stack.size() < sigCount + 1) - throw new ScriptException("Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < num_of_pubkeys + num_of_signatures + 3"); + throw new ScriptException(ScriptError.SCRIPT_ERR_INVALID_STACK_OPERATION, "Attempted OP_CHECKMULTISIG(VERIFY) on a stack with size < num_of_pubkeys + num_of_signatures + 3"); LinkedList sigs = new LinkedList<>(); for (int i = 0; i < sigCount; i++) { @@ -1625,13 +1652,13 @@ public class Script { // We uselessly remove a stack object to emulate a Bitcoin Core bug. byte[] nullDummy = stack.pollLast(); if (verifyFlags.contains(VerifyFlag.NULLDUMMY) && nullDummy.length > 0) - throw new ScriptException("OP_CHECKMULTISIG(VERIFY) with non-null nulldummy: " + Arrays.toString(nullDummy)); + throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_NULLFAIL, "OP_CHECKMULTISIG(VERIFY) with non-null nulldummy: " + Arrays.toString(nullDummy)); if (opcode == OP_CHECKMULTISIG) { stack.add(valid ? new byte[] {1} : new byte[] {}); } else if (opcode == OP_CHECKMULTISIGVERIFY) { if (!valid) - throw new ScriptException("Script failed OP_CHECKMULTISIGVERIFY"); + throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_NULLFAIL, "Script failed OP_CHECKMULTISIGVERIFY"); } return opCount; } @@ -1672,7 +1699,7 @@ public class Script { throw new RuntimeException(e); // Should not happen unless we were given a totally broken transaction. } if (getProgram().length > 10000 || scriptPubKey.getProgram().length > 10000) - throw new ScriptException("Script larger than 10,000 bytes"); + throw new ScriptException(ScriptError.SCRIPT_ERR_SCRIPT_SIZE, "Script larger than 10,000 bytes"); LinkedList stack = new LinkedList<>(); LinkedList p2shStack = null; @@ -1683,10 +1710,10 @@ public class Script { executeScript(txContainingThis, scriptSigIndex, scriptPubKey, stack, verifyFlags); if (stack.size() == 0) - throw new ScriptException("Stack empty at end of script execution."); + throw new ScriptException(ScriptError.SCRIPT_ERR_EVAL_FALSE, "Stack empty at end of script execution."); if (!castToBool(stack.pollLast())) - throw new ScriptException("Script resulted in a non-true stack: " + stack); + throw new ScriptException(ScriptError.SCRIPT_ERR_EVAL_FALSE, "Script resulted in a non-true stack: " + stack); // P2SH is pay to script hash. It means that the scriptPubKey has a special form which is a valid // program but it has "useless" form that if evaluated as a normal program always returns true. @@ -1704,7 +1731,7 @@ public class Script { if (verifyFlags.contains(VerifyFlag.P2SH) && scriptPubKey.isPayToScriptHash()) { for (ScriptChunk chunk : chunks) if (chunk.isOpCode() && chunk.opcode > OP_16) - throw new ScriptException("Attempted to spend a P2SH scriptPubKey with a script that contained script ops"); + throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_PUSHONLY, "Attempted to spend a P2SH scriptPubKey with a script that contained script ops"); byte[] scriptPubKeyBytes = p2shStack.pollLast(); Script scriptPubKeyP2SH = new Script(scriptPubKeyBytes); @@ -1712,10 +1739,10 @@ public class Script { executeScript(txContainingThis, scriptSigIndex, scriptPubKeyP2SH, p2shStack, verifyFlags); if (p2shStack.size() == 0) - throw new ScriptException("P2SH stack empty at end of script execution."); + throw new ScriptException(ScriptError.SCRIPT_ERR_EVAL_FALSE, "P2SH stack empty at end of script execution."); if (!castToBool(p2shStack.pollLast())) - throw new ScriptException("P2SH script execution resulted in a non-true stack"); + throw new ScriptException(ScriptError.SCRIPT_ERR_EVAL_FALSE, "P2SH script execution resulted in a non-true stack"); } } diff --git a/core/src/main/java/org/bitcoinj/script/ScriptError.java b/core/src/main/java/org/bitcoinj/script/ScriptError.java new file mode 100644 index 000000000..cf28d767d --- /dev/null +++ b/core/src/main/java/org/bitcoinj/script/ScriptError.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017 Nicola Atzei + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.bitcoinj.script; + +import java.util.HashMap; +import java.util.Map; + +public enum ScriptError { + + SCRIPT_ERR_OK("OK"), + SCRIPT_ERR_UNKNOWN_ERROR("UNKNOWN_ERROR"), + SCRIPT_ERR_EVAL_FALSE("EVAL_FALSE"), + SCRIPT_ERR_OP_RETURN("OP_RETURN"), + + /* Max sizes */ + SCRIPT_ERR_SCRIPT_SIZE("SCRIPT_SIZE"), + SCRIPT_ERR_PUSH_SIZE("PUSH_SIZE"), + SCRIPT_ERR_OP_COUNT("OP_COUNT"), + SCRIPT_ERR_STACK_SIZE("STACK_SIZE"), + SCRIPT_ERR_SIG_COUNT("SIG_COUNT"), + SCRIPT_ERR_PUBKEY_COUNT("PUBKEY_COUNT"), + + /* Failed verify operations */ + SCRIPT_ERR_VERIFY("VERIFY"), + SCRIPT_ERR_EQUALVERIFY("EQUALVERIFY"), + SCRIPT_ERR_CHECKMULTISIGVERIFY("CHECKMULTISIGVERIFY"), + SCRIPT_ERR_CHECKSIGVERIFY("CHECKSIGVERIFY"), + SCRIPT_ERR_NUMEQUALVERIFY("NUMEQUALVERIFY"), + + /* Logical/Format/Canonical errors */ + SCRIPT_ERR_BAD_OPCODE("BAD_OPCODE"), + SCRIPT_ERR_DISABLED_OPCODE("DISABLED_OPCODE"), + SCRIPT_ERR_INVALID_STACK_OPERATION("INVALID_STACK_OPERATION"), + SCRIPT_ERR_INVALID_ALTSTACK_OPERATION("INVALID_ALTSTACK_OPERATION"), + SCRIPT_ERR_UNBALANCED_CONDITIONAL("UNBALANCED_CONDITIONAL"), + + /* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */ + SCRIPT_ERR_NEGATIVE_LOCKTIME("NEGATIVE_LOCKTIME"), + SCRIPT_ERR_UNSATISFIED_LOCKTIME("UNSATISFIED_LOCKTIME"), + + /* Malleability */ + SCRIPT_ERR_SIG_HASHTYPE("SIG_HASHTYPE"), + SCRIPT_ERR_SIG_DER("SIG_DER"), + SCRIPT_ERR_MINIMALDATA("MINIMALDATA"), + SCRIPT_ERR_SIG_PUSHONLY("SIG_PUSHONLY"), + SCRIPT_ERR_SIG_HIGH_S("SIG_HIGH_S"), + SCRIPT_ERR_SIG_NULLDUMMY("SIG_NULLDUMMY"), + SCRIPT_ERR_PUBKEYTYPE("PUBKEYTYPE"), + SCRIPT_ERR_CLEANSTACK("CLEANSTACK"), + SCRIPT_ERR_MINIMALIF("MINIMALIF"), + SCRIPT_ERR_SIG_NULLFAIL("NULLFAIL"), + + /* softfork safeness */ + SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS("DISCOURAGE_UPGRADABLE_NOPS"), + SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM("DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM"), + + /* segregated witness */ + SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH("WITNESS_PROGRAM_WRONG_LENGTH"), + SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY("WITNESS_PROGRAM_WITNESS_EMPTY"), + SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH("WITNESS_PROGRAM_MISMATCH"), + SCRIPT_ERR_WITNESS_MALLEATED("WITNESS_MALLEATED"), + SCRIPT_ERR_WITNESS_MALLEATED_P2SH("WITNESS_MALLEATED_P2SH"), + SCRIPT_ERR_WITNESS_UNEXPECTED("WITNESS_UNEXPECTED"), + SCRIPT_ERR_WITNESS_PUBKEYTYPE("WITNESS_PUBKEYTYPE"), + + SCRIPT_ERR_ERROR_COUNT("ERROR_COUNT"); + + private final String mnemonic; + private static final Map mnemonicToScriptErrorMap; + + private ScriptError(String name) { + this.mnemonic = name; + } + + static { + mnemonicToScriptErrorMap = new HashMap<>(); + for (ScriptError err : ScriptError.values()) { + mnemonicToScriptErrorMap.put(err.getMnemonic(), err); + } + } + + public String getMnemonic() { + return mnemonic; + } + + public static ScriptError fromMnemonic(String name) { + ScriptError err = mnemonicToScriptErrorMap.get(name); + if (err == null) + throw new IllegalArgumentException(name + " is not a valid name"); + return err; + } +} diff --git a/core/src/test/java/org/bitcoinj/core/TransactionTest.java b/core/src/test/java/org/bitcoinj/core/TransactionTest.java index 9807e82fa..fad13dcbc 100644 --- a/core/src/test/java/org/bitcoinj/core/TransactionTest.java +++ b/core/src/test/java/org/bitcoinj/core/TransactionTest.java @@ -299,7 +299,7 @@ public class TransactionTest { TransactionInput ti = new TransactionInput(PARAMS, tx, new byte[0]) { @Override public Script getScriptSig() throws ScriptException { - throw new ScriptException(""); + throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, ""); } }; diff --git a/core/src/test/java/org/bitcoinj/script/ScriptTest.java b/core/src/test/java/org/bitcoinj/script/ScriptTest.java index 00c862b2a..27c9739ee 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptTest.java @@ -292,19 +292,19 @@ public class ScriptTest { if (test.size() == 1) continue; // skip comment Set verifyFlags = parseVerifyFlags(test.get(2).asText()); - String expectedError = test.get(3).asText(); + ScriptError expectedError = ScriptError.fromMnemonic(test.get(3).asText()); try { Script scriptSig = parseScriptString(test.get(0).asText()); Script scriptPubKey = parseScriptString(test.get(1).asText()); Transaction txCredit = buildCreditingTransaction(scriptPubKey); Transaction txSpend = buildSpendingTransaction(txCredit, scriptSig); scriptSig.correctlySpends(txSpend, 0, scriptPubKey, verifyFlags); - if (!expectedError.equals("OK")) + if (!expectedError.equals(ScriptError.SCRIPT_ERR_OK)) fail(test + " is expected to fail"); } catch (ScriptException e) { - if (expectedError.equals("OK")) { - // TODO check for specific error + if (!e.getError().equals(expectedError)) { System.err.println(test); + e.printStackTrace(); System.err.flush(); throw e; } diff --git a/core/src/test/resources/org/bitcoinj/script/script_tests.json b/core/src/test/resources/org/bitcoinj/script/script_tests.json index ffae5ed47..171a813be 100644 --- a/core/src/test/resources/org/bitcoinj/script/script_tests.json +++ b/core/src/test/resources/org/bitcoinj/script/script_tests.json @@ -1121,6 +1121,103 @@ ["0x00", "'00' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE", "Basic OP_0 execution"], +["MINIMALDATA enforcement for PUSHDATAs"], + +["0x4c 0x00", "DROP 1", "MINIMALDATA", "MINIMALDATA", "Empty vector minimally represented by OP_0"], +["0x01 0x81", "DROP 1", "MINIMALDATA", "MINIMALDATA", "-1 minimally represented by OP_1NEGATE"], +["0x01 0x01", "DROP 1", "MINIMALDATA", "MINIMALDATA", "1 to 16 minimally represented by OP_1 to OP_16"], +["0x01 0x02", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x03", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x04", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x05", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x06", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x07", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x08", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x09", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0a", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0b", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0c", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0d", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0e", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x0f", "DROP 1", "MINIMALDATA", "MINIMALDATA"], +["0x01 0x10", "DROP 1", "MINIMALDATA", "MINIMALDATA"], + +["0x4c 0x48 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "MINIMALDATA", + "PUSHDATA1 of 72 bytes minimally represented by direct push"], + +["0x4d 0xFF00 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "MINIMALDATA", + "PUSHDATA2 of 255 bytes minimally represented by PUSHDATA1"], + +["0x4e 0x00010000 0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "MINIMALDATA", + "PUSHDATA4 of 256 bytes minimally represented by PUSHDATA2"], + +["MINIMALDATA enforcement for numeric arguments"], +["0x0000", "DROP 0 EQUAL", "MINIMALDATA,CLEANSTACK", "OK", "numequals 0"], + +["0x01 0x00", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals 0"], +["0x02 0x0000", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals 0"], +["0x01 0x80", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "0x80 (negative zero) numequals 0"], +["0x02 0x0080", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals 0"], +["0x02 0x0500", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals 5"], +["0x03 0x050000", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals 5"], +["0x02 0x0580", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals -5"], +["0x03 0x050080", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "numequals -5"], +["0x03 0xff7f80", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "Minimal encoding is 0xffff"], +["0x03 0xff7f00", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "Minimal encoding is 0xff7f"], +["0x04 0xffff7f80", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "Minimal encoding is 0xffffff"], +["0x04 0xffff7f00", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR", "Minimal encoding is 0xffff7f"], + +["Test every numeric-accepting opcode for correct handling of the numeric minimal encoding rule"], + +["1 0x02 0x0000", "PICK DROP", "MINIMALDATA", "UNKNOWN_ERROR"], +["1 0x02 0x0000", "ROLL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "1ADD DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "1SUB DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "NEGATE DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "ABS DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "NOT DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000", "0NOTEQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], + +["0 0x02 0x0000", "ADD DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "ADD DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "SUB DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "SUB DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "BOOLAND DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "BOOLAND DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "BOOLOR DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "BOOLOR DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "NUMEQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 1", "NUMEQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "NUMEQUALVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "NUMEQUALVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "NUMNOTEQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "NUMNOTEQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "LESSTHAN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "LESSTHAN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "GREATERTHAN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "GREATERTHAN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "LESSTHANOREQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "LESSTHANOREQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "GREATERTHANOREQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "GREATERTHANOREQUAL DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "MIN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "MIN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000", "MAX DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0x02 0x0000 0", "MAX DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], + +["0x02 0x0000 0 0", "WITHIN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000 0", "WITHIN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0 0x02 0x0000", "WITHIN DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], + +["0 0 0x02 0x0000", "CHECKMULTISIG DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000 0", "CHECKMULTISIG DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000 0 1", "CHECKMULTISIG DROP 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0 0x02 0x0000", "CHECKMULTISIGVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], +["0 0x02 0x0000 0", "CHECKMULTISIGVERIFY 1", "MINIMALDATA", "UNKNOWN_ERROR"], + ["CHECKSEQUENCEVERIFY tests"], ["", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "INVALID_STACK_OPERATION", "CSV automatically fails on an empty stack"], ["-1", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "NEGATIVE_LOCKTIME", "CSV automatically fails if stack top is negative"],