package com.yoghurt.crypto.transactions.client.util.transaction; import java.util.ArrayList; import com.yoghurt.crypto.transactions.shared.domain.Transaction; import com.yoghurt.crypto.transactions.shared.domain.TransactionInput; import com.yoghurt.crypto.transactions.shared.domain.TransactionOutPoint; import com.yoghurt.crypto.transactions.shared.domain.TransactionOutput; import com.yoghurt.crypto.transactions.shared.domain.VariableLengthInteger; import com.yoghurt.crypto.transactions.shared.util.ArrayUtil; import com.yoghurt.crypto.transactions.shared.util.NumberParseUtil; import com.yoghurt.crypto.transactions.shared.util.ScriptParseUtil; public final class TransactionParseUtil extends TransactionUtil { private TransactionParseUtil() {} public static Transaction parseTransactionBytes(final byte[] bytes) { return parseTransactionBytes(bytes, new Transaction()); } public static Transaction parseTransactionBytes(final byte[] bytes, final Transaction transaction) { return parseTransactionBytes(bytes, transaction, 0); } public static Transaction parseTransactionBytes(final byte[] bytes, final Transaction transaction, final int initialPointer) { int pointer = initialPointer; // Parse the version bytes pointer = parseVersion(transaction, pointer, bytes); // Parse the transaction input size pointer = parseTransactionInputSize(transaction, pointer, bytes); // Parse the transaction inputs pointer = parseTransactionInputs(transaction, pointer, bytes); // Parse the transaction output size pointer = parseTransactionOutputSize(transaction, pointer, bytes); // Parse the transaction outputs pointer = parseTransactionOutputs(transaction, pointer, bytes); // Parse the lock time pointer = parseLockTime(transaction, pointer, bytes); // Compute the transaction tx computeTransactionHash(transaction, initialPointer, pointer, bytes); // Verify if the byte array size is equal to the pointer // TODO This will not working in the future because it'd be possible that a massive byte array which doesn't // represent a single transaction is passed (such as blocks) if(pointer != bytes.length) { throw new IllegalStateException("Raw transaction bytes not fully consumed"); } return transaction; } private static void computeTransactionHash(final Transaction transaction, final int initialPointer, final int pointer, final byte[] bytes) { final byte[] txBytes = ArrayUtil.arrayCopy(bytes, initialPointer, pointer); // Create SHA256 digest and feed it the tx bytes final byte[] txHash = ComputeUtil.computeDoubleSHA256(txBytes); // Convert to LE ArrayUtil.reverse(txHash); // Set the transaction tx transaction.setTransactionId(txHash); } private static int parseTransactionInputs(final Transaction transaction, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; final long numInputs = transaction.getInputSize().getValue(); final ArrayList<TransactionInput> inputs = new ArrayList<TransactionInput>((int) numInputs); // Stick it all in the transaction transaction.setInputs(inputs); // Iterate over the number of inputs in the transaction for (int i = 0; i < numInputs; i++) { // Create an empty transaction input final TransactionInput input = new TransactionInput(); // Set the index input.setInputIndex(i); // Add the input to the list (early, to be able to see from where it went wrong, if it goes wrong) inputs.add(input); // Parse the tx out point to the previous transaction pointer = parseTransactionOutPoint(input, pointer, bytes); // Parse the script pointer = ScriptParseUtil.parseScript(input, pointer, bytes, transaction.isCoinbase()); // Parse the sequence bytes pointer = parseSequence(input, pointer, bytes); } return pointer; } private static int parseTransactionOutputs(final Transaction transaction, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; final long numOutputs = transaction.getOutputSize().getValue(); final ArrayList<TransactionOutput> outputs = new ArrayList<TransactionOutput>((int) numOutputs); // Stick it all in the transaction transaction.setOutputs(outputs); // Iterate over the number of output in the transaction for (int i = 0; i < numOutputs; i++) { // Create an empty transaction output final TransactionOutput output = new TransactionOutput(); // Set the index output.setOutputIndex(i); // Add the output to the list (early, to be able to see from where it went wrong, if it goes wrong) outputs.add(output); // Parse the transaction value output.setTransactionValue(NumberParseUtil.parseLong(ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_OUTPUT_VALUE_SIZE))); // Parse the script pointer = ScriptParseUtil.parseScript(output, pointer, bytes, false); } return pointer; } private static int parseTransactionOutPoint(final TransactionInput input, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; final TransactionOutPoint outPoint = new TransactionOutPoint(); final byte[] prevHash = ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_INPUT_OUTPOINT_SIZE); outPoint.setReferenceTransaction(prevHash); outPoint.setIndex(NumberParseUtil.parseUint32(ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_OUTPOINT_INDEX_SIZE))); input.setOutPoint(outPoint); return pointer; } private static int parseTransactionInputSize(final Transaction transaction, final int pointer, final byte[] bytes) { final VariableLengthInteger variableInteger = new VariableLengthInteger(bytes, pointer); transaction.setInputSize(variableInteger); return pointer + variableInteger.getByteSize(); } private static int parseTransactionOutputSize(final Transaction transaction, final int pointer, final byte[] bytes) { final VariableLengthInteger variableInteger = new VariableLengthInteger(bytes, pointer); transaction.setOutputSize(variableInteger); return pointer + variableInteger.getByteSize(); } private static int parseSequence(final TransactionInput input, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; input.setTransactionSequence(NumberParseUtil.parseUint32(ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_SEQUENCE_SIZE))); return pointer; } private static int parseVersion(final Transaction transaction, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; transaction.setVersion(NumberParseUtil.parseUint32(ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_VERSION_FIELD_SIZE))); return pointer; } private static int parseLockTime(final Transaction transaction, final int initialPointer, final byte[] bytes) { int pointer = initialPointer; transaction.setLockTime(NumberParseUtil.parseUint32(ArrayUtil.arrayCopy(bytes, pointer, pointer = pointer + TRANSACTION_LOCK_TIME_SIZE))); return pointer; } }