The interpreter
Bitcoin4J has a complete Script interpreter, ported from the node software. We use it for two things: checking that a Transaction correctly spends the Outputs it claims, which the verifying a spend page covers, and running a script on its own to see what it does. The page on Bitcoin Script in the Developer Guide explains the execution model.
Running a script on its own
Section titled “Running a script on its own”Interpreter.executeScript() runs one script against a stack that we provide. Without a Transaction there is
nothing to sign over, so OP_CHECKSIG and its relatives throw an IllegalStateException, but everything else
works.
// We run a script on its own, without a Transaction, and look at the stack afterwards.Script script = new ScriptBuilder().number(2).number(3).op(OP_ADD).number(5).op(OP_EQUAL).build();LinkedList<byte[]> stack = new LinkedList<>();
Interpreter.executeScript(null, 0, script, stack, Coin.ZERO, EnumSet.of(Script.VerifyFlag.UTXO_AFTER_GENESIS));
byte[] top = stack.getLast(); // OP_EQUAL leaves 0x01 for true, an empty array for falseIn the preceding example executeScript() takes six parameters:
- the Transaction, or null when there is none,
- the index of the Input being run, which only matters with a Transaction,
- the script itself,
- the stack, which the interpreter changes in place,
- the amount of the Output being spent, again only needed for Signature checks,
- the verify flags.
The stack is a LinkedList<byte[]> with the top of the stack at the end. A script “ends true” when the top
item is non-zero.
Tracing execution
Section titled “Tracing execution”To watch a script run, we pass a ScriptTraceCallback, which is called after each opcode with the program
counter, the opcode, its name and both stacks.
Script script = new ScriptBuilder().number(2).number(3).op(OP_ADD).build();List<String> steps = new ArrayList<>();
ScriptTraceCallback tracer = (pc, opcode, opcodeName, stack, altStack) -> steps.add(pc + ": " + opcodeName + " (stack depth " + stack.size() + ")");
Interpreter.executeScript(null, 0, script, new LinkedList<>(), Coin.ZERO, EnumSet.of(Script.VerifyFlag.UTXO_AFTER_GENESIS), null, tracer);The seventh parameter is an optional Locking Script, which the interpreter only needs for
SIGHASH_CHRONICLE Signatures. We pass null for it here.
Do note that the names given to the callback have no
OP_prefix (ADD,DUP), and small numbers appear as2and3, even though the Javadoc showsOP_DUPas an example. It is a rather straightforward way of finding out where a custom script goes wrong.
Choosing the rules
Section titled “Choosing the rules”The flags decide which rules the interpreter applies. Here is a summary of them:
| Flag | Effect |
|---|---|
UTXO_AFTER_GENESIS |
The spent Output was created after Genesis: no limits on script, push or number size, restored opcodes, no P2SH, OP_CHECKLOCKTIMEVERIFY and OP_CHECKSEQUENCEVERIFY as no-ops |
AFTER_CHRONICLE |
The Chronicle rules, including SIGHASH_CHRONICLE Signatures and the restored opcodes. For Transactions of version 2 and up, correctlySpends() also drops SIGPUSHONLY, CLEANSTACK, MINIMALDATA, MINIMALIF, LOW_S, NULLFAIL and NULLDUMMY |
SIGHASH_FORKID |
Signatures must use the FORKID digest, as BSV requires |
STRICTENC, DERSIG, LOW_S |
Signatures and Public Keys must be strictly encoded |
NULLFAIL |
A failing Signature check must have been given an empty Signature |
MINIMALDATA |
Pushes and numbers must use their shortest encoding |
MINIMALIF |
OP_IF and OP_NOTIF arguments must be empty or 0x01 |
SIGPUSHONLY |
The Unlocking Script may only push data |
CLEANSTACK |
Exactly one item may remain on the stack (only together with P2SH) |
NULLDUMMY |
The extra item that OP_CHECKMULTISIG pops must be empty |
P2SH |
Evaluate P2SH redeem scripts (pre-Genesis only) |
CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY |
Enforce those opcodes (pre-Genesis only) |
DISCOURAGE_UPGRADABLE_NOPS |
Fail on the reserved OP_NOP opcodes |
COMPRESSED_PUBKEYTYPE |
Public Keys must be compressed |
Please note that
MONOLITH_OPCODESexists in the enum for compatibility, but has no effect. The opcodes restored in May 2018 are always enabled.
For a spend on BSV today, Fixtures.FLAGS on the verifying a spend page
is a sensible starting point. The pages on P2SH and
Hodlocker show how the pre-Genesis flags change the outcome.
The Genesis limits
Section titled “The Genesis limits”UTXO_AFTER_GENESIS makes a real difference to what a script may do. Here is one example of a limit that it
lifts.
// UTXO_AFTER_GENESIS lifts the old limits, e.g. on the size of numbers.// 0x0100000000 is a 5-byte number. Before Genesis, numbers were limited to 4 bytes.Script script = Script.fromAsmString("0100000000 OP_1 OP_ADD");
assertThrows(ScriptException.class, () -> Interpreter.executeScript(null, 0, script, new LinkedList<>(), Coin.ZERO, EnumSet.noneOf(Script.VerifyFlag.class)));assertDoesNotThrow(() -> Interpreter.executeScript(null, 0, script, new LinkedList<>(), Coin.ZERO, EnumSet.of(Script.VerifyFlag.UTXO_AFTER_GENESIS)));Before Genesis, numbers were limited to 4 bytes, pushes to 520 bytes and scripts to 500 opcodes. After Genesis, those limits are gone, which is what makes larger contracts possible on BSV.
Reading the errors
Section titled “Reading the errors”When a script fails, the interpreter throws a ScriptException, and getError() returns a ScriptError that
names the rule that failed.
// Mallory tries to spend Alice's Output with her own key.Transaction theft = new TransactionBuilder() .spendFromTransaction(new TransactionSigner(SIGHASH, mallory), fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, new P2PKHUnlockBuilder(mallory.getPublicKey())) .spendTo(new P2PKHLockBuilder(bobAddress), BigInteger.valueOf(99_000)) .build(true);
ScriptException e = assertThrows(ScriptException.class, () -> Fixtures.verifySpend(theft, 0, fundingTx.getOutputs().get(0)));ScriptError error = e.getError(); // SCRIPT_ERR_EQUALVERIFY, as the Public Key hash does not matchThe verifying a spend page has a table of the errors one meets most often
and their usual cause. getMessage() adds a little more detail.

