-
Notifications
You must be signed in to change notification settings - Fork 22
Building scripts with the Tape API
Imagine a Bitcoin Script is like a tape, where each cell of the tape contains either some data (a vector of bytes), or an instruction (a single byte known as an Opcode).
When the tape is played back, each cell is evaluated from start to finish. If a cell contains a piece of data, that data is placed onto a "stack". If a cell contains an Opcode, then an instruction is carried out on the stack. Some Opcodes manipulate the order of the stack, some take one or more values from the stack as inputs and replace them with a computed output.
Bitcoin Script is a predicate function. This means it evaluates as true or false. When the tape has finished, if the top item of the stack contains a null value (an empty byte vector or a vector of zeros), then it evaluates as false. Otherwise it evaluates as true and the script is considered valid.
When a Bitcoin Script is truthy, it is a valid script and coins are about to be spent.
In the Cast lockingScript()
and unlockingScript()
functions, this.script
is an instance of Tape, which provides and API for building our script. Tape functions always return this
can be chained together.
Pushes a data value onto the tape. Accepts any of a Opcode integer, byte vector or string. If an array of valid values is given, each element is pushed onto the tape. Numbers should be encoded using the num()
helper.
this.script
.push('hello world')
.push(['hello', 'world'])
.push(num(24))
// script evaluates to ["hello world", "hello", "world", [24]]
Calls the macro function with the given args, and the Cast instance applied the this
context. Macros are useful for breaking up more complex sequences of script into re-usable functions.
function reverse(len) {
for (let i = 1; i < len; i++) {
this.script.push(OP_1).push(OP_SPLIT)
}
for (let i = 1; i < len; i++) {
this.script.push(OP_SWAP).push(OP_CAT)
}
}
this.script
.push([1, 2, 3, 4])
.apply(reverse, 4)
// script evaluates to [[4, 3, 2, 1]]
Iterates over the given elements invoking the callback on each. The callback will be invoked with the Cast instance as the this
context.
this.script
.each(['foo', 'bar'], (el, i) => {
this.script.push(el).push(num(i))
})
// script evaluates to ["foo", OP_0, "bar", OP_1]
Iterates the given number of times invoking the callback on each loop. The callback will be invoked with the Cast instance as the this
context.
this.script
.repeat(3, (i) => {
tape.push(OP_1)
tape.push(OP_SPLIT)
})
// script evaluates to [OP_1, OP_SPLIT, OP_1, OP_SPLIT, OP_1, OP_SPLIT]