package com.yoghurt.crypto.transactions.shared.util; import com.yoghurt.crypto.transactions.shared.domain.Operation; import com.yoghurt.crypto.transactions.shared.domain.ScriptEntity; import com.yoghurt.crypto.transactions.shared.domain.ScriptPart; import com.yoghurt.crypto.transactions.shared.domain.VariableLengthInteger; public final class ScriptParseUtil { private ScriptParseUtil() {} public static int parseScript(final ScriptEntity entity, final int initialPointer, final byte[] bytes, final boolean isCoinbase) { int pointer = initialPointer; // Parse the number of bytes in the script pointer = parseScriptSize(entity, pointer, bytes); // Parse the actual script bytes if (isCoinbase) { pointer = parseCoinbaseScriptBytes(entity, pointer, bytes, entity.getScriptSize().getValue()); } else { pointer = parseScriptBytes(entity, pointer, bytes, entity.getScriptSize().getValue()); } return pointer; } public static ScriptEntity parseScript(final byte[] scriptBytes) { final ScriptEntity entity = new ScriptEntity(); parseScriptBytes(entity, 0, scriptBytes, scriptBytes.length); return entity; } private static int parseCoinbaseScriptBytes(final ScriptEntity entity, final int initialPointer, final byte[] bytes, final long value) { int pointer = initialPointer; entity.getInstructions().add(new ScriptPart(null, ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + (int) entity.getScriptSize().getValue()))); return pointer; } private static int parseScriptSize(final ScriptEntity scriptEntity, final int pointer, final byte[] bytes) { final VariableLengthInteger variableInteger = new VariableLengthInteger(bytes, pointer); scriptEntity.setScriptSize(variableInteger); return pointer + variableInteger.getByteSize(); } public static int parseScriptBytes(final ScriptEntity scriptEntity, final int initialPointer, final byte[] bytes, final long length) { int pointer = initialPointer; while (pointer < initialPointer + length) { pointer = parseOpcode(pointer, scriptEntity, bytes); } if (pointer != initialPointer + length) { throw new IllegalStateException("More bytes than advertised were consumed in the script. (advertised:" + length + ", actual:" + (pointer - initialPointer) + ")"); } return pointer; } private static int parseOpcode(final int initialPointer, final ScriptEntity script, final byte[] bytes) { int pointer = initialPointer; final int opcode = bytes[pointer] & 0xFF; if (ScriptOperationUtil.isDataPushOperation(opcode)) { pointer = parsePushData(pointer, opcode, script, bytes); } else { pointer++; script.getInstructions().add(new ScriptPart(ScriptOperationUtil.getOperation(opcode))); } return pointer; } private static int parsePushData(final int initialPointer, final int opcode, final ScriptEntity script, final byte[] bytes) { int pointer = initialPointer; // Simple 1-byte pushdata notation. if (opcode <= 75) { pointer++; script.getInstructions().add(new ScriptPart(Operation.OP_PUSHDATA, ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + opcode))); } else { // Switch over remaining options; OP_PUSHDATA1, 2, 4 switch (opcode) { case 76: // OP_PUSHDATA1 final int size = bytes[pointer = pointer + 1] & 0xFF; pointer++; final byte[] payload = ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + size); script.getInstructions().add(new ScriptPart(Operation.OP_PUSHDATA1, payload)); break; case 77: case 78: // TODO Support OP_PUSHDATA2 and 4 throw new UnsupportedOperationException(); default: throw new IllegalStateException("Unreachable code reached. (..)"); } } return pointer; } }