package io.emax.cosigner.bitcoin.bitcoindrpc; import io.emax.cosigner.bitcoin.bitcoindrpc.RawTransaction.VariableInt; import io.emax.cosigner.common.ByteUtilities; import java.math.BigInteger; import java.util.LinkedList; public final class RawInput { private String txHash; private int txIndex; private long scriptSize = 0; private String script = ""; private int sequence = -1; public String getTxHash() { return txHash; } public void setTxHash(String txHash) { this.txHash = txHash; } public int getTxIndex() { return txIndex; } public void setTxIndex(int txIndex) { this.txIndex = txIndex; } public long getScriptSize() { return scriptSize; } public void setScriptSize(long scriptSize) { this.scriptSize = scriptSize; } public String getScript() { return script; } public void setScript(String script) { this.script = script; } public int getSequence() { return sequence; } public void setSequence(int sequence) { this.sequence = sequence; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((script == null) ? 0 : script.hashCode()); result = prime * result + (int) (scriptSize ^ (scriptSize >>> 32)); result = prime * result + sequence; result = prime * result + ((txHash == null) ? 0 : txHash.hashCode()); result = prime * result + txIndex; return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } RawInput other = (RawInput) obj; if (script == null) { if (other.script != null) { return false; } } else if (!script.equals(other.script)) { return false; } if (scriptSize != other.scriptSize) { return false; } if (sequence != other.sequence) { return false; } if (txHash == null) { if (other.txHash != null) { return false; } } else if (!txHash.equals(other.txHash)) { return false; } return txIndex == other.txIndex; } @Override public String toString() { return "RawInput [txHash=" + txHash + ", txIndex=" + txIndex + ", scriptSize=" + scriptSize + ", script=" + script + ", sequence=" + sequence + "]"; } /** * Encodes this input as a byte array. * * @return Hex string representing the input. */ public String encode() { String tx = ""; // Tx Hash byte[] hashBytes = ByteUtilities.toByteArray(getTxHash()); hashBytes = ByteUtilities.leftPad(hashBytes, 32, (byte) 0x00); hashBytes = ByteUtilities.flipEndian(hashBytes); tx += ByteUtilities.toHexString(hashBytes); // Tx Index byte[] indexBytes = ByteUtilities.stripLeadingNullBytes(BigInteger.valueOf(getTxIndex()).toByteArray()); indexBytes = ByteUtilities.leftPad(indexBytes, 4, (byte) 0x00); indexBytes = ByteUtilities.flipEndian(indexBytes); tx += ByteUtilities.toHexString(indexBytes); // Script Size setScriptSize(getScript().length() / 2L); byte[] scriptSizeBytes = RawTransaction.writeVariableInt(getScriptSize()); tx += ByteUtilities.toHexString(scriptSizeBytes); // Script byte[] scriptBytes = ByteUtilities.toByteArray(getScript()); tx += ByteUtilities.toHexString(scriptBytes); // Sequence byte[] sequenceBytes = ByteUtilities.stripLeadingNullBytes(BigInteger.valueOf(getSequence()).toByteArray()); sequenceBytes = ByteUtilities.leftPad(sequenceBytes, 4, (byte) 0xFF); sequenceBytes = ByteUtilities.flipEndian(sequenceBytes); tx += ByteUtilities.toHexString(sequenceBytes); return tx; } /** * Parses a hex string that represents an input, and converts it into a RawInput * * @param txData Hex string representing the input * @return RawInput generated from the input. */ public static RawInput parse(String txData) { RawInput input = new RawInput(); byte[] rawTx = ByteUtilities.toByteArray(txData); int buffPointer = 0; byte[] hashBytes = ByteUtilities.readBytes(rawTx, buffPointer, 32); buffPointer += 32; hashBytes = ByteUtilities.flipEndian(hashBytes); input.setTxHash(ByteUtilities.toHexString(hashBytes)); byte[] indexBytes = ByteUtilities.readBytes(rawTx, buffPointer, 4); buffPointer += 4; indexBytes = ByteUtilities.flipEndian(indexBytes); input.setTxIndex(new BigInteger(1, indexBytes).intValue()); VariableInt varScriptSize = RawTransaction.readVariableInt(rawTx, buffPointer); buffPointer += varScriptSize != null ? varScriptSize.getSize() : 0; input.setScriptSize(varScriptSize != null ? varScriptSize.getValue() : 0); byte[] scriptBytes = ByteUtilities.readBytes(rawTx, buffPointer, (int) input.getScriptSize()); buffPointer += input.getScriptSize(); input.setScript(ByteUtilities.toHexString(scriptBytes)); byte[] sequenceBytes = ByteUtilities.readBytes(rawTx, buffPointer, 4); //buffPointer += 4; sequenceBytes = ByteUtilities.flipEndian(sequenceBytes); input.setSequence(new BigInteger(1, sequenceBytes).intValue()); return input; } /** * Returns the size of the encoded data * * @return Size of the encoded data. */ public long getDataSize() { int sizeSize = RawTransaction.writeVariableInt(getScriptSize()).length; // Tx Hash + Index + scriptSize + Script + sequence return 32 + 4 + sizeSize + getScriptSize() + 4; } /** * Copies the current RawInput * * @return A new copy of this RawInput. */ public RawInput copy() { RawInput input = new RawInput(); input.setTxHash(getTxHash()); input.setTxIndex(getTxIndex()); input.setScriptSize(getScriptSize()); input.setScript(getScript()); input.setSequence(getSequence()); return input; } /** * Removes the multi-sig redeem script from the input signature so that more signatures can be * appended. * * @param redeemScript Script that we want to remove. */ public void stripMultiSigRedeemScript(String redeemScript) { LinkedList<String> stackItems = new LinkedList<>(); byte[] myScript = ByteUtilities.toByteArray(getScript()); int bufferPointer = 0; VariableInt stackItemSize; String stackItem; while (bufferPointer < myScript.length) { stackItemSize = RawTransaction.readVariableStackInt(myScript, bufferPointer); bufferPointer += stackItemSize != null ? stackItemSize.getSize() : 0; stackItem = ByteUtilities.toHexString(ByteUtilities.readBytes(myScript, bufferPointer, (int) (stackItemSize != null ? stackItemSize.getValue() : 0))); bufferPointer += stackItemSize != null ? stackItemSize.getValue() : 0; if (!stackItem.equalsIgnoreCase(redeemScript)) { stackItems.add(stackItem); } } StringBuilder myScriptString = new StringBuilder(); for (String item : stackItems) { byte[] itemBytes = ByteUtilities.toByteArray(item); byte[] prefixBytes = RawTransaction.writeVariableStackInt(itemBytes.length); myScriptString.append(ByteUtilities.toHexString(prefixBytes)); myScriptString.append(ByteUtilities.toHexString(itemBytes)); } setScript(myScriptString.toString()); } }