The script interpreter
Interpreter is a full Bitcoin SV Script Interpreter. Its main entry point, correctlySpends, checks one
Input against the Output it spends. Verifying a spend covers that for signed
Transactions. On this page we look at scripts in general.
The examples use the keys and the fundingTransaction helper described in About the examples.
A script without Signatures
Section titled “A script without Signatures”The Interpreter always runs in the context of a spending Transaction, even when the script checks no Signature. We therefore build one around the scripts:
// Locking Script: whoever supplies two numbers that add up to 5 can spend.final lockingScript = SVScript.fromString('OP_ADD OP_5 OP_EQUAL');final fundingTx = fundingTransaction(lockingScript, BigInt.from(1000));
final unlockingScript = ScriptBuilder().number(2).number(3).build();final spendTx = TransactionBuilder() .spendFromTxn(fundingTx, 0, TransactionInput.MAX_SEQ_NUMBER, DefaultUnlockBuilder.fromScript(unlockingScript)) .spendToPKH(aliceKey.toAddress(networkType: NetworkType.TEST), BigInt.from(900)) .build(false);
Interpreter().correctlySpends(unlockingScript, lockingScript, spendTx, 0, {VerifyFlag.SIGHASH_FORKID, VerifyFlag.UTXO_AFTER_GENESIS}, Coin.ofSat(BigInt.from(1000)));Verification flags
Section titled “Verification flags”The flags select the consensus and policy rules. We pass them as a Set<VerifyFlag>.
| Flag | Effect |
|---|---|
SIGHASH_FORKID |
Signatures use the FORKID digest. Needed for any Bitcoin SV Signature. |
UTXO_AFTER_GENESIS |
The Output was created after Genesis: no script size or opcode limits, OP_RETURN ends the script successfully, and P2SH is not evaluated. |
AFTER_CHRONICLE |
The Chronicle rules: reinstated opcodes, larger numbers, and relaxed malleability rules for Transaction version 2 and above. |
P2SH |
Evaluate P2SH redeem scripts (pre-Genesis Outputs only). |
STRICTENC, DERSIG, LOW_S, NULLFAIL, NULLDUMMY, MINIMALDATA, MINIMALIF, SIGPUSHONLY, CLEANSTACK |
Policy rules on encodings and stack contents, as in the node. |
CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY |
Enable those opcodes (pre-Genesis). |
For an Output created after Genesis, {SIGHASH_FORKID, UTXO_AFTER_GENESIS} matches what the network checks. Add
AFTER_CHRONICLE for Transactions under the Chronicle rules.
Chronicle opcodes
Section titled “Chronicle opcodes”The Chronicle upgrade reinstated opcodes such as OP_2MUL, OP_2DIV, OP_SUBSTR, OP_LEFT, OP_RIGHT,
OP_LSHIFTNUM, OP_RSHIFTNUM, OP_VER, OP_VERIF and OP_VERNOTIF. They only run with AFTER_CHRONICLE:
// OP_2MUL is one of the opcodes the Chronicle upgrade reinstated.final preChronicle = {VerifyFlag.SIGHASH_FORKID, VerifyFlag.UTXO_AFTER_GENESIS};final postChronicle = {...preChronicle, VerifyFlag.AFTER_CHRONICLE};
// With preChronicle: throws ScriptException (SCRIPT_ERR_DISABLED_OPCODE).// With postChronicle: 3 * 2 == 6, the spend is valid.Interpreter().correctlySpends(unlockingScript, lockingScript, spendTx, 0, postChronicle, Coin.ofSat(BigInt.from(1000)));Tracing execution
Section titled “Tracing execution”Setting traceCallback lets us see each step: the chunk index, the opcode, and copies of the main and alt stacks
after that step. This is the tool to reach for when debugging a script that fails.
final steps = <String>[];final interpreter = Interpreter() ..traceCallback = (int chunkIndex, int opcode, List<List<int>> stack, List<List<int>> altStack) { steps.add('${OpCodes.getOpCodeName(opcode)} -> stack depth ${stack.length}'); };
interpreter.correctlySpends(unlockingScript, lockingScript, spendTx, 0, {VerifyFlag.SIGHASH_FORKID, VerifyFlag.UTXO_AFTER_GENESIS}, Coin.ofSat(BigInt.from(1000)));// steps: [OP_2 -> stack depth 1, OP_3 -> stack depth 2, OP_ADD -> stack depth 1,// OP_5 -> stack depth 2, OP_EQUAL -> stack depth 1]Failures throw a
ScriptException, whoseerrorfield names the rule (ScriptError.SCRIPT_ERR_EVAL_FALSE,SCRIPT_ERR_DISABLED_OPCODE, …). The Interpreter also prints a stack trace to the console when it throws.

