package org.ripple.power.txns.btc; import java.io.EOFException; import java.math.BigInteger; import java.util.Arrays; /** * <p>A transaction output has the following format:</p> * <pre> * Size Field Description * ==== ===== =========== * 8 bytes TxOutValue Value expressed in Satoshis (0.00000001 BTC) * VarInt TxOutScriptLength Script length * Variable TxOutScript Script * </pre> * * <p>All numbers are encoded in little-endian format (least-significant byte to most-significant byte)</p> */ public class TransactionOutput implements ByteSerializable { /** Unspendable 'Proof-of-burn' script (1CounterpartyXXXX...) */ private final byte[] unspendableScript = new byte[] { (byte)0x76, (byte)0xa9, (byte)0x14, (byte)0x81, (byte)0x88, (byte)0x95, (byte)0xf3, (byte)0xdc, (byte)0x2c, (byte)0x17, (byte)0x86, (byte)0x29, (byte)0xd3, (byte)0xd2, (byte)0xd8, (byte)0xfa, (byte)0x3e, (byte)0xc4, (byte)0xa3, (byte)0xf8, (byte)0x17, (byte)0x98, (byte)0x21, (byte)0x88, (byte)0xac }; /** Output value in Satoshis (0.00000001 BTC) */ private final BigInteger value; /** Transaction output index */ private final int txIndex; /** Output script */ private final byte[] scriptBytes; /** * Creates a transaction output for the specified amount using a * PAY_TO_PUBKEY_HASH script * * @param txIndex Transaction output index * @param value Transaction output value * @param address Send address */ public TransactionOutput(int txIndex, BigInteger value, Address address) { this.txIndex = txIndex; this.value = value; // // Create the output script for PAY_TO_PUBKEY_HASH // OP_DUP OP_HASH160 <pubkey-hash> OP_EQUALVERIFY OP_CHECKSIG // scriptBytes = new byte[1+1+1+20+1+1]; scriptBytes[0] = (byte)ScriptOpCodes.OP_DUP; scriptBytes[1] = (byte)ScriptOpCodes.OP_HASH160; scriptBytes[2] = (byte)20; System.arraycopy(address.getHash(), 0, scriptBytes, 3, 20); scriptBytes[23] = (byte)ScriptOpCodes.OP_EQUALVERIFY; scriptBytes[24] = (byte)ScriptOpCodes.OP_CHECKSIG; } /** * Creates a transaction output for the specified amount using the supplied script * * @param txIndex Transaction output index * @param value Transaction output value * @param scriptBytes Transaction output script */ public TransactionOutput(int txIndex, BigInteger value, byte[] scriptBytes) { this.txIndex = txIndex; this.value = value; this.scriptBytes = scriptBytes; } /** * Creates a transaction output from the encoded byte stream * * @param txIndex Index within the transaction output list * @param inBuffer Input stream * @throws EOFException Input stream is too short * @throws VerificationException Verification failed */ public TransactionOutput(int txIndex, SerializedBuffer inBuffer) throws EOFException, VerificationException { this.txIndex = txIndex; // // Get the amount // value = BigInteger.valueOf(inBuffer.getLong()); // // Get the script // scriptBytes = inBuffer.getBytes(); } /** * Return the serialized transaction output * * @param outBuffer Output buffer * @return Output buffer */ @Override public SerializedBuffer getBytes(SerializedBuffer outBuffer) { outBuffer.putLong(value.longValue()) .putVarInt(scriptBytes.length) .putBytes(scriptBytes); return outBuffer; } /** * Returns the serialized transaction output * * @return Serialized transaction output */ @Override public byte[] getBytes() { SerializedBuffer buffer = new SerializedBuffer(); return getBytes(buffer).toByteArray(); } /** * Returns the output amount * * @return Output amount */ public BigInteger getValue() { return value; } /** * Returns the transaction index for this output * * @return Transaction index */ public int getIndex() { return txIndex; } /** * Returns the script bytes * * @return Script bytes or null */ public byte[] getScriptBytes() { return scriptBytes; } /** * Checks if the output is spendable. This is done by checking for OP_RETURN * as the first script operation. Any script starting this way can never be * spent. Note that an empty script is always spendable. * * Proof-of-burn transactions are sent to '1CounterpartyXXXXXXXXXXXXXXXUWLpVr'. * This address has no private key and thus can never be spent. So we will * mark it as unspendable. * * @return TRUE if the output is spendable */ public boolean isSpendable() { boolean spendable = true; if (scriptBytes.length > 0) { if (scriptBytes[0] == ScriptOpCodes.OP_RETURN) spendable = false; else if (Arrays.equals(scriptBytes, unspendableScript)) spendable = false; } return spendable; } /** * Serializes this output for use in a transaction signature * * @param index Index of input being signed * @param hashType The signature hash type * @param outBuffer Output buffer */ public void serializeForSignature(int index, int hashType, SerializedBuffer outBuffer) { if (hashType == ScriptOpCodes.SIGHASH_SINGLE && index != txIndex) { // // For SIGHASH_SINGLE, we have a zero-length script and a value of -1 // outBuffer.putLong(-1L) .putVarInt(0); } else { // // Encode normally // outBuffer.putLong(value.longValue()) .putVarInt(scriptBytes.length) .putBytes(scriptBytes); } } }