This scripting language was inspired by Bitcoin script. The main use envisioned is as a method for embedding ACL into decentralized data structures, e.g. hash DAGs. The core mechanisms under the hood are the following:
- Tape: the
Tapetakes the script bytes and advances a pointer as the bytes are read. It also keeps a dict of flags which control how certain ops execute and a dict of contracts mapping contract_id to dict contracts used for checking transfers between compatible protocols. - Stack: the
Stackprovides a memory structure similar to the stack used in Forth and Bitcoin script. Most ops interact with the stack, and most values are stored in the stack. The stack can contain onlybytesitems; each item is limited in size; and the number of items is also limited. - Cache: the
dictcache is where special values are held, e.g. a timestamp or the parts of a message to be used for checking signatures. Additionally, it is possible to move items from stack to cache or vice versa with the limitation that onlybytescache keys can be used for these operations while interpreter values are stored withstrcache keys and thus cannot be written by scripts.
The syntax of tapescript is simple and unforgiving, and it takes two forms: the bytes fed into the interpreter and the human-readable source code fed into the compiler which produces bytecode for the VM. The syntax for bytecode is just the op code followed by its arguments. The syntax for human-readable code is outlined below.
All symbols must be separated by whitespace, but which whitespace is used does not matter. (Making source code easy on the eyes is still recommended, as it is in any language; see the Style section below for detailed recommendations.) Any op name, value, or bracket/parenthesis is a symbol.
All values in human-readable tapescript are prefixed by an encoding char:
d: any value starting withdis a decimal int (e.g.d123)f: any value starting withfis a decimal float (e.g.f-3.21)s: any value starting withsis is a string (e.g.s"hello world")x: any value starting withxis hexadecimal bytes (e.g.x00ff)
Note that string escapes are not currently supported. It is especially important to note that an escaped quotation mark will not be interpreted by the compiler as escaped and will instead terminate the string and cause a syntax error.
Everything between two hashtags or double quotes is disregarded as a comment.
To call an op, write the op name followed by any argument(s). For example,
OP_PUSH s"hello world" will convert the utf-8 string "hello world" into bytes
and push it onto the stack, utilizing OP_PUSH1. Note that each op has an alias
equal to the name of the op without the OP_ prefix.
Some features are implemented using subtapes by reading a 2 byte uint size argument from the tape, then reading that many bytes from the tape as the subtape definition, then executing the subtape. Subtape definitions have a maximum size of 64KiB (2^16-1 bytes). The following features use subtapes:
OP_IFOP_IF_ELSEOP_DEFOP_TRY_EXCEPTOP_EVALOP_MERKLEVALOP_TAPROOTOP_LOOP
Note that OP_EVAL does not require any arguments because it reads the top item
from the stack as the subtape definition rather than parsing one out from the
tape. OP_MERKLEVAL and OP_TAPROOT call OP_EVAL if the supplied script
validates as part of the cryptographic commitment.
Tapescript includes three conditional operators: OP_IF, OP_IF_ELSE, and
OP_MERKLEVAL. OP_IF_ELSE is used under the hood for the human-readable
syntax of OP_IF ( hoisted_statements ) { if_body } ELSE { else_body }.
Unlike in c- type languages, the condition is pulled from the stack by OP_IF/
OP_IF_ELSE, so the condition is not provided as an argument to the operators.
OP_IF {
OP_PUSH s"true value found on the stack"
} ELSE {
OP_PUSH s"false value found on the stack"
}However, conditional statement hoisting is available, so any statements in
parentheses after OP_IF will be hoisted, e.g. IF ( <statements> ) { <body> }
will be compiled as <statements> IF { <body> }.
If OP_TRUE was called, or non-null bytes were put on the stack in some other
way, before this script ran, the first branch will execute. If OP_FALSE was
called, or if null bytes were put on the stack, before this script ran, the
second branch will execute.
The bodies for both clauses must be between parentheses, or the final clause
must be terminated by END_IF, e.g. OP_IF OP_DUP ELSE OP_POP0 END_IF.
Tapescript includes a streamlined mechanism for branching scripts that hide the
unexecuted branches behind cryptographic commitments. The syntax for a locking
script using this mechanism is simply OP_MERKLEVAL <root sha256 hash>. This
op reads 32 bytes from the tape as the root digest; calls OP_DUP then
OP_SHA256 twice; moves stack item at index 2 to the top and calls OP_SHA256
once; calls OP_XOR; calls OP_SHA256; pushes root hash onto the stack;
calls OP_EQUAL_VERIFY; then finally calls OP_EVAL.
The unlocking script must provide the code of the branch to execute, the hash of the unexecuted branch, and anything needed to execute the branch (in reverse order). This generalizes to any number of levels of branches, but there can be only two branches per level. These form a Merkle-tree like script structure.
See "Example 5: merklized script" in the script_examples.md
file for a thorough example of how this works and how it compares to using
OP_IF_ELSE for conditional execution and cryptographic script commitments.
Tools are provided that generate the locking and unlocking scripts for use with
OP_MERKLEVAL. See the "#### Merklized Scripts" section of the readme for more
details.
Tapescript includes an implementation of the taproot mechanism whereby a script
commitment and a public key are combined into a single root commitment which
allows for two execution branches: checking a signature against the root
commitment (which is a valid public key) and executing the committed script
after proving that the committed script and public key combine to form the root.
The locking script is push <root commitment> taproot <allowable sigflags>,
which is 36 bytes.
The key-spend unlocking script takes the following form: PUSH <sig>, where the
sig is a signature created with the private key corresponding to the committed
public key tweaked by adding an ed25519 scalar derived from
sha256(pubkey + sha256(script)). This signature then validates against the
root commitment, itself a tweaked public key.
The script-spend unlocking script takes the following form:
PUSH <script> PUSH <pubkey>. When the locking script runs, OP_TAPROOT will
verify that the supplied script and pubkey combine to form the root commitment,
then it will execute the script. Any additional conditions encoded in the script
must be fulfilled prior; in practice the committed script will be another
locking script, and the unlocking script will be a combination of the unlocking
script for the script and then the unlocking proof for OP_TAPROOT.
By using an OP_MERKLEVAL locking script as the committed script, OP_TAPROOT
provides an equivalent script experience as the Taproot+MAST upgrade to Bitcoin.
Some ops, such as OP_VERIFY and OP_CHECK_EPOCH_VERIFY, will raise exceptions
under certain conditions. If these ops are called within an OP_TRY block, the
exception will be caught, serialized, and put into the cache under the key x45,
then the EXCEPT block will be executed. Example:
OP_TRY {
OP_VERIFY
OP_PUSH x20
} EXCEPT {
OP_READ_CACHE x45
OP_PUSH s"ScriptExecutionError|OP_VERIFY check failed"
OP_EQUAL
}The above results in a 20 hexadecimal value if it executed without error,
true if it raised a ScriptExecutionError with the given error message, and
false is some other error was raised.
This feature can be combined with soft forks for conditional logic.
OP_TRY {
OP_PUSH s"this is a new feature"
OP_SOME_SOFT_FORK_RAISES_ERROR d1
OP_PUSH s"old nodes will always execute this"
} EXCEPT {
OP_PUSH s"old nodes will not execute this"
}A function can be defined using OP_DEF. Up to 256 functions can be defined,
and all statements between the opening and closing curly braces ({ and })
or before a terminal END_DEF will be executed when the function is called
using OP_CALL. Each definition is referenced by an integer 0-255. Note that
the definition number passed as an argument for OP_CALL must be a value like
any other, while the integer used with OP_DEF can be a plain int or a value.
Also note that the compiler currently does not support defining functions within other functions. The interpreter can probably handle it, but that would be undocumented behavior. If I get feedback that indicates this would be a useful feature to support, I will revisit the topic.
OP_DEF 0 {
OP_DUP
OP_SHA256
}
OP_DEF 1
OP_SHAKE256 d20
END_DEF
OP_PUSH x0123
OP_CALL d0
OP_CALL d1This will define two functions, put the 2 bytes x0123 onto the stack, then call
the two functions in sequence. The result will be a stack with x0123 and
shake_256(sha256(b'\x01\x23').digest()).digest(20).
As of 0.4.0, tapescript supports loops. Loop logic is similar to conditional
logic: using OP_LOOP { clause }, it is possible to run the clause in a loop
as long as the top value of the stack evaluates to True. For example, to count
down from 10 to 0:
push d10
loop {
push d1
swap2
subtract_ints d2
}If the top value evaluates to False, the loop code will not run. Additionally,
if the loop runs for more than the callstack_limit, a ScriptExecutionError
will be raised to prevent locking up the runtime with an infinite loop.
As of v0.3.0, the compiler supports macros and variables of a sort.
Macros are code templates that take in arguments and return the source code with the template values replaced by the macro arguments.
To define a macro, use the following syntax:
!= name [ arg1 arg2 etc ] {
OP_SOMETHING arg1
OP_PUSH arg2
OP_SOMETHING_ELSE
# etc #
}To invoke a macro, use the following:
!name [ arg1 arg2 etc ]Only positional arguments are supported, and the values supplied to macro calls must be valid. Each macro invocation will be followed by a separate compilation of just the resulting code, and that bytecode will be inserted if successful.
Variables are simply syntactic sugar for using the cache as a set of registers.
The syntax is simple: @= name [ values ] or @= name int to set and @name
to copy the values onto the stack. The first setting syntax pushes values onto
the stack and then puts them from the stack into the cache. The second setting
syntax simply pulls values from the stack. Variables cannot be used for on-tape
arguments to ops, e.g. OP_MERKLEVAL @root will not work. To see the number of
items held in a cache location, you can use rcz s"name" or @#name; this will
put the number of items onto the stack as an encoded signed int.
Note that reading and writing multiple values with either this or the assembly syntax will reverse the order of items.
Version 0.6.0 added two comptime features: inline compilation and inline
execution. The inline compilation feature syntax is ~ { ops } and replaces the
code section with a hexadecimal value symbol equal to the compiled ops. The
inline execution feature syntax is ~! { ops }; it compiles and executes the
ops, then pops the top item of the Stack, and replaces the code section with
that item as a hexadecimal value symbol -- if the Stack was empty, it outputs
nothing.
Because this is implemented as a preprocessor step, it is recursive. Example:
push ~! {
push ~! { push d1 random mod_int d2 }
push ~! { push d1 random mod_int d2 }
if ( equal ) {
push ~ { true }
} else {
push ~ { false }
}
}This will replace ~ { true } with the byte code for OP_TRUE as a hexadecimal
value, then it will replace ~ { false } with the byte code for OP_FALSE,
then it will replace ~! { push d1 random mod_int d2 } with a random byte mod 2
as a hex symbol, then it will compile and execute the whole OP_IF_ELSE, then
it will replace the whole outer comptime exec block with the result. It is a
contrived example, but it works and can be tested by copying and pasting into
the REPL (expect the stack to contain 0x00 half of the time and 0x01 the other
half of the time).
A more pragmatic example is to generate cryptographic commitments. Example from tests/vectors/correspondent_locking_script.src:
# locking script #
OP_DUP
OP_SHAKE256 d20
OP_PUSH ~! {
push ~ {
# committed script #
OP_IF {
OP_PUSH x09f5067410b240ac3aa3143016f2285f32fd6eb86ee0efe34248a25bb57bb937
OP_CHECK_SIG x00
} ELSE {
OP_PUSH x1481cd547c77799b4551f1e2947a9ad350bafe972ba55c827ef78279a096343f
OP_PUSH xcdf907630128847e63dc0b6156b331b29f56cf899e5689b61da3747382d1a80a
OP_SWAP d1 d2
OP_CHECK_SIG_VERIFY x00
OP_CHECK_SIG x00
}
}
shake256 d20
}
OP_EQUAL_VERIFY
OP_EVALThis will create a short (27 byte) locking script that is unlocked by satisfying the committed script and then pushing the committed script to the stack. The locking script will duplicate the top item on the stack, hash it, push the hash of the committed script, verify the top two items are equal, then execute the committed script.
While style of human-readable source scripts is not enforced, the following are encouraged:
- Each statement invoking an op should be on its own line except when a few such
statements are logically connected and result in one or zero values, e.g. two
PUSHops followed byADD_INTS d2or an op followed byVERIFY. - The bodies of functions and conditional clauses should be indented. I recommend 4 spaces per indentation level.
- The opening bracket of a function should be at the end of the line starting
OP_DEF, and the closing bracket of the function should be on its own line following the final statement of the function body. IfEND_DEFis used instead of brackets, then it should be on its own deindented line. - The opening parenthesis should be at the end of the line starting
OP_IForELSE, and the closing parenthesis should be on a new line following the final statement of the conditional clause body. ELSEshould be on the same line as the closing parenthesis of the previous conditional clause, i.e.} ELSE {should be its own line.- If
END_IFis used instead of a closing parenthesis, it should be on its own deindented line following the final statement of the conditional clause. - The type prefix of a value should be lowercase. If not, at least be consistent.
- The opening bracket of a try...except block should be at the end of the line
starting
OP_TRY, and the closing bracket be on its own line following the statements in the block. If anEXCEPTblock is specified, it should be on the same line as the closing bracket of the previous block. IfEND_TRYis used instead of a closing bracket, then it should be on its own line. - Brackets and parenthesis are recommended instead
END_DEF/END_IF/END_TRY. Choose a single convention and be consistent.
Below is a list of ops, the arguments for each, and a brief explanation of what each does. See docs.md for more in-depth details about each op.
In the op call syntax below, prefixed values within brackets are items on the stack, in the order in which they must be pushed onto the stack.
OP_FALSE- puts x00 onto stackOP_TRUE- puts xFF onto stackOP_PUSH val- putsvalonto stack; uses one ofOP_PUSH0,OP_PUSH1, orOP_PUSH2, depending on the size of thevalOP_PUSH0 val- putsvalonto stack;valmust be exactly 1 byteOP_PUSH1 size val- putsvalonto stack;valbyte length must be <256;sizemust be the length of thevalOP_PUSH2 size val- putsvalonto stack;valbyte length must be <65536;sizemust be the length of thevalOP_GET_MESSAGE sigflags- constructs the message from the sigfields and puts it onto the stack; runs signature extension plugins beforehandOP_POP0- takes the top item from the stack and puts in the cache at b'P'OP_POP1 count- takes the topcountitems from the stack and puts them in the cache at b'P'OP_SIZE- counts the number of items on the stack and puts it onto the stackOP_WRITE_CACHE size cache_key count- takescountnumber of items from the stack and stores them atcache_key;sizemust be the length ofcache_keyOP_READ_CACHE size cache_key- takes values from the cache atcache_keyand puts them onto the stack;sizemust be the length ofcache_keyOP_READ_CACHE_SIZE size cache_key- counts the number of items in the cache atcache_key;sizemust be the length ofcache_key[cache_key] OP_READ_CACHE_STACK- takes an item from the stack as a cache_key, reads the items in the cache at that location, and puts them onto the stack[cache_key] OP_READ_CACHE_STACK_SIZE- takes an item from the stack as a cache_key, counts the number of items in the cache at that location, and puts the count onto the stack[... int1] OP_ADD_INTS count- takescountnumber of ints from the stack, adds them together, and puts the sum onto the stack[... int1] OP_SUBTRACT_INTS count- takescountnumber of ints from the stack, subtractscount-1of them from the first one, and puts the difference onto the stack[int1 ...] OP_MULT_INTS count- takescountnumber of ints from the stack, multiplies them together, and puts the product onto the stack[int1] OP_DIV_INT size divisor- takes an int from the stack, divides it by thedivisor, and puts the quotient onto the stack;sizemust be the byte length of the divisor[int1 int2] OP_DIV_INTS- takes two ints from the stack, divides the first by the second, and puts the quotient onto the stack[int1] OP_MOD_INT size divisor- takes an int from the stack, divides it by thedivisor, and puts the remainder onto the stack;sizemust be the byte length of thedivisor[int2 int1] OP_MOD_INTS- takes two ints from the stack, dividesint1byint2, and puts the remainder onto the stack[... float1] OP_ADD_FLOATS count- takescountnumber of floats from the stack, adds them together, and puts the sum onto the stack[... float1] OP_SUBTRACT_FLOATS count- takescountnumber of floats from- the stack, subtracts
count-1of them from the first one, and puts the difference onto the stack [float1] OP_DIV_FLOAT divisor- takes a float from the stack, divides it bydivisor, and puts the quotient onto the stack;divisormust be a 4-byte float[float1 float2] OP_DIV_FLOATS- takes 2 floats from the stack, dividesfloat1byfloat2, and puts the quotient onto the stackOP_MOD_FLOAT divisor- takes a float from the stack, divides it bydivisor, and puts the remainder onto the stackOP_MOD_FLOATS- takes 2 floats from the stack, divides the second by the first, and puts the remainder onto the stack[... point1] OP_ADD_POINTS count- takescounted25519 points from the stack, adds them together, and puts the resulting ed25519 point onto the stack[item] OP_COPY count- copies the top value on the stackcounttimes[item] OP_DUP- duplicates the top stack value[item] OP_SHA256- replaces the top value of the stack with its sha256 digest[item] OP_SHAKE256 size- replaces the top value of the stack with itssizelength shake_256 digest[bool] OP_VERIFY- takes a value from the stack and raises an error if it does not evaluate toTrue[item1 item2] OP_EQUAL- takes 2 values from the stack and putsTrueonto the stack if they are the same orFalseif they are not[item1 item2] OP_EQUAL_VERIFY- callsOP_EQUALand thenOP_VERIFY[sig vkey] OP_CHECK_SIG allowed_flags- takes a VerifyKey and signature from the stack, builds a message from the cache valuessigfield[1-8]depending upon the sigflags allowed byallowed_flagsand appended to the signature, checks if the signature is valid for the VerifyKey and message, and putsTrueonto the stack if the signature validated orFalseif it did not[sig vkey] OP_CHECK_SIG_VERIFY allowed_flags- callsOP_CHECK_SIG allowed_flagsthenOP_VERIFY[constraint] OP_CHECK_TIMESTAMP- takes an unsigned int from the stack as a constraint, takes a timestamp from the cache at "timestamp", compares the timestamp to the constraint, and putsFalseonto the stack if the timestamp is less than the constraint or if the "ts_threshold" flag was set and exceeded by the difference between the timestamp and the current time (i.e. if the timestamp is more than ts_threshold into the future) and putsTrueonto the stack otherwise[constraint] OP_CHECK_TIMESTAMP_VERIFY- callsOP_CHECK_TIMESTAMPthenOP_VERIFY[constraint] OP_CHECK_EPOCH- takes an unsigned int from the stack as a constraint, subtracts the current time from the constraint, and putsFalseonto the stack if the "epoch_threshold" flag is met or exceeded by the difference and putsTrueonto the stack otherwise[constraint] OP_CHECK_EPOCH_VERIFY- callsOP_CHECK_EPOCHthenOP_VERIFYOP_DEF handle size def_body- defines a function; see section aboveOP_CALL handle- calls a function; see section above[bool] OP_IF length clause- runs conditional code; see section above[bool] OP_IF_ELSE length1 clause1 length2 clause2- runs conditional code; see section above[script] OP_EVAL- takes a value from the stack and runs it as a script[item] OP_NOT- takes a value from the stack and puts the inverse boolean value onto the stack[int] OP_RANDOM- pulls an int from the stack and puts a random byte string that long onto the stackOP_RETURN- ends the script; since functions and conditional clauses are run as subtapes,OP_RETURNends only the local execution and returns to the outer contextOP_SET_FLAG number- sets the tape flagnumberto the default valueOP_UNSET_FLAG number- unsets the tape flagnumberOP_DEPTH- puts the size of the stack onto the stack as signed int[...] OP_SWAP idx1 idx2- swaps the items at the given indices on the stack[item1 item2] OP_SWAP2- swaps the order of the top two items on the stack[...] OP_REVERSE count- reverses the order of the topcountitems on the stack[item2 item1] OP_CONCAT- takes two values from the stack, concatenatesitem2 + item1, and puts the result onto the stack[item] OP_SPLIT idx- takes a value from the stack, splits it at the givenidx, and puts the two resulting byte strings onto the stack[str1 str2] OP_CONCAT_STR- takes 2 utf-8 strings from the stack, concatenatesstr2 + str1, and puts the result onto the stack[str] OP_SPLIT_STR idx- takes a utf-8 string from the stack, splits atidx, and puts the 2 resulting strings onto the stack[...] OP_CHECK_TRANSFER- checks proofs of a transfer; see section below[commitment script] OP_MERKLEVAL hash- enforces cryptographic commitment to branching script; see section aboveOP_TRY_EXCEPT size1 try_body size2 except_body- executes the first block; if an exception is raised, it is serialized into a string and put on the stack, then the second block is executed[v2 v1] OP_LESS- pulls 2 valuesv1andv2from stack; puts(v1<v2)onto stack[v2 v1] OP_LESS_OR_EQUAL- pulls 2 valuesv1andv2from stack; puts(v1<=v2)onto stackOP_GET_VALUE key- puts the read-only cache value(s) at the strkeyonto the stack[f2 f1] OP_FLOAT_LESS- takes floatsf1andf2from the stack and puts(f1<f2)onto the stack[f2 f1] OP_FLOAT_LESS_OR_EQUAL- takes floatsf1andf2from the stack and puts(f1<=f2)onto the stack[int] OP_INT_TO_FLOAT- takes int from stack and puts it back as a float[f32] OP_FLOAT_TO_INT- takes float from stack and puts it back as an int[bool] OP_LOOP length clause- runs the clause in a loop as long as the top value on the stack is not null; errors if the callstack limit is exceeded[...] OP_CHECK_MULTISIG allowed_flags m n- takesnvkeys andmsignatures from stack; puts true onto the stack if each of the signatures is valid for one of the vkeys and if each vkey is used only once; otherwise, puts false onto the stack[...] OP_CHECK_MULTISIG_VERIFY allowed_flags m n- callsOP_CHECK_MULTISIGthenOP_VERIFY[seed] OP_SIGN flags- takes a signing key seed from the stack, signs a message constructed from sigfields not blanked by the flags, and puts that signature onto the stack.[message seed] OP_SIGN_STACK- takes a signing key seed and message from the stack, signs the message, and puts the signature onto the stack.[sig msg vkey] OP_CHECK_SIG_STACK- takes a verify key, message, and signature from the stack; putsTrueonto the stack if the signature was valid for the vkey and message, otherwise putsFalseonto the stack.[seed] OP_DERIVE_SCALAR- takes a seed from stack; derives an ed25519 private key scalar from it; puts the scalar onto the stack and into cache[b'x'] iftape.flags[1].[scalar] OP_CLAMP_SCALAR is_key- reads byte from tape as boolis_key; pulls a value from the stack; clamps the value as an ed25519 private key ifis_keyelse as normal scalar; puts clamped scalar onto the stack.[...] OP_ADD_SCALARS count- takescountvalues from stack; uses ed25519 scalar addition to sum them; put the sum onto the stack.[... minuend] OP_SUBTRACT_SCALARS count- takescountvalues from stack; uses ed25519 scalar subtraction to subtractcount-1values from the first value; put the difference onto the stack.[scalar] OP_DERIVE_POINT- takes a value from the stack as a scalar; generates an ed25519 curve point from it; puts the point onto the stack and into cache[b'X'] iftape.flags[2].[... minuend] OP_SUBTRACT_POINTS count- takescountvalues from the stack as ed25519 points; subtractscount-1of them from the first using ed25519 inverse group operator; puts difference onto the stack.[seed m T] OP_MAKE_ADAPTER_SIG_PUBLIC- takes tweak pointT, messagem, and prvkeyseedfrom stack; derives key scalarxfromseedand noncerfromseedandm; derives nonce pointRfromr; generates signature adaptersa; putsRandsaonto stack; sets cache[b'r'] toriftape.flags[3]; sets cache[b'R'] toRiftape.flages[4]; sets cache[b'T'] toTiftape.flags[6]; sets cache[b'sa'] iftape.flags[9].[m t seed] OP_MAKE_ADAPTER_SIG_PRIVATE- takes prvkeyseed, tweak scalart, and messagemfrom the stack; derives prvkey scalarxfromseed; derives pubkeyXfromx; derives private noncerfromseedandm; derives public nonce pointRfromr; derives public tweak pointTfromt; creates signature adaptersa; putsT,R, andsaonto stack; sets cache keys b't' totiftape.flags[5], b'T' toTiftapeflags[6], b'R' toRiftape.flags[4], and b'sa' tosaiftape.flags[8](can be used in code with @t, @T, @R, and @sa). Valuesseedandtshould be 32 bytes each. ValuesT,R, andsaare all public 32 byte values and necessary for verification;tis used to decrypt the signature.[sa R m T X] OP_CHECK_ADAPTER_SIG- takes public keyX, tweak pointT, messagem, nonce pointR, and signature adaptersafrom the stack; putsTrueonto the stack if the signature adapter is valid andFalseotherwise.[sa R t] OP_DECRYPT_ADAPTER_SIG- takes tweak scalart, nonce pointR, and signature adaptersafrom stack; calculates nonceRT; decrypts signaturesfromsa; putssonto stack; putsRTonto the stack; sets cache keys b's' tosiftape.flags[9]and b'RT' toRTiftape.flags[7](can be used in code with @s and @RT).[... argcount contract_id] OP_INVOKE- takes an item from the stack as a contract ID; takes a uint from the stack ascount; takescountitems from the stack as arguments; tries to invoke the contract'sabimethod, passing it the arguments; puts any return values onto the stack. RaisesScriptExecutionErrorif the contract is missing. RaisesTypeErrorif the return value type is not bytes or NoneType. If allowed bytape.flags[0], will put any return values into cache at key b'IR'.[item2 item1] OP_XOR- takes two items from the stack; bitwise XORs them together; puts result onto the stack. Can be used in boolean logic as boolean values are just bytes.[item2 item1] OP_OR- takes two items from the stack; bitwise ORs them together; puts result onto the stack. Can be used in boolean logic as boolean values are just bytes.[item2 item1] OP_AND- takes two items from the stack; bitwise ANDs them together; puts result onto the stack. Can be used in boolean logic as boolean values are just bytes.[...] OP_CHECK_TEMPLATE sigflags- pulls a template from the stack for every sigfield indicated in the sigflags and validates the associated sigfield against the template by running the "check_template" plugins or, if there are none, by doing an equality comparison; if all template checks pass, puts 0xff onto the stack, otherwise puts 0x00 onto the stack; runs the signature extension plugins beforehand iftape.flags[10]is set, which is default behavior.[...] OP_CHECK_TEMPLATE_VERIFY sigflags- runsOP_CHECK_TEMPLATE sigflagsthenOP_VERIFY[...] PUSH root OP_TAPROOT sigflags- if the 2nd item in the stack is a public key, verify the supplied script (3rd item from stack top) and the public key combine into the root using sha256 and ed25519, then execute the supplied script if they do or remove the script from the stack and put 0x00 onto the stack if they do not; else verify the 2nd item is a signature that validates against the root (top item) as the public key, and put 0xFF onto stack if it is or 0x00 onto the stack otherwiseNOP count- removescountvalues from the stack; dummy ops useful for soft fork updates
Pulls an item from the stack, interpreting as an unsigned int count;
takes an item from the stack as a contract_id; takes an item from the stack as
an amount; takes an item from the stack as a serialized constraint; takes an
item from the stack as a destination (address, locking script hash, etc);
takes the count number of items from the stack as sources; takes the count
number of items from the stack as txn_proofs; verifies that the aggregate of
the transfers to the destination from the sources equals or exceeds the
amount; verifies that the transfers were valid using the proofs and the
contract code; verifies the constraint was followed for each txn proof; and
puts True onto the stack if successful and False otherwise. Sources and
proofs must be in corresponding order on the stack.
For this to work, the contract must be loaded into the tape's contracts dict
at the bytes contract_id dict key. This can be done by passing a contracts dict
into run_script or run_auth_scripts. If the contract should be loaded for all
script executions, instead it can be added with add_contract(contract_id, contract).
The contract must be an instance of a class implementing the CanCheckTransfer
interface with following functions:
verify_txn_proof(txn_proof: bytes) -> boolverify_transfer(txn_proof: bytes, source: bytes, destination: bytes) -> boolverify_txn_constraint(txn_proof: bytes, constraint: bytes) -> boolcalc_txn_aggregates(txn_proofs: list[bytes], scope: bytes = None) -> dict[bytes, int]
The contract should be the source of the values put onto the stack and passed to
the contract functions by OP_CHECK_TRANSFER count or at least sharing an
interface with the source of those values.
The first three functions will be called on each transaction proof, and a False
returned for any of them will result in False placed onto the stack. Then,
calc_txn_aggregates will be called and supplied the list of txn proofs, and
the result for the destination will be taken out of the result of that function
call; if it is equal to or greater than the amount and all proofs were valid,
it puts True onto the stack, else it puts False onto the stack.
Takes an item from the stack as contract_id; takes a uint from the stack as
argcount; takes argcount items from the stack as arguments; tries to invoke
the contract's abi method, passing it the arguments; puts any return values
onto the stack. Raises ScriptExecutionError if the contract is missing or does
not implement the CanBeInvoked interface. Raises TypeError if the return
value type is not bytes or NoneType. If allowed by tape.flag[0], will put any
return values into cache at key b'IR'.
Example:
# file somecontract.py -- do not include this line in the file
from tapescript import int_to_bytes
class SomeContract:
def abi(self, args: list[bytes]) -> list[bytes]:
if not len(args):
return [b'\x00']
avg_size = sum([len(a) for a in args]) / len(args)
return [int_to_bytes(int(avg_size))]
# end of file somecontract.py -- there must be an empty line at the end of the file
# file bootstrap.py
from hashlib import shake_256
from inspect import getsource
from tapescript import add_contract
import somecontract
def boot():
contract_id = shake_256(bytes(getsource(somecontract), 'utf-8')).digest(20)
add_contract(contract_id, somecontract.SomeContract())
# file test.py
from bootstrap import boot
from tapescript import compile_script, run_script, bytes_to_int
boot()
script = '''
push xfeedbeef
push s"yellow submarine"
push d2
push x66b58394825b07bc65e504697654d7dd43640f26
invoke
'''
_, stack, cache = run_script(compile_script(script))
assert stack.size() == 1
assert bytes_to_int(stack.get()) == 10