Script templates
A wallet or an indexer regularly needs to answer the question: what kind of script is this, and what is in it?
The ScriptTemplateRegistry answers it. Each registered ScriptTemplate knows how to recognise one script
type and how to pull its fields out into a ScriptInfo. The page on
standard script templates in the Developer Guide
describes the common types.
Identifying a script
Section titled “Identifying a script”ScriptTemplateRegistry registry = ScriptTemplateRegistry.getInstance();
ScriptTemplate template = registry.identifyScript(lockingScript); // null if nothing matchesString name = template.getName();
ScriptInfo info = registry.extractScriptInfo(lockingScript);if (info instanceof P2PKHScriptInfo) { byte[] pubkeyHash = ((P2PKHScriptInfo) info).getPubKeyHash();In the preceding example:
identifyScript()returns the first template that matches, or null,extractScriptInfo()identifies the script and extracts its fields in one call, and also returns null when nothing matches,- each template has its own
ScriptInfosubclass, so we cast to get at the fields.
The built-in templates
Section titled “The built-in templates”List<String> names = new ArrayList<>();for (ScriptTemplate t : ScriptTemplateRegistry.getInstance().getAllTemplates()) { names.add(t.getName());}The registry comes with these templates, tried in this order:
| Name | Recognises | Info class |
|---|---|---|
AuthorIdentity |
Author Identity Protocol data | AuthorIdentityScriptInfo |
BProtocol |
B:// file data | BProtocolScriptInfo |
HODLocker |
Hodlocker time locks | HodlockerScriptInfo |
P2PKH |
Pay to public key hash | P2PKHScriptInfo |
P2PK |
Pay to public key | P2PKScriptInfo |
P2MS |
Bare multisig | P2MSScriptInfo |
P2SH |
Pay to script hash | P2SHScriptInfo |
OP_RETURN |
Data Outputs | OpReturnScriptInfo |
The order matters, since the more specific templates have to come first. A B:// Output is also an
OP_RETURNOutput, and the registry reports the first match.
Reading data Outputs
Section titled “Reading data Outputs”The OP_RETURN template gives us the pushes after OP_FALSE OP_RETURN as a list of byte arrays.
// We read the pushes back out of an OP_FALSE OP_RETURN Output.OpReturnScriptInfo info = (OpReturnScriptInfo) ScriptTemplateRegistry.getInstance().extractScriptInfo(dataScript);List<byte[]> pushes = info.getDataChunks();Reading multisig Outputs
Section titled “Reading multisig Outputs”P2MSScriptInfo info = (P2MSScriptInfo) ScriptTemplateRegistry.getInstance().extractScriptInfo(multisig);int threshold = info.getThreshold(); // 2int totalKeys = info.getTotalKeys(); // 3
// canBeSatisfiedBy() asks whether these keys meet the threshold.ScriptTemplate p2ms = ScriptTemplateRegistry.getInstance().getTemplate("P2MS");boolean twoKeys = p2ms.canBeSatisfiedBy(List.of(a, b), multisig);boolean oneKey = p2ms.canBeSatisfiedBy(List.of(a), multisig);canBeSatisfiedBy() tells a wallet whether the keys it holds are enough to spend an Output, without trying
to sign anything.
Registering our own template
Section titled “Registering our own template”A template implements four methods: getName(), matches(), canBeSatisfiedBy() and
extractScriptInfo(). Here is one for the hash puzzle from the custom builders
page.
/** Recognises OP_SHA256 <32 bytes> OP_EQUAL. */public class HashPuzzleTemplate implements ScriptTemplate {
/** What extractScriptInfo() returns for a matching script. */ public static class Info extends ScriptInfo { public final byte[] hash;
Info(byte[] hash) { super("HashPuzzle"); this.hash = hash; } }
@Override public String getName() { return "HashPuzzle"; }
@Override public boolean matches(Script script) { List<ScriptChunk> chunks = script.getChunks(); return chunks.size() == 3 && chunks.get(0).equalsOpCode(OP_SHA256) && chunks.get(1).data != null && chunks.get(1).data.length == 32 && chunks.get(2).equalsOpCode(OP_EQUAL); }
@Override public boolean canBeSatisfiedBy(List<PublicKey> keys, Script script) { return false; // satisfied by a secret, not by a key }
@Override public ScriptInfo extractScriptInfo(Script script) { if (!matches(script)) { throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Not a hash puzzle script"); } return new Info(script.getChunks().get(1).data); }}We register it once, and from then on the registry recognises the script.
ScriptTemplateRegistry registry = ScriptTemplateRegistry.getInstance();registry.register(new HashPuzzleTemplate()); // a template with the same name is replaced
Script puzzle = new HashPuzzleLockBuilder(secret).getLockingScript();HashPuzzleTemplate.Info info = (HashPuzzleTemplate.Info) registry.extractScriptInfo(puzzle);It should be noted that the registry is a single, shared instance, so a template we register is visible to the whole application. New templates go to the end of the list, after the built-in ones, and replacing a template by name keeps its place. The registry is also not synchronised, so the best place to register templates is at start-up, before any other thread uses it.

