From 752e7006e530b3eb31e709b099aaed429804f890 Mon Sep 17 00:00:00 2001
From: Mike Hearn
Date: Wed, 24 Apr 2013 18:23:20 +0200
Subject: [PATCH] Move ScriptTest.
---
.../com/google/bitcoin/script/Script.java | 102 ++++--------------
.../google/bitcoin/script/ScriptChunk.java | 82 ++++++++++++++
.../{core => script}/script_invalid.json | 0
.../{core => script}/script_valid.json | 0
.../bitcoin/{core => script}/tx_invalid.json | 0
.../bitcoin/{core => script}/tx_valid.json | 0
6 files changed, 105 insertions(+), 79 deletions(-)
create mode 100644 core/src/main/java/com/google/bitcoin/script/ScriptChunk.java
rename core/src/test/resources/com/google/bitcoin/{core => script}/script_invalid.json (100%)
rename core/src/test/resources/com/google/bitcoin/{core => script}/script_valid.json (100%)
rename core/src/test/resources/com/google/bitcoin/{core => script}/tx_invalid.json (100%)
rename core/src/test/resources/com/google/bitcoin/{core => script}/tx_valid.json (100%)
diff --git a/core/src/main/java/com/google/bitcoin/script/Script.java b/core/src/main/java/com/google/bitcoin/script/Script.java
index 4bd03e4d..e5db0e02 100644
--- a/core/src/main/java/com/google/bitcoin/script/Script.java
+++ b/core/src/main/java/com/google/bitcoin/script/Script.java
@@ -35,7 +35,6 @@ import java.util.*;
import static com.google.bitcoin.script.ScriptOpCodes.*;
import static com.google.bitcoin.core.Utils.bytesToHexString;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
// TODO: Make this class a superclass with derived classes giving accessor methods for the various common templates.
@@ -52,65 +51,10 @@ import static com.google.common.base.Preconditions.checkState;
*/
public class Script {
private static final Logger log = LoggerFactory.getLogger(Script.class);
- private static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
-
- /**
- * An element that is either an opcode or a raw byte array (signature, pubkey, etc).
- */
- public static class Chunk {
- private boolean isOpCode;
- public byte[] data;
- private int startLocationInProgram;
-
- public Chunk(boolean isOpCode, byte[] data) {
- this(isOpCode, data, -1);
- }
-
- public Chunk(boolean isOpCode, byte[] data, int startLocationInProgram) {
- this.isOpCode = isOpCode;
- this.data = data;
- this.startLocationInProgram = startLocationInProgram;
- }
-
- public boolean equalsOpCode(int opCode) {
- return isOpCode && data.length == 1 && (0xFF & data[0]) == opCode;
- }
-
- public boolean isOpCode() {
- return isOpCode;
- }
-
- public int getStartLocationInProgram() {
- checkState(startLocationInProgram >= 0);
- return startLocationInProgram;
- }
-
- public void write(OutputStream stream) throws IOException {
- if (isOpCode) {
- checkState(data.length == 1);
- stream.write(data);
- } else {
- checkState(data.length <= MAX_SCRIPT_ELEMENT_SIZE);
- if (data.length < OP_PUSHDATA1) {
- stream.write(data.length);
- } else if (data.length <= 0xFF) {
- stream.write(OP_PUSHDATA1);
- stream.write(data.length);
- } else if (data.length <= 0xFFFF) {
- stream.write(OP_PUSHDATA2);
- stream.write(0xFF & data.length);
- stream.write(0xFF & (data.length >> 8));
- } else {
- stream.write(OP_PUSHDATA4);
- Utils.uint32ToByteStreamLE(data.length, stream);
- }
- stream.write(data);
- }
- }
- }
+ public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
// The program is a set of chunks where each element is either [opcode] or [data, data, data ...]
- protected List chunks;
+ protected List chunks;
// Unfortunately, scripts are not ever re-serialized or canonicalized when used in signature hashing. Thus we
// must preserve the exact bytes that we read off the wire, along with the parsed form.
protected byte[] program;
@@ -135,8 +79,8 @@ public class Script {
*/
public String toString() {
StringBuilder buf = new StringBuilder();
- for (Chunk chunk : chunks) {
- if (chunk.isOpCode) {
+ for (ScriptChunk chunk : chunks) {
+ if (chunk.isOpCode()) {
buf.append(getOpCodeName(chunk.data[0]));
buf.append(" ");
} else {
@@ -156,7 +100,7 @@ public class Script {
if (program != null)
return Arrays.copyOf(program, program.length);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
- for (Chunk chunk : chunks) {
+ for (ScriptChunk chunk : chunks) {
chunk.write(bos);
}
return bos.toByteArray();
@@ -166,7 +110,7 @@ public class Script {
}
/** Returns an immutable list of the scripts parsed form. */
- public List getChunks() {
+ public List getChunks() {
return Collections.unmodifiableList(chunks);
}
@@ -180,7 +124,7 @@ public class Script {
* The official client does something similar.
*/
private void parse(byte[] program) throws ScriptException {
- chunks = new ArrayList(10); // Arbitrary choice.
+ chunks = new ArrayList(10); // Arbitrary choice.
ByteArrayInputStream bis = new ByteArrayInputStream(program);
int initialSize = bis.available();
while (bis.available() > 0) {
@@ -206,14 +150,14 @@ public class Script {
}
if (dataToRead == -1) {
- chunks.add(new Chunk(true, new byte[]{(byte) opcode}, startLocationInProgram));
+ chunks.add(new ScriptChunk(true, new byte[]{(byte) opcode}, startLocationInProgram));
} else {
if (dataToRead > MAX_SCRIPT_ELEMENT_SIZE)
throw new ScriptException("Push of data element that is larger than the max element size");
byte[] data = new byte[(int)dataToRead];
if (dataToRead > 0 && bis.read(data, 0, (int)dataToRead) < dataToRead)
throw new ScriptException("Unexpected end of script");
- chunks.add(new Chunk(false, data, startLocationInProgram));
+ chunks.add(new ScriptChunk(false, data, startLocationInProgram));
}
}
}
@@ -226,7 +170,7 @@ public class Script {
*/
public boolean isSentToRawPubKey() {
return chunks.size() == 2 && chunks.get(1).equalsOpCode(OP_CHECKSIG) &&
- !chunks.get(0).isOpCode && chunks.get(0).data.length > 1;
+ !chunks.get(0).isOpCode() && chunks.get(0).data.length > 1;
}
/**
@@ -408,11 +352,11 @@ public class Script {
////////////////////// Interface used during verification of transactions/blocks ////////////////////////////////
- private static int getSigOpCount(List chunks, boolean accurate) throws ScriptException {
+ private static int getSigOpCount(List chunks, boolean accurate) throws ScriptException {
int sigOps = 0;
int lastOpCode = OP_INVALIDOPCODE;
- for (Chunk chunk : chunks) {
- if (chunk.isOpCode) {
+ for (ScriptChunk chunk : chunks) {
+ if (chunk.isOpCode()) {
int opcode = 0xFF & chunk.data[0];
switch (opcode) {
case OP_CHECKSIG:
@@ -481,7 +425,7 @@ public class Script {
// Ignore errors and count up to the parse-able length
}
for (int i = script.chunks.size() - 1; i >= 0; i--)
- if (!script.chunks.get(i).isOpCode) {
+ if (!script.chunks.get(i).isOpCode()) {
Script subScript = new Script();
subScript.parse(script.chunks.get(i).data);
return getSigOpCount(subScript.chunks, true);
@@ -518,18 +462,18 @@ public class Script {
*/
public boolean isSentToMultiSig() {
if (chunks.size() < 4) return false;
- Chunk chunk = chunks.get(chunks.size() - 1);
+ ScriptChunk chunk = chunks.get(chunks.size() - 1);
// Must end in OP_CHECKMULTISIG[VERIFY].
- if (!chunk.isOpCode) return false;
+ if (!chunk.isOpCode()) return false;
if (!(chunk.equalsOpCode(OP_CHECKMULTISIG) || chunk.equalsOpCode(OP_CHECKMULTISIGVERIFY))) return false;
try {
// Second to last chunk must be an OP_N opcode and there should be that many data chunks (keys).
- Chunk m = chunks.get(chunks.size() - 2);
- if (!m.isOpCode) return false;
+ ScriptChunk m = chunks.get(chunks.size() - 2);
+ if (!m.isOpCode()) return false;
int numKeys = decodeFromOpN(m.data[0]);
if (chunks.size() != 3 + numKeys) return false;
for (int i = 1; i < chunks.size() - 2; i++) {
- if (chunks.get(i).isOpCode) return false;
+ if (chunks.get(i).isOpCode()) return false;
}
// First chunk must be an OP_N opcode too.
decodeFromOpN(chunks.get(0).data[0]);
@@ -620,10 +564,10 @@ public class Script {
LinkedList altstack = new LinkedList();
LinkedList ifStack = new LinkedList();
- for (Chunk chunk : script.chunks) {
+ for (ScriptChunk chunk : script.chunks) {
boolean shouldExecute = !ifStack.contains(false);
- if (!chunk.isOpCode) {
+ if (!chunk.isOpCode()) {
if (!shouldExecute)
continue;
@@ -1267,8 +1211,8 @@ public class Script {
// TODO: Check if we can take out enforceP2SH if there's a checkpoint at the enforcement block.
if (enforceP2SH && scriptPubKey.isPayToScriptHash()) {
- for (Chunk chunk : chunks)
- if (chunk.isOpCode && (chunk.data[0] & 0xff) > OP_16)
+ for (ScriptChunk chunk : chunks)
+ if (chunk.isOpCode() && (chunk.data[0] & 0xff) > OP_16)
throw new ScriptException("Attempted to spend a P2SH scriptPubKey with a script that contained script ops");
byte[] scriptPubKeyBytes = p2shStack.pollLast();
diff --git a/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java b/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java
new file mode 100644
index 00000000..684d61b6
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * 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 com.google.bitcoin.script;
+
+import com.google.bitcoin.core.Utils;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA1;
+import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA2;
+import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA4;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * An element that is either an opcode or a raw byte array (signature, pubkey, etc).
+ */
+public class ScriptChunk {
+ private boolean isOpCode;
+ public byte[] data;
+ private int startLocationInProgram;
+
+ public ScriptChunk(boolean isOpCode, byte[] data) {
+ this(isOpCode, data, -1);
+ }
+
+ public ScriptChunk(boolean isOpCode, byte[] data, int startLocationInProgram) {
+ this.isOpCode = isOpCode;
+ this.data = data;
+ this.startLocationInProgram = startLocationInProgram;
+ }
+
+ public boolean equalsOpCode(int opCode) {
+ return isOpCode && data.length == 1 && (0xFF & data[0]) == opCode;
+ }
+
+ public boolean isOpCode() {
+ return isOpCode;
+ }
+
+ public int getStartLocationInProgram() {
+ checkState(startLocationInProgram >= 0);
+ return startLocationInProgram;
+ }
+
+ public void write(OutputStream stream) throws IOException {
+ if (isOpCode) {
+ checkState(data.length == 1);
+ stream.write(data);
+ } else {
+ checkState(data.length <= Script.MAX_SCRIPT_ELEMENT_SIZE);
+ if (data.length < OP_PUSHDATA1) {
+ stream.write(data.length);
+ } else if (data.length <= 0xFF) {
+ stream.write(OP_PUSHDATA1);
+ stream.write(data.length);
+ } else if (data.length <= 0xFFFF) {
+ stream.write(OP_PUSHDATA2);
+ stream.write(0xFF & data.length);
+ stream.write(0xFF & (data.length >> 8));
+ } else {
+ stream.write(OP_PUSHDATA4);
+ Utils.uint32ToByteStreamLE(data.length, stream);
+ }
+ stream.write(data);
+ }
+ }
+}
diff --git a/core/src/test/resources/com/google/bitcoin/core/script_invalid.json b/core/src/test/resources/com/google/bitcoin/script/script_invalid.json
similarity index 100%
rename from core/src/test/resources/com/google/bitcoin/core/script_invalid.json
rename to core/src/test/resources/com/google/bitcoin/script/script_invalid.json
diff --git a/core/src/test/resources/com/google/bitcoin/core/script_valid.json b/core/src/test/resources/com/google/bitcoin/script/script_valid.json
similarity index 100%
rename from core/src/test/resources/com/google/bitcoin/core/script_valid.json
rename to core/src/test/resources/com/google/bitcoin/script/script_valid.json
diff --git a/core/src/test/resources/com/google/bitcoin/core/tx_invalid.json b/core/src/test/resources/com/google/bitcoin/script/tx_invalid.json
similarity index 100%
rename from core/src/test/resources/com/google/bitcoin/core/tx_invalid.json
rename to core/src/test/resources/com/google/bitcoin/script/tx_invalid.json
diff --git a/core/src/test/resources/com/google/bitcoin/core/tx_valid.json b/core/src/test/resources/com/google/bitcoin/script/tx_valid.json
similarity index 100%
rename from core/src/test/resources/com/google/bitcoin/core/tx_valid.json
rename to core/src/test/resources/com/google/bitcoin/script/tx_valid.json