Custom script builders
The transaction builder works with any script through two abstract classes:
LockingScriptBuilder, whosegetScriptPubkey()returns the Locking Script, and whoseparse(script)reads one back.UnlockingScriptBuilder, whosegetScriptSig()returns the Unlocking Script, built from thesignatureslist that signers fill, and whoseparse(script)reads one back.
The built-in P2PKH, P2PK, multisig and data builders are all implementations of these. On this page we will write a pair for a script of our own: coins that need both a secret and a Signature to spend.
The examples use the keys and the fundingTransaction helper described in About the examples.
A Locking Script builder
Section titled “A Locking Script builder”/// Locks coins to the SHA-256 hash of a secret, and to a Public Key hash./// Spending needs both the secret and a Signature from the key./// OP_SHA256 <hash> OP_EQUALVERIFY OP_DUP OP_HASH160 <pubkeyHash> OP_EQUALVERIFY OP_CHECKSIGclass HashLockBuilder extends LockingScriptBuilder { late List<int> secretHash; late List<int> pubkeyHash;
HashLockBuilder(this.secretHash, Address owner) : pubkeyHash = HEX.decode(owner.pubkeyHash160);
/// Reads the two hashes back out of an existing Locking Script. HashLockBuilder.fromScript(SVScript script) : super.fromScript(script);
@override SVScript getScriptPubkey() => ScriptBuilder() .opCode(OpCodes.OP_SHA256) .addData(Uint8List.fromList(secretHash)) .opCode(OpCodes.OP_EQUALVERIFY) .opCode(OpCodes.OP_DUP) .opCode(OpCodes.OP_HASH160) .addData(Uint8List.fromList(pubkeyHash)) .opCode(OpCodes.OP_EQUALVERIFY) .opCode(OpCodes.OP_CHECKSIG) .build();
@override void parse(SVScript script) { final chunks = script.chunks; if (chunks.length != 8 || chunks[0].opcodenum != OpCodes.OP_SHA256) { throw ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, 'Not a hash-lock script'); } secretHash = chunks[1].buf!; pubkeyHash = chunks[5].buf!; }}The fromScript constructor of the base class stores the script and calls parse, so a builder that implements
parse can read existing Outputs.
An Unlocking Script builder
Section titled “An Unlocking Script builder”/// Unlocking Script: <signature> <public key> <secret>./// The signer fills [signatures] when the transaction is built.class HashUnlockBuilder extends UnlockingScriptBuilder { final SVPublicKey publicKey; final List<int> secret;
HashUnlockBuilder(this.publicKey, this.secret);
@override SVScript getScriptSig() { if (signatures.isEmpty) return SVScript(); // before signing, the script is empty return ScriptBuilder() .addData(Uint8List.fromList(HEX.decode(signatures[0].toTxFormat()))) .addData(Uint8List.fromList(HEX.decode(publicKey.toHex()))) .addData(Uint8List.fromList(secret)) .build(); }
@override void parse(SVScript script) {} // not needed for spending}Do note that
getScriptSigis called before signing as well, when the builder serialises the Transaction to compute the digest. Return an empty script whilesignaturesis empty.
Using them
Section titled “Using them”We lock coins with spendToLockBuilder:
final secret = utf8.encode('correct horse battery staple');final locker = HashLockBuilder(sha256(secret), myAddress);
final lockTx = TransactionBuilder() .spendFromTxnWithSigner(DefaultTransactionSigner(sighashAll, aliceKey), fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, P2PKHUnlockBuilder(aliceKey.publicKey)) .spendToLockBuilder(locker, BigInt.from(50000)) .sendChangeToPKH(myAddress) .build(true);We spend them with the custom unlocker. The signer signs as usual, and adds its Signature to the unlocker’s
signatures:
final spendTx = TransactionBuilder() .spendFromTxnWithSigner(DefaultTransactionSigner(sighashAll, aliceKey), lockTx, 1, TransactionInput.MAX_SEQ_NUMBER, HashUnlockBuilder(aliceKey.publicKey, secret)) .spendToPKH(myAddress, BigInt.from(49900)) .build(true);And we read an Output back:
final parsed = HashLockBuilder.fromScript(lockedOutput.script);HEX.encode(parsed.pubkeyHash) == myAddress.pubkeyHash160; // trueFor a script that needs no Signature, return the script from getScriptSig directly, or wrap a ready-made script
in DefaultLockBuilder.fromScript / DefaultUnlockBuilder.fromScript. The
Interpreter page has an example.

