package piuk.blockchain.android; import com.google.bitcoin.core.Utils; import java.util.ArrayList; import java.util.List; //TODO remove public class BitcoinScript { final List<byte[]> chunks; final byte[] program; int cursor; public static final int ScriptOutTypeStrange = 0; public static final int ScriptOutTypeAddress = 1; public static final int ScriptOutTypePubKey = 2; public static final int ScriptOutTypeP2SH = 3; public static final int ScriptOutTypeMultiSig = 4; public static final int ScriptInTypeStrange = 5; public static final int ScriptInTypeAddress = 6; public static final int ScriptInTypePubKey = 7; // push value public static final int OP_0 = 0; public static final int OP_FALSE = 0; public static final int OP_PUSHDATA1 = 76; public static final int OP_PUSHDATA2 = 77; public static final int OP_PUSHDATA4 = 78; public static final int OP_1NEGATE = 79; public static final int OP_RESERVED = 80; public static final int OP_1 = 81; public static final int OP_2 = 82; public static final int OP_3 = 83; public static final int OP_4 = 84; public static final int OP_5 = 85; public static final int OP_6 = 86; public static final int OP_7 = 87; public static final int OP_8 = 88; public static final int OP_9 = 89; public static final int OP_10 = 90; public static final int OP_11 = 91; public static final int OP_12 = 92; public static final int OP_13 = 93; public static final int OP_14 = 94; public static final int OP_15 = 95; public static final int OP_16 = 96; // control public static final int OP_NOP = 97; public static final int OP_VER = 98; public static final int OP_IF = 99; public static final int OP_NOTIF = 100; public static final int OP_VERIF = 101; public static final int OP_VERNOTIF = 102; public static final int OP_ELSE = 103; public static final int OP_ENDIF = 104; public static final int OP_VERIFY = 105; public static final int OP_RETURN = 106; // stack ops public static final int OP_TOALTSTACK = 107; public static final int OP_FROMALTSTACK = 108; public static final int OP_2DROP = 109; public static final int OP_2DUP = 110; public static final int OP_3DUP = 111; public static final int OP_2OVER = 112; public static final int OP_2ROT = 113; public static final int OP_2SWAP = 114; public static final int OP_IFDUP = 115; public static final int OP_DEPTH = 116; public static final int OP_DROP = 117; public static final int OP_DUP = 118; public static final int OP_NIP = 119; public static final int OP_OVER = 120; public static final int OP_PICK = 121; public static final int OP_ROLL = 122; public static final int OP_ROT = 123; public static final int OP_SWAP = 124; public static final int OP_TUCK = 125; // splice ops public static final int OP_CAT = 126; public static final int OP_SUBSTR = 127; public static final int OP_LEFT = 128; public static final int OP_RIGHT = 129; public static final int OP_SIZE = 130; // bit logic public static final int OP_INVERT = 131; public static final int OP_AND = 132; public static final int OP_OR = 133; public static final int OP_XOR = 134; public static final int OP_EQUAL = 135; public static final int OP_EQUALVERIFY = 136; public static final int OP_RESERVED1 = 137; public static final int OP_RESERVED2 = 138; // numeric public static final int OP_1ADD = 139; public static final int OP_1SUB = 140; public static final int OP_2MUL = 141; public static final int OP_2DIV = 142; public static final int OP_NEGATE = 143; public static final int OP_ABS = 144; public static final int OP_NOT = 145; public static final int OP_0NOTEQUAL = 146; public static final int OP_ADD = 147; public static final int OP_SUB = 148; public static final int OP_MUL = 149; public static final int OP_DIV = 150; public static final int OP_MOD = 151; public static final int OP_LSHIFT = 152; public static final int OP_RSHIFT = 153; public static final int OP_BOOLAND = 154; public static final int OP_BOOLOR = 155; public static final int OP_NUMEQUAL = 156; public static final int OP_NUMEQUALVERIFY = 157; public static final int OP_NUMNOTEQUAL = 158; public static final int OP_LESSTHAN = 159; public static final int OP_GREATERTHAN = 160; public static final int OP_LESSTHANOREQUAL = 161; public static final int OP_GREATERTHANOREQUAL = 162; public static final int OP_MIN = 163; public static final int OP_MAX = 164; public static final int OP_WITHIN = 165; // crypto public static final int OP_RIPEMD160 = 166; public static final int OP_SHA1 = 167; public static final int OP_SHA256 = 168; public static final int OP_HASH160 = 169; public static final int OP_HASH256 = 170; public static final int OP_CODESEPARATOR = 171; public static final int OP_CHECKSIG = 172; public static final int OP_CHECKSIGVERIFY = 173; public static final int OP_CHECKMULTISIG = 174; public static final int OP_CHECKMULTISIGVERIFY = 175; // expansion public static final int OP_NOP1 = 176; public static final int OP_NOP2 = 177; public static final int OP_NOP3 = 178; public static final int OP_NOP4 = 179; public static final int OP_NOP5 = 180; public static final int OP_NOP6 = 181; public static final int OP_NOP7 = 182; public static final int OP_NOP8 = 183; public static final int OP_NOP9 = 184; public static final int OP_NOP10 = 185; // template matching params public static final int OP_PUBKEYHASH = 253; public static final int OP_PUBKEY = 254; public static final int OP_INVALIDOPCODE = 255; public static String[] op_names = new String[256]; static { op_names[OP_0] = "OP_0"; op_names[OP_FALSE] = "OP_FALSE"; op_names[OP_PUSHDATA1] = "OP_PUSHDATA1"; op_names[OP_PUSHDATA2] = "OP_PUSHDATA2"; op_names[OP_PUSHDATA4] = "OP_PUSHDATA4"; op_names[OP_1NEGATE] = "OP_1NEGATE"; op_names[OP_RESERVED] = "OP_RESERVED"; op_names[OP_1] = "OP_1"; op_names[OP_2] = "OP_2"; op_names[OP_3] = "OP_3"; op_names[OP_4] = "OP_4"; op_names[OP_5] = "OP_5"; op_names[OP_6] = "OP_6"; op_names[OP_7] = "OP_7"; op_names[OP_8] = "OP_8"; op_names[OP_9] = "OP_9"; op_names[OP_10] = "OP_10"; op_names[OP_11] = "OP_11"; op_names[OP_12] = "OP_12"; op_names[OP_13] = "OP_13"; op_names[OP_14] = "OP_14"; op_names[OP_15] = "OP_15"; op_names[OP_16] = "OP_16"; op_names[OP_NOP] = "OP_NOP"; op_names[OP_VER] = "OP_VER"; op_names[OP_IF] = "OP_IF"; op_names[OP_NOTIF] = "OP_NOTIF"; op_names[OP_VERIF] = "OP_VERIF"; op_names[OP_VERNOTIF] = "OP_VERNOTIF"; op_names[OP_ELSE] = "OP_ELSE"; op_names[OP_ENDIF] = "OP_ENDIF"; op_names[OP_VERIFY] = "OP_VERIFY"; op_names[OP_RETURN] = "OP_RETURN"; op_names[OP_TOALTSTACK] = "OP_TOALTSTACK"; op_names[OP_FROMALTSTACK] = "OP_FROMALTSTACK"; op_names[OP_2DROP] = "OP_2DROP"; op_names[OP_2DUP] = "OP_2DUP"; op_names[OP_3DUP] = "OP_3DUP"; op_names[OP_2OVER] = "OP_2OVER"; op_names[OP_2ROT] = "OP_2ROT"; op_names[OP_2SWAP] = "OP_2SWAP"; op_names[OP_IFDUP] = "OP_IFDUP"; op_names[OP_DEPTH] = "OP_DEPTH"; op_names[OP_DUP] = "OP_DUP"; op_names[OP_DROP] = "OP_DROP"; op_names[OP_NIP] = "OP_NIP"; op_names[OP_OVER] = "OP_OVER"; op_names[OP_PICK] = "OP_PICK"; op_names[OP_ROLL] = "OP_ROLL"; op_names[OP_ROT] = "OP_ROT"; op_names[OP_SWAP] = "OP_SWAP"; op_names[OP_TUCK] = "OP_TUCK"; op_names[OP_CAT] = "OP_CAT"; op_names[OP_SUBSTR] = "OP_SUBSTR"; op_names[OP_LEFT] = "OP_LEFT"; op_names[OP_RIGHT] = "OP_RIGHT"; op_names[OP_SIZE] = "OP_SIZE"; op_names[OP_INVERT] = "OP_INVERT"; op_names[OP_AND] = "OP_AND"; op_names[OP_OR] = "OP_OR"; op_names[OP_XOR] = "OP_XOR"; op_names[OP_EQUAL] = "OP_EQUAL"; op_names[OP_EQUALVERIFY] = "OP_EQUALVERIFY"; op_names[OP_RESERVED1] = "OP_RESERVED1"; op_names[OP_RESERVED2] = "OP_RESERVED2"; op_names[OP_1ADD] = "OP_1ADD"; op_names[OP_1SUB] = "OP_1SUB"; op_names[OP_2MUL] = "OP_2MUL"; op_names[OP_2DIV] = "OP_2DIV"; op_names[OP_NEGATE] = "OP_NEGATE"; op_names[OP_ABS] = "OP_ABS"; op_names[OP_NOT] = "OP_NOT"; op_names[OP_0NOTEQUAL] = "OP_0NOTEQUAL"; op_names[OP_ADD] = "OP_ADD"; op_names[OP_SUB] = "OP_SUB"; op_names[OP_MUL] = "OP_MUL"; op_names[OP_DIV] = "OP_DIV"; op_names[OP_MOD] = "OP_MOD"; op_names[OP_LSHIFT] = "OP_LSHIFT"; op_names[OP_RSHIFT] = "OP_RSHIFT"; op_names[OP_BOOLAND] = "OP_BOOLAND"; op_names[OP_BOOLOR] = "OP_BOOLOR"; op_names[OP_NUMEQUAL] = "OP_NUMEQUAL"; op_names[OP_NUMEQUALVERIFY] = "OP_NUMEQUALVERIFY"; op_names[OP_NUMNOTEQUAL] = "OP_NUMNOTEQUAL"; op_names[OP_LESSTHAN] = "OP_LESSTHAN"; op_names[OP_GREATERTHAN] = "OP_GREATERTHAN"; op_names[OP_LESSTHANOREQUAL] = "OP_LESSTHANOREQUAL"; op_names[OP_GREATERTHANOREQUAL] = "OP_GREATERTHANOREQUAL"; op_names[OP_MIN] = "OP_MIN"; op_names[OP_MAX] = "OP_MAX"; op_names[OP_WITHIN] = "OP_WITHIN"; op_names[OP_RIPEMD160] = "OP_RIPEMD160"; op_names[OP_SHA1] = "OP_SHA1"; op_names[OP_SHA256] = "OP_SHA256"; op_names[OP_HASH160] = "OP_HASH160"; op_names[OP_HASH256] = "OP_HASH256"; op_names[OP_CODESEPARATOR] = "OP_CODESEPARATOR"; op_names[OP_CHECKSIG] = "OP_CHECKSIG"; op_names[OP_CHECKSIGVERIFY] = "OP_CHECKSIGVERIFY"; op_names[OP_CHECKMULTISIG] = "OP_CHECKMULTISIG"; op_names[OP_CHECKMULTISIGVERIFY] = "OP_CHECKMULTISIGVERIFY"; op_names[OP_NOP1] = "OP_NOP1"; op_names[OP_NOP2] = "OP_NOP2"; op_names[OP_NOP3] = "OP_NOP3"; op_names[OP_NOP4] = "OP_NOP4"; op_names[OP_NOP5] = "OP_NOP5"; op_names[OP_NOP6] = "OP_NOP6"; op_names[OP_NOP7] = "OP_NOP7"; op_names[OP_NOP8] = "OP_NOP8"; op_names[OP_NOP9] = "OP_NOP9"; op_names[OP_NOP10] = "OP_NOP10"; op_names[OP_PUBKEYHASH] = "OP_PUBKEYHASH"; op_names[OP_PUBKEY] = "OP_PUBKEY"; op_names[OP_INVALIDOPCODE] = "OP_INVALIDOPCODE"; } private byte[] getData(int len) throws Exception { try { byte[] buf = new byte[len]; System.arraycopy(program, cursor, buf, 0, len); cursor += len; return buf; } catch (ArrayIndexOutOfBoundsException e) { // We want running out of data in the array to be treated as a // handleable script parsing exception, // not something that abnormally terminates the app. throw new Exception("Failed read of " + len + " bytes", e); } } public byte[] getChunk(int chunk) { return chunks.get(chunk); } private int readByte() { return 0xFF & program[cursor++]; } public static int unsignedByteToInt(byte b) { return b & 0xFF; } public static void writeOpcode(List<Byte> bytes, int op0) { bytes.add((byte) op0); }; public static void writeBytes(List<Byte> bytes, byte[] data) { if (data.length < OP_PUSHDATA1) { bytes.add((byte) data.length); } else if (data.length <= 0xff) { bytes.add((byte) OP_PUSHDATA1); bytes.add((byte) data.length); } else if (data.length <= 0xffff) { bytes.add((byte) OP_PUSHDATA2); bytes.add((byte) (data.length & 0xff)); bytes.add((byte) ((data.length >>> 8) & 0xff)); } else { bytes.add((byte) OP_PUSHDATA4); bytes.add((byte) (data.length & 0xff)); bytes.add((byte) ((data.length >>> 8) & 0xff)); bytes.add((byte) ((data.length >>> 16) & 0xff)); bytes.add((byte) ((data.length >>> 24) & 0xff)); } for (byte b : data) { bytes.add(b); } }; public static BitcoinScript createSimpleOutBitoinScript( BitcoinAddress address) throws Exception { // Now create the redemption script List<Byte> bytes = new ArrayList<Byte>(); if (address.getVersion() == 0) { BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_DUP); BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_HASH160); BitcoinScript.writeBytes(bytes, address.getHash160().getBytes()); BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_EQUALVERIFY); BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_CHECKSIG); return new BitcoinScript(bytes); } else if (address.getVersion() == 5) { BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_HASH160); BitcoinScript.writeBytes(bytes, address.getHash160().getBytes()); BitcoinScript.writeOpcode(bytes, BitcoinScript.OP_EQUAL); return new BitcoinScript(bytes); } else { throw new Exception("Bitcoin address version " + address.getVersion() + " not supported yet"); } } public BitcoinScript(List<Byte> bytes) throws Exception { byte[] scriptBytes = new byte[bytes.size()]; for (int ii = 0; ii < scriptBytes.length; ++ii) { scriptBytes[ii] = bytes.get(ii); } this.program = scriptBytes; this.chunks = new ArrayList<byte[]>(); // Arbitrary choice of initial // size. init(); } private void init() throws Exception { cursor = 0; int length = program.length; while (cursor < length) { int opcode = readByte(); 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. } else if (opcode == OP_PUSHDATA1) { int len = readByte(); chunks.add(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)); } else if (opcode == OP_PUSHDATA4) { // Read a uint32, then read that many bytes of data. throw new Exception("PUSHDATA4: Unimplemented"); } else { chunks.add(new byte[] { (byte) opcode }); } } } public BitcoinScript(byte[] bytes) throws Exception { this.program = bytes; this.chunks = new ArrayList<byte[]>(); // Arbitrary choice of initial // size. init(); } public byte[] getProgram() { return program; } public int getInType() { System.out.println("Chunks " + this.chunks.size()); if (this.chunks.size() == 1) { // Direct IP to IP transactions only have the public key in their // scriptSig. return ScriptInTypeAddress; } else if (this.chunks.size() == 2 && this.chunks.get(0).length > 1) { return ScriptInTypePubKey; } else { return ScriptInTypeStrange; } }; public BitcoinScript getP2SHScript() throws Exception { return new BitcoinScript(this.chunks.get(0)); } public int getOutType() { try { if (this.chunks.size() > 3 && unsignedByteToInt(this.program[this.program.length - 1]) == OP_CHECKMULTISIG) { // Transfer to Bitcoin address return ScriptOutTypeMultiSig; } else if (this.program.length == 25 && unsignedByteToInt(this.program[0]) == OP_DUP && unsignedByteToInt(this.program[1]) == OP_HASH160 && this.program[2] == 20 && unsignedByteToInt(this.program[23]) == OP_EQUALVERIFY && unsignedByteToInt(this.program[24]) == OP_CHECKSIG) { // Transfer to Bitcoin address return ScriptOutTypeAddress; } else if (this.program.length == 23 && unsignedByteToInt(this.program[0]) == OP_HASH160 && unsignedByteToInt(this.program[1]) == 0x14 && unsignedByteToInt(this.program[22]) == OP_EQUAL) { // Pay to Script Hash return ScriptOutTypeP2SH; } else if (this.chunks.size() == 2 && unsignedByteToInt(this.chunks.get(1)[0]) == OP_CHECKSIG) { // Transfer to Public Key return ScriptOutTypePubKey; } else { return ScriptOutTypeStrange; } } catch (Exception e) { return ScriptOutTypeStrange; } }; public Hash getSimpleInPubKey() { switch (this.getInType()) { case ScriptInTypePubKey: System.out.println("Pub key"); return new Hash(this.chunks.get(1)); default: return null; } }; public BitcoinAddress getAddress() { try { System.out.println("Out type " + this.getOutType()); switch (this.getOutType()) { case ScriptOutTypeAddress: return new BitcoinAddress(new Hash(this.chunks.get(2)), (short) 0); case ScriptOutTypePubKey: return new BitcoinAddress(new Hash( Utils.sha256hash160(this.chunks.get(0))), (short) 0); case ScriptOutTypeP2SH: return new BitcoinAddress(new Hash( Utils.sha256hash160(this.chunks.get(0))), (short) 5); } } catch (Exception e) { e.printStackTrace(); } return null; }; public int extractPubKeys(List<Hash> pubkeys) { switch (this.getOutType()) { case ScriptOutTypePubKey: pubkeys.add(new Hash(this.chunks.get(0))); return 1; case ScriptOutTypeMultiSig: int n = unsignedByteToInt(this.chunks.get(this.chunks.size() - 2)[0]) - OP_1 + 1; for (int ii = 0; ii < n; ++ii) { pubkeys.add(0, new Hash(this.chunks.get(this.chunks.size() - 3 - ii))); } int m = unsignedByteToInt(this.chunks.get(this.chunks.size() - 3 - n)[0]) - OP_1 + 1; return m; } return 0; }; }