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 5b8894d1..87ff448a 100644 --- a/core/src/main/java/com/google/bitcoin/script/Script.java +++ b/core/src/main/java/com/google/bitcoin/script/Script.java @@ -1,6 +1,7 @@ /** * Copyright 2011 Google Inc. * Copyright 2012 Matt Corallo. + * Copyright 2014 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -108,12 +109,12 @@ public class Script { StringBuilder buf = new StringBuilder(); for (ScriptChunk chunk : chunks) { if (chunk.isOpCode()) { - buf.append(getOpCodeName(chunk.data[0])); + buf.append(getOpCodeName(chunk.opcode)); buf.append(" "); } else { // Data chunk buf.append("["); - buf.append(bytesToHexString(chunk.data)); + buf.append(chunk.data != null ? bytesToHexString(chunk.data) : "null"); buf.append("] "); } } @@ -192,13 +193,13 @@ public class Script { ScriptChunk chunk; if (dataToRead == -1) { - chunk = new ScriptChunk(true, new byte[]{(byte) opcode}, startLocationInProgram); + chunk = new ScriptChunk(opcode, null, startLocationInProgram); } else { if (dataToRead > bis.available()) throw new ScriptException("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(false, data, startLocationInProgram); + chunk = new ScriptChunk(opcode, data, startLocationInProgram); } // Save some memory by eliminating redundant copies of the same chunk objects. INTERN_TABLE can be null // here because this method is called whilst setting it up. @@ -272,12 +273,16 @@ public class Script { if (chunks.size() != 2) { throw new ScriptException("Script not of right size, expecting 2 but got " + chunks.size()); } - if (chunks.get(0).data.length > 2 && chunks.get(1).data.length > 2) { + final ScriptChunk chunk0 = chunks.get(0); + final byte[] chunk0data = chunk0.data; + final ScriptChunk chunk1 = chunks.get(1); + final byte[] chunk1data = chunk1.data; + if (chunk0data != null && chunk0data.length > 2 && chunk1data != null && chunk1data.length > 2) { // If we have two large constants assume the input to a pay-to-address output. - return chunks.get(1).data; - } else if (chunks.get(1).data.length == 1 && chunks.get(1).equalsOpCode(OP_CHECKSIG) && chunks.get(0).data.length > 2) { + return chunk1data; + } else if (chunk1.equalsOpCode(OP_CHECKSIG) && chunk0data != null && chunk0data.length > 2) { // A large constant followed by an OP_CHECKSIG is the key. - return chunks.get(0).data; + return chunk0data; } else { throw new ScriptException("Script did not match expected form: " + toString()); } @@ -381,8 +386,7 @@ public class Script { int lastOpCode = OP_INVALIDOPCODE; for (ScriptChunk chunk : chunks) { if (chunk.isOpCode()) { - int opcode = 0xFF & chunk.data[0]; - switch (opcode) { + switch (chunk.opcode) { case OP_CHECKSIG: case OP_CHECKSIGVERIFY: sigOps++; @@ -397,19 +401,12 @@ public class Script { default: break; } - lastOpCode = opcode; + lastOpCode = chunk.opcode; } } return sigOps; } - /** - * Converts an opcode to its int representation (including OP_1NEGATE and OP_0/OP_FALSE) - * @throws IllegalArgumentException If the opcode is not an OP_N opcode - */ - public static int decodeFromOpN(byte opcode) throws IllegalArgumentException { - return decodeFromOpN((int)opcode); - } static int decodeFromOpN(int opcode) { checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16), "decodeFromOpN called on non OP_N opcode"); if (opcode == OP_0) @@ -499,13 +496,13 @@ public class Script { // Second to last chunk must be an OP_N opcode and there should be that many data chunks (keys). ScriptChunk m = chunks.get(chunks.size() - 2); if (!m.isOpCode()) return false; - int numKeys = decodeFromOpN(m.data[0]); + int numKeys = decodeFromOpN(m.opcode); if (numKeys < 1 || chunks.size() != 3 + numKeys) return false; for (int i = 1; i < chunks.size() - 2; i++) { if (chunks.get(i).isOpCode()) return false; } // First chunk must be an OP_N opcode too. - if (decodeFromOpN(chunks.get(0).data[0]) < 1) return false; + if (decodeFromOpN(chunks.get(0).opcode) < 1) return false; } catch (IllegalStateException e) { return false; // Not an OP_N opcode. } @@ -605,7 +602,7 @@ public class Script { stack.add(chunk.data); } else { - int opcode = 0xFF & chunk.data[0]; + int opcode = chunk.opcode; if (opcode > OP_16) { opCount++; if (opCount > 201) @@ -1254,7 +1251,7 @@ 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 (ScriptChunk chunk : chunks) - if (chunk.isOpCode() && (chunk.data[0] & 0xff) > OP_16) + if (chunk.isOpCode() && chunk.opcode > 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/ScriptBuilder.java b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java index 6cdf2601..1f19f710 100644 --- a/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java +++ b/core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java @@ -42,20 +42,31 @@ public class ScriptBuilder { } public ScriptBuilder op(int opcode) { - chunks.add(new ScriptChunk(true, new byte[]{(byte)opcode})); + checkArgument(opcode > OP_PUSHDATA4); + chunks.add(new ScriptChunk(opcode, null)); return this; } public ScriptBuilder data(byte[] data) { byte[] copy = Arrays.copyOf(data, data.length); - chunks.add(new ScriptChunk(false, copy)); + int opcode; + if (data.length < OP_PUSHDATA1) { + opcode = data.length; // OP_0 in case of empty vector + } else if (data.length < 256) { + opcode = OP_PUSHDATA1; + } else if (data.length < 65536) { + opcode = OP_PUSHDATA2; + } else { + throw new RuntimeException("Unimplemented"); + } + chunks.add(new ScriptChunk(opcode, copy)); return this; } public ScriptBuilder smallNum(int num) { checkArgument(num >= 0, "Cannot encode negative numbers with smallNum"); checkArgument(num <= 16, "Cannot encode numbers larger than 16 with smallNum"); - chunks.add(new ScriptChunk(true, new byte[]{(byte)Script.encodeToOpN(num)})); + chunks.add(new ScriptChunk(Script.encodeToOpN(num), null)); return this; } diff --git a/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java b/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java index c879d9be..d641cd23 100644 --- a/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java +++ b/core/src/main/java/com/google/bitcoin/script/ScriptChunk.java @@ -1,5 +1,6 @@ /* * Copyright 2013 Google Inc. + * Copyright 2014 Andreas Schildbach * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,38 +23,42 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import javax.annotation.Nullable; + 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.checkNotNull; 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 final int opcode; + @Nullable public final byte[] data; private int startLocationInProgram; - public ScriptChunk(boolean isOpCode, byte[] data) { - this(isOpCode, data, -1); + public ScriptChunk(int opcode, byte[] data) { + this(opcode, data, -1); } - public ScriptChunk(boolean isOpCode, byte[] data, int startLocationInProgram) { - this.isOpCode = isOpCode; + public ScriptChunk(int opcode, byte[] data, int startLocationInProgram) { + this.opcode = opcode; this.data = data; this.startLocationInProgram = startLocationInProgram; } - public boolean equalsOpCode(int opCode) { - return isOpCode && data.length == 1 && (0xFF & data[0]) == opCode; + public boolean equalsOpCode(int opcode) { + return opcode == this.opcode; } /** * If this chunk is a single byte of non-pushdata content (could be OP_RESERVED or some invalid Opcode) */ public boolean isOpCode() { - return isOpCode; + return opcode > OP_PUSHDATA4; } public int getStartLocationInProgram() { @@ -62,25 +67,33 @@ public class ScriptChunk { } 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) { + if (isOpCode()) { + checkState(data == null); + stream.write(opcode); + } else if (data != null) { + checkNotNull(data); + if (opcode < OP_PUSHDATA1) { + checkState(data.length == opcode); + stream.write(opcode); + } else if (opcode == OP_PUSHDATA1) { + checkState(data.length <= 0xFF); stream.write(OP_PUSHDATA1); stream.write(data.length); - } else if (data.length <= 0xFFFF) { + } else if (opcode == OP_PUSHDATA2) { + checkState(data.length <= 0xFFFF); stream.write(OP_PUSHDATA2); stream.write(0xFF & data.length); stream.write(0xFF & (data.length >> 8)); - } else { + } else if (opcode == OP_PUSHDATA4) { + checkState(data.length <= Script.MAX_SCRIPT_ELEMENT_SIZE); stream.write(OP_PUSHDATA4); Utils.uint32ToByteStreamLE(data.length, stream); + } else { + throw new RuntimeException("Unimplemented"); } stream.write(data); + } else { + stream.write(opcode); // smallNum } } @@ -91,7 +104,7 @@ public class ScriptChunk { ScriptChunk chunk = (ScriptChunk) o; - if (isOpCode != chunk.isOpCode) return false; + if (opcode != chunk.opcode) return false; if (startLocationInProgram != chunk.startLocationInProgram) return false; if (!Arrays.equals(data, chunk.data)) return false; @@ -100,7 +113,7 @@ public class ScriptChunk { @Override public int hashCode() { - int result = (isOpCode ? 1 : 0); + int result = opcode; result = 31 * result + (data != null ? Arrays.hashCode(data) : 0); result = 31 * result + startLocationInProgram; return result; diff --git a/core/src/main/java/com/google/bitcoin/script/ScriptOpCodes.java b/core/src/main/java/com/google/bitcoin/script/ScriptOpCodes.java index bb73bf3d..34514587 100644 --- a/core/src/main/java/com/google/bitcoin/script/ScriptOpCodes.java +++ b/core/src/main/java/com/google/bitcoin/script/ScriptOpCodes.java @@ -385,9 +385,7 @@ public class ScriptOpCodes { /** * Converts the given OpCode into a string (eg "0", "PUSHDATA", or "NON_OP(10)") */ - public static String getOpCodeName(byte opCode) { - int opcode = opCode & 0xff; - + public static String getOpCodeName(int opcode) { if (opCodeMap.containsKey(opcode)) return opCodeMap.get(opcode);