diff --git a/core/src/main/java/com/google/bitcoin/core/Script.java b/core/src/main/java/com/google/bitcoin/core/Script.java index 0da13106..0ca0002d 100644 --- a/core/src/main/java/com/google/bitcoin/core/Script.java +++ b/core/src/main/java/com/google/bitcoin/core/Script.java @@ -27,6 +27,23 @@ import java.util.List; import static com.google.bitcoin.core.Utils.bytesToHexString; +/** + * A chunk in a script + */ +class ScriptChunk { + public boolean isOpCode; + public byte[] data; + public ScriptChunk(boolean isOpCode, byte[] data) { + this.isOpCode = isOpCode; + this.data = data; + } + public boolean equalsOpCode(int opCode) { + return isOpCode && + data.length == 1 && + (0xFF & data[0]) == opCode; + } +} + /** * Bitcoin transactions don't specify what they do directly. Instead a * small binary stack language is used to define programs that when evaluated return whether the transaction @@ -53,8 +70,7 @@ public class Script { private int cursor; // The program is a set of byte[]s where each element is either [opcode] or [data, data, data ...] - // TODO: Differentiate between 1 byte data chunks and opcodes here. - List chunks; + List chunks; byte[] programCopy; // TODO: remove this private final NetworkParameters params; @@ -77,10 +93,10 @@ public class Script { */ public String toString() { StringBuffer buf = new StringBuffer(); - for (byte[] chunk : chunks) { - if (chunk.length == 1) { + for (ScriptChunk chunk : chunks) { + if (chunk.isOpCode) { String opName; - int opcode = 0xFF & chunk[0]; + int opcode = 0xFF & chunk.data[0]; switch (opcode) { case OP_DUP: opName = "DUP"; @@ -103,9 +119,9 @@ public class Script { } else { // Data chunk buf.append("["); - buf.append(chunk.length); + buf.append(chunk.data.length); buf.append("]"); - buf.append(bytesToHexString(chunk)); + buf.append(bytesToHexString(chunk.data)); buf.append(" "); } } @@ -156,7 +172,7 @@ public class Script { program = programCopy; offset = 0; - chunks = new ArrayList(10); // Arbitrary choice of initial size. + chunks = new ArrayList(10); // Arbitrary choice of initial size. cursor = offset; while (cursor < offset + length) { int opcode = readByte(); @@ -167,19 +183,19 @@ public class Script { if (opcode > 0 && opcode < OP_PUSHDATA1) { // Read some bytes of data, where how many is the opcode value itself. - chunks.add(getData(opcode)); // opcode == len here. + chunks.add(new ScriptChunk(false, getData(opcode))); // opcode == len here. } else if (opcode == OP_PUSHDATA1) { int len = readByte(); - chunks.add(getData(len)); + chunks.add(new ScriptChunk(false, getData(len))); } else if (opcode == OP_PUSHDATA2) { // Read a short, then read that many bytes of data. int len = readByte() | (readByte() << 8); - chunks.add(getData(len)); + chunks.add(new ScriptChunk(false, getData(len))); } else if (opcode == OP_PUSHDATA4) { // Read a uint32, then read that many bytes of data. log.error("PUSHDATA4: Unimplemented"); } else { - chunks.add(new byte[]{(byte) opcode}); + chunks.add(new ScriptChunk(true, new byte[]{(byte) opcode})); } } } @@ -193,7 +209,8 @@ public class Script { public boolean isSentToRawPubKey() { if (chunks.size() != 2) return false; - return (0xFF & chunks.get(1)[0]) == OP_CHECKSIG && chunks.get(0).length > 1; + return chunks.get(1).equalsOpCode(OP_CHECKSIG) && + !chunks.get(0).isOpCode && chunks.get(0).data.length > 1; } /** @@ -204,11 +221,11 @@ public class Script { */ public boolean isSentToAddress() { if (chunks.size() != 5) return false; - return (0xFF & chunks.get(0)[0]) == OP_DUP && - (0xFF & chunks.get(1)[0]) == OP_HASH160 && - chunks.get(2).length == Address.LENGTH && - (0xFF & chunks.get(3)[0]) == OP_EQUALVERIFY && - (0xFF & chunks.get(4)[0]) == OP_CHECKSIG; + return chunks.get(0).equalsOpCode(OP_DUP) && + chunks.get(1).equalsOpCode(OP_HASH160) && + chunks.get(2).data.length == Address.LENGTH && + chunks.get(3).equalsOpCode(OP_EQUALVERIFY) && + chunks.get(4).equalsOpCode(OP_CHECKSIG); } /** @@ -221,7 +238,7 @@ public class Script { if (!isSentToAddress()) throw new ScriptException("Script not in the standard scriptPubKey form"); // Otherwise, the third element is the hash of the public key, ie the bitcoin address. - return chunks.get(2); + return chunks.get(2).data; } /** @@ -236,12 +253,12 @@ 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).length > 2 && chunks.get(1).length > 2) { + if (chunks.get(0).data.length > 2 && chunks.get(1).data.length > 2) { // If we have two large constants assume the input to a pay-to-address output. - return chunks.get(1); - } else if (chunks.get(1).length == 1 && (0xFF & chunks.get(1)[0]) == OP_CHECKSIG && chunks.get(0).length > 2) { + 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) { // A large constant followed by an OP_CHECKSIG is the key. - return chunks.get(0); + return chunks.get(0).data; } else { throw new ScriptException("Script did not match expected form: " + toString()); } diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java index 67236db4..f418a528 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -699,7 +699,7 @@ public class WalletTest { assertNotNull(t2); // TODO: This code is messy, improve the Script class and fixinate! assertEquals(t2.toString(), 1, t2.getInputs().get(0).getScriptSig().chunks.size()); - assertTrue(t2.getInputs().get(0).getScriptSig().chunks.get(0).length > 50); + assertTrue(t2.getInputs().get(0).getScriptSig().chunks.get(0).data.length > 50); System.out.println(t2); }