Custom builders
The TransactionBuilder does not care which scripts it handles. It asks each Output’s LockingScriptBuilder
for a Locking Script, and each Input’s UnlockingScriptBuilder for an Unlocking Script. To use a
script of our own, we write a pair of small classes, and everything else (fees, change, signing, the checks
in build()) keeps working.
As an example, let’s lock coins with a hash puzzle. Anyone who knows the secret can spend the Output, and no key is involved. The Developer Guide’s page on Bitcoin Script explains how such a script evaluates.
Do note that a bare hash puzzle can be spent by anyone who sees the secret, and the secret becomes public as soon as the spending Transaction is broadcast. It is a teaching example, not a way to protect coins.
Writing a locking builder
Section titled “Writing a locking builder”A locking builder extends LockingScriptBuilder and implements getLockingScript().
/** Locks coins to whoever reveals a secret, with OP_SHA256 <sha256(secret)> OP_EQUAL. */public class HashPuzzleLockBuilder extends LockingScriptBuilder {
private final byte[] hash;
public HashPuzzleLockBuilder(byte[] secret) { this.hash = Sha256Hash.hash(secret); }
@Override public Script getLockingScript() { return new ScriptBuilder() .op(OP_SHA256) .data(hash) .op(OP_EQUAL) .build(); }}Our builder goes wherever the library takes a LockingScriptBuilder, e.g. in spendTo().
byte[] secret = "open sesame".getBytes(StandardCharsets.UTF_8);
// Our builder goes wherever the library takes a LockingScriptBuilder.Transaction puzzleTx = new TransactionBuilder() .spendFromTransaction(new TransactionSigner(SIGHASH, alice), aliceFunding, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(alice.getPublicKey())) .spendTo(new HashPuzzleLockBuilder(secret), BigInteger.valueOf(10_000)) .sendChangeTo(aliceAddress) .build(true);Writing an unlocking builder
Section titled “Writing an unlocking builder”An unlocking builder extends UnlockingScriptBuilder and implements getUnlockingScript().
/** Unlocks a HashPuzzleLockBuilder Output by pushing the secret. No Signature is involved. */public class HashPuzzleUnlockBuilder extends UnlockingScriptBuilder {
private final byte[] secret;
public HashPuzzleUnlockBuilder(byte[] secret) { this.secret = secret; }
@Override public Script getUnlockingScript() { return new ScriptBuilder().data(secret).build(); }}Since no Signature is needed, we spend the puzzle without a signer.
// No signer this time. The Unlocking Script needs no Signature, only the secret.Transaction claimTx = new TransactionBuilder() .spendFromTransaction(puzzleTx, 0, TransactionInput.MAX_SEQ_NUMBER, new HashPuzzleUnlockBuilder(secret)) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(9_744)) .build(true);The test behind this example runs the spend through the interpreter, and checks that a wrong secret is rejected.
Builders that need a Signature
Section titled “Builders that need a Signature”When an Input has a signer, TransactionSigner.sign() hands the finished TransactionSignature to the
unlocker through addSignature(), and the unlocker reads it back with getSignatures() when the builder asks
for the Unlocking Script. That is all a signature-aware builder needs to do.
This one writes OP_0 followed by each Signature in the order they were added. It is a minimal version of the
library’s own P2MSUnlockBuilder, used on the multisig page, and a good
starting point for any script that needs Signatures from the signer.
/** * Unlocks a bare multisig Output with OP_0 <sig 1> ... <sig m>. * TransactionSigner.sign() hands us each Signature through addSignature(), in the order we sign. */public class MultisigUnlockBuilder extends UnlockingScriptBuilder {
@Override public Script getUnlockingScript() { ScriptBuilder builder = new ScriptBuilder().op(ScriptOpCodes.OP_0); // the dummy item that CHECKMULTISIG pops try { for (TransactionSignature signature : getSignatures()) { builder.data(signature.toTxFormat()); // the DER Signature plus its Sighash byte } } catch (IOException e) { throw new UncheckedIOException(e); } return builder.build(); }}Please note that an Unlocking Script must push each Signature with its Sighash byte, which is what
TransactionSignature.toTxFormat()gives us.getSignatureBytes()leaves that byte out, and the spend is rejected.
Parsing an existing script
Section titled “Parsing an existing script”The library’s builders also have a constructor that takes a Script, and fill in their fields from it. For our
own scripts we can do the same, or describe them to the Script template registry,
which also lets the rest of an application recognise them.
For a one-off script that needs no logic at all,
DefaultLockBuilder(script)andDefaultUnlockBuilder(script)wrap a finishedScriptas it is.

