Skip to content

Parsing and serialising

Transactions travel as raw bytes, usually hex-encoded. That is what nodes and APIs return and accept. The transaction format page in the Developer Guide lays out the fields. The examples on this page parse a two-input testnet Transaction, RAW_TX.

Transaction tx = Transaction.fromHex(rawHex);
String txid = tx.getTransactionId(); // hex, in the byte order explorers display
long version = tx.getVersion();
long lockTime = tx.getLockTime();
List<TransactionInput> inputs = tx.getInputs();
List<TransactionOutput> outputs = tx.getOutputs();

getInputs() and getOutputs() return read-only views. We change a Transaction through addInput(), addOutput(), replaceInput(), replaceOutput(), setLockTime() and setVersion().

TransactionInput input = tx.getInputs().get(0);
String spentTxId = Utils.HEX.encode(input.getPrevTxnId()); // display byte order, like getTransactionId()
long spentIndex = input.getPrevTxnOutputIndex();
long sequence = input.getSequenceNumber();
String unlockingAsm = input.getScriptSig().toAsmString();

The byte order of Transaction ids. A Transaction id is a double SHA-256 hash, shown with its bytes reversed. getTransactionId() and TransactionInput.getPrevTxnId() both use that display order, so their hex can be compared directly and pasted into a block explorer. getTransactionIdBytes() returns the hash in internal order, which is the opposite.

TransactionOutput output = tx.getOutputs().get(0);
BigInteger satoshis = output.getAmount();
// We recognise the script and recover the Address it pays.
if (ScriptPattern.isP2PKH(output.getScript())) {
byte[] pubkeyHash = ScriptPattern.extractHashFromP2PKH(output.getScript());
Address paidTo = LegacyAddress.fromPubKeyHash(NetworkAddressType.TEST_PKH, pubkeyHash);

ScriptPattern recognises the common script types. The Script template registry does the same for more types, and extracts their fields as well.

Transaction tx = Transaction.fromHex(RAW_TX);
byte[] bytes = tx.serialize();
String hex = Utils.HEX.encode(bytes);
// Parsing from a stream works the same way, e.g. from a socket or a file.
Transaction fromStream = Transaction.fromStream(new ByteArrayInputStream(bytes));

Parsing and serialising round-trip exactly, so the hex we get out equals the hex we put in. new Transaction(ByteBuffer) is a third way in.

A parsed Input does not know what kind of script it spends, so its Unlocking Script sits in a DefaultUnlockBuilder, unchanged. To get at the Signature and Public Key, we parse the script with the builder for its type.

// A parsed Input keeps its Unlocking Script as-is, in a DefaultUnlockBuilder.
Transaction tx = Transaction.fromHex(RAW_TX);
UnlockingScriptBuilder unlocker = tx.getInputs().get(0).getUnlockingScriptBuilder();
// To work with the parts, we parse the script with the matching builder.
P2PKHUnlockBuilder p2pkh = new P2PKHUnlockBuilder(unlocker.getUnlockingScript());
String signerPubkey = p2pkh.getSignerPubkey().getPubKeyHex();

To check a parsed Transaction’s Signatures we need the Outputs it spends, since their scripts and amounts are not in the Transaction itself. We fetch the funding Transactions, and then use the interpreter.