Building scripts
Every Locking Script and Unlocking Script is a Script, which is a list of chunks. Each chunk is
either an opcode or a push of data. The lock and unlock builders on the Transactions pages produce scripts for
the standard types. When we need something else, we build the script ourselves with a ScriptBuilder. The
page on Bitcoin Script in the Developer Guide explains how
scripts execute.
Building a script
Section titled “Building a script”Let’s build a P2PKH Locking Script by hand, one opcode at a time.
// import static org.twostack.bitcoin4j.script.ScriptOpCodes.*;byte[] pubkeyHash = Utils.HEX.decode("94837d2d5d6106aa97db38957dcc294181ee91e9");Script script = new ScriptBuilder() .op(OP_DUP) .op(OP_HASH160) .data(pubkeyHash) // picks the right push opcode for the length .op(OP_EQUALVERIFY) .op(OP_CHECKSIG) .build();
String asm = script.toAsmString();String hex = Utils.HEX.encode(script.getProgram());In the preceding example:
- the opcodes are constants on
ScriptOpCodes, which reads best with a static import, data()pushes a byte array, and picks the right push opcode for its length (a direct push up to 75 bytes, thenOP_PUSHDATA1,OP_PUSHDATA2orOP_PUSHDATA4),toAsmString()andgetProgram()give us the script as text and as bytes.
ScriptBuilderalso has static helpers for the common shapes, e.g.createP2PKHOutputScript(hash),createP2PKOutputScript(pubkey)andcreateOutputScript(address).
Pushing numbers
Section titled “Pushing numbers”Numbers in Script are little-endian, with a sign bit. number() takes care of the encoding for us.
Script script = new ScriptBuilder() .number(5) // 0..16 become OP_0..OP_16 .number(1000) // larger numbers become a minimal little-endian push .op(OP_ADD) .number(1005) .op(OP_EQUAL) .build();The values 0 to 16 become the single-byte opcodes OP_0 to OP_16, -1 becomes OP_1NEGATE, and anything
else becomes a minimal push.
data() follows the same minimal encoding. A one-byte array holding a value from 1 to 16 is written as the
opcode OP_1 to OP_16, not as a push of one byte, since that is the only form the MINIMALDATA rule accepts.
// A one-byte push of 0x01..0x10 becomes the opcode OP_1..OP_16, as the minimal encoding requires.Script script = new ScriptBuilder().data(new byte[]{0x05}).build();assertArrayEquals(new byte[]{(byte) OP_5}, script.getProgram());
// The OP_RETURN template reads it back as the one byte we pushed.Script dataScript = new ScriptBuilder().op(OP_FALSE).op(OP_RETURN).data(new byte[]{0x05}).build();OpReturnScriptInfo info = (OpReturnScriptInfo) ScriptTemplateRegistry.getInstance().extractScriptInfo(dataScript);Do note that the data builders on the data Outputs page use
data()as well, so a one-byte buffer from 1 to 16 lands in their scripts as an opcode. Code of our own that reads such scripts chunk by chunk has to expect that.
Parsing a script
Section titled “Parsing a script”Scripts reach us in three forms, and we can read all of them.
// ASM has opcodes by name and data as bare hex. It is the format bsv and DartSV use.Script fromAsm = Script.fromAsmString("OP_DUP OP_HASH160 94837d2d5d6106aa97db38957dcc294181ee91e9 OP_EQUALVERIFY OP_CHECKSIG");
// The bitcoind test-vector format writes data pushes as <length> 0x<hex>.Script fromBitcoind = Script.fromBitcoindString("OP_DUP OP_HASH160 20 0x94837d2d5d6106aa97db38957dcc294181ee91e9 OP_EQUALVERIFY OP_CHECKSIG");
// Raw bytes, e.g. from a Transaction Output.Script fromBytes = new Script(Utils.HEX.decode("76a91494837d2d5d6106aa97db38957dcc294181ee91e988ac"));All three give us the same Script. toAsmString() and toBitcoindString() write the two text forms back
out.
Walking through the chunks
Section titled “Walking through the chunks”getChunks() gives us the parsed chunks. A chunk with data is a push, and any other chunk is an opcode.
StringBuilder description = new StringBuilder();for (ScriptChunk chunk : script.getChunks()) { if (chunk.isPushData() && chunk.data != null) { description.append("push ").append(chunk.data.length).append(" bytes; "); } else { description.append(ScriptOpCodes.getOpCodeName(chunk.opcode)).append("; "); }}ScriptOpCodes converts between names and values in both directions. Please note that the names have no
OP_ prefix.
int opcode = ScriptOpCodes.getOpCode("CHECKSIG"); // names without the OP_ prefixString name = ScriptOpCodes.getOpCodeName(OP_CHECKSIG);Recognising script types
Section titled “Recognising script types”For the standard types, ScriptPattern has a test and an extractor for each.
Script script = ScriptBuilder.createOutputScript(address); // P2PKH for this Address
boolean isP2pkh = ScriptPattern.isP2PKH(script);boolean isOpReturn = ScriptPattern.isOpReturn(script);byte[] hash = ScriptPattern.extractHashFromP2PKH(script);Script.ScriptType type = script.getScriptType(); // P2PKH, P2PK, P2SH, or nullgetScriptType() returns P2PKH, P2PK or P2SH, and null for anything else. For more types, and to pull
the fields out of them, we use the Script templates.

