Skip to content

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.

ScriptTemplateRegistry registry = ScriptTemplateRegistry.getInstance();
ScriptTemplate template = registry.identifyScript(lockingScript); // null if nothing matches
String 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 ScriptInfo subclass, so we cast to get at the fields.
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_RETURN Output, and the registry reports the first match.

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();
P2MSScriptInfo info = (P2MSScriptInfo) ScriptTemplateRegistry.getInstance().extractScriptInfo(multisig);
int threshold = info.getThreshold(); // 2
int 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.

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.