This is the specification for TxVM, the transaction virtual machine.
TxVM defines a procedural representation for blockchain transactions and the rules for a virtual machine to interpret them and ensure their validity.
Earlier versions of the Chain Protocol (and other blockchain systems) represent transactions with a static data structure, exposing the pieces of information — the inputs and outputs, with their associated fields — needed to test the transaction’s validity. An ad hoc set of validation rules applied to that information produces a true/false result.
In TxVM, this model is inverted. A transaction is a program that runs in a specialized virtual machine that embodies validation rules. The result of the transaction is information about inputs and outputs, guaranteed valid by the successful completion of the transaction program.
The “scripts” that in other blockchain systems are used to lock and unlock pieces of blockchain value are, in TxVM, simply subroutines of the overall transaction program. This allows the creation of sophisticated, secure value flows that are difficult or impossible to do otherwise.
The transaction program, together with a version number and runlimit, is called the transaction witness. It contains all data and logic required to produce a unique transaction ID. It also contains any necessary signatures (although these typically appear after “finalization” and don’t contribute to the transaction ID).
A witness program runs in the context of a stack-based virtual machine. When the virtual machine executes the program, it creates and manipulates data of various types: plain data, including integers, strings, and tuples; and special entry types that include values and contracts. A value is a specific amount of a specific asset that can be merged or split, issued or retired, but not otherwise created or destroyed. A contract encapsulates a program (as TxVM bytecode) plus its runtime state, and once created must be executed to completion or persisted in the global state for later execution.
Some TxVM instructions (such as storing a contract for later execution) propose alterations to the global blockchain state. These proposals accumulate in the transaction log, a data structure in the virtual machine that is the principal result of executing a transaction. Hashing the transaction log gives the unique transaction ID.
A TxVM transaction is valid if and only if it runs to completion without encountering failure conditions and without leaving any data on the virtual machine’s stacks.
After a TxVM program runs, the proposed state changes in the transaction log are compared with the global state to determine the transaction’s applicability to the blockchain.
The items on TxVM stacks are typed. The available types fall into two broad categories: plain data and entries.
Plain data items can be freely created, copied, and destroyed. Entries are subject to special rules as to when and how they may be created and destroyed, and may never be copied.
TxVM supports these plain data types:
Items of these types can be freely created, copied, and destroyed.
A signed 64-bit integer, i.e. an integer between -2^63 and 2^63 - 1.
A byte string with length between 0 and 2^31 - 1 bytes.
An immutable sequence of zero or more plain data items.
TxVM supports these entry types:
Items of these types may not be created, destroyed, or copied except as described below.
A value is a specific amount of a specific asset type. Values are created with issue and destroyed with retire. As a special case, zero-amount values may be created with nonce and destroyed with drop. A value may be split into two values adding to the same amount, and two values of like type may be merged together.
Each value also contains an anchor, a string derived from the nonce, issuance, or upstream value(s) that created it. Anchors ensure the global uniqueness of distinct values even when they have identical amounts and asset types. Anchors also prevent “replay attacks.”
Values are secured by “locking them up” inside contracts. Each contract has its own stack where values (and other data) may be stored, and also its own program expressing the conditions for releasing or otherwise manipulating any values it controls.
A value can be converted to plain data by rendering it as a tuple containing these items:
"V"
, a string, the type code for converted values.amount
, an integer.assetid
, a string.anchor
, a string.
A contract is a program (a string of bytecode) and associated storage in the form of a stack. Contracts are created with the contract instruction and destroyed by running them to completion, at which point the contract’s stack must be empty.
Each contract also contains a seed which is a hash of the initial program with which the contract was created. The program can change during the contract’s lifecycle — the output, yield, and wrap instructions all update it — but the seed will not.
A running contract that has not yet completed may remove itself from the current transaction (and return control to its caller) with the output instruction. Such a contract is stored in the global blockchain state. It may be recovered from the global state with the input instruction, whereupon it can resume execution with the stack contents it had before. Both actions are recorded in the transaction log.
A contract may also suspend its execution with the wrap and yield instructions.
A contract can be converted to plain data by rendering it as a tuple containing these items:
"C"
, a string, the type code for converted contracts.seed
, a string.program
, a string.
followed by the contents of its stack as zero or more items, each item converted. The bottom item of the stack appears first and the top item appears last.
The plain data tuple representation of a contract is used as an argument to the input instruction, which “un-converts” it, reconstituting it as a callable contract object. (The output instruction stores only the contract’s snapshot ID in the global state. It is the caller’s responsibility when using input to construct a tuple producing the same snapshot ID as was previously stored.)
A wrapped contract is a contract that has been made portable with the wrap instruction. When called, a wrapped contract automatically “unwraps” and becomes a plain contract.
A wrapped contract can be converted to plain data by rendering it as a tuple containing these items:
"W"
, a string, the type code for converted wrapped contracts.seed
, a string.program
, a string.
These are followed by the contents of the stack as zero or more items, each item converted. The bottom item of the stack appears first and the top item appears last.
The plain data and entry types described above are all the types defined by TxVM. However, in some contexts, values of the built-in types are used as if they were other, domain-specific types. This section describes how certain types are sometimes interpreted.
A boolean is a true-or-false value. Any plain data value may be
interpreted as a boolean. All values are “true” except for the integer
0
, which is “false.” (Note, empty tuples and empty strings are also
“true.”) Operations that produce booleans produce 0
for false and
1
for true.
Important: Entry types cannot be interpreted as booleans. Operations that expect a boolean value fail execution if it is not a plain data item.
A program is a string containing a sequence of TxVM instructions. Each instruction is an opcode optionally followed by immediate data.
The opcode is a variable-length unsigned integer encoded using LEB128.
The immediate data is 0 or more bytes, depending on the opcode.
All opcodes below 0x5f (95 in decimal) have no immediate data.
Opcodes starting with 0x5f are pushdata
instructions. Each is followed by opcode - 95
bytes of immediate
data. (So 0x5f is followed by zero bytes of immediate data and pushes
a zero-byte string to the stack, 0x60 is followed by one byte of
immediate data and pushes that as a one-byte string to the stack, 0x61
is followed by two bytes, and so on.)
A witness program is a program (a string of bytecode) specified by a transaction witness, which runs as a “top-level contract” during TxVM execution.
A contract snapshot is a tuple representing the complete state of a contract at the point that its execution is suspended with the output instruction.
See also snapshot ID.
A transaction witness is a tuple with the following fields:
version
, an int.runlimit
, an int.program
, a program (i.e. a string of bytecode), referred to as the witness program.
When a contract executes the wrap instruction to become a wrapped contract, or when it is snapshotted by the output instruction, every item on its stack must be portable. It is an error at such times for the stack to contain any non-portable items.
All plain data items (integers, strings, and tuples) are portable, as are values and wrapped contracts. Ordinary contracts are not portable.
Rules regarding the handling of values and contracts ensure that all transactions balance and that values cannot be created except as authorized, or destroyed without leaving a record.
These rules can be thought of as the conservation laws of TxVM.
The most important law is that a transaction cannot complete successfully unless the top-level contract stack and the argument stack are both empty. Values and contracts are limited in the ways they can be removed from stacks once added:
- A contract is removed if it runs to completion (with its own stack empty);
- A value is removed if destroyed with the retire instruction, which creates a record in the transaction log;
- A contract is removed, together with the contents of its stack, if it uses the output instruction, which creates a record in the transaction log and an entry in the global blockchain state allowing the contract (and its stack) to be recreated later.
Because of this law, every contract must execute to completion or invoke output; and every value must be safely locked in a contract that invokes output, or be retired.
New values may be created with the issue instruction. The
asset type of the value is given by its asset ID, an
identifier derived from the contract containing the issue
instruction that created it. An issue
instruction in any other
contract necessarily creates values of a different asset type. In this
way, an asset’s issuance contract is unique and solely responsible for
deciding when issuance is authorized.
Values with a zero amount may be copied and dropped from the stack freely, like plain data. Zero-amount values are useful for the anchors they contain.
Non-zero values never exist in two places at once. They may not be copied or dropped, but they may be moved between stacks with get and put.
When retire is used to destroy a value, a retirement entry is added to the transaction log.
The split instruction turns a single value into two values with the same sum. The merge instruction turns two values of like type into a single value whose amount is the sum of the inputs.
A contract never exists in two places at once. It cannot be destroyed or stored anywhere without its own permission.
When a contract is created with the contract instruction, it exists on a parent contract’s stack.
When a contract is called with call, it is removed from the caller’s stack and exists transiently in the VM's internal call stack.
When a contract completes execution with an empty contract stack, it is destroyed by the VM.
When a contract suspends execution via yield, it is placed back on the argument stack.
When a contract suspends execution via output, it is moved (in the form of its snapshot ID) to the global blockchain state.
When a contract is recreated using input, it is pushed to the contract stack and its snapshot ID is removed from the global blockchain state.
When a contract is wrapped, it is returned to the argument stack in a portable form. A portable contract can be stored inside other contracts or called.
(If wrapped contracts can be called like normal contracts, why have
the wrapped/unwrapped distinction at all? It’s to ensure that a
contract may not be persisted to the global state (on the stack of a
parent contract) without its own permission. The only way to wrap a
contract, after all, is for the contract itself to execute the wrap
instruction.)
TxVM is incompatible with versions 1 and 2 of the Chain protocol. The version number for TxVM transactions and blocks therefore starts at 3.
Forward- and backward-compatible upgrades (“soft forks”) are possible
with extension instructions, enabled by the
extension flag and higher version numbers. It is
possible to write a compatible contract that uses features of a newer
transaction version while remaining usable by non-upgraded software
(that understands only older transaction versions) as long as
new-version code paths are protected by checks for the transaction
version. To facilitate that, a hypothetical TxVM upgrade may introduce
an extension instruction “version assertion” that fails execution if
the version is below a given number (e.g. 4 versionverify
).
The argument stack is a shared stack used to pass items between contracts.
The argument stack may contain plain data items and entries. Items are moved from the current contract stack to the argument stack using the put instruction, and retrieved using the get instruction.
Each contract has its own contract stack.
A contract stack may contains plain data items and entries. When a contract is running (via the call instruction), its stack becomes the current contract stack. All stack-affecting instructions operate on the current contract stack. The get and put instructions move items between the current contract stack and the argument stack.
If a contract executes the wrap or output instruction, its stack must contain no non-portable types.
The transaction log contains tuples that describe the effects of various instructions.
The transaction log is empty at the beginning of a TxVM program. It is append-only. Items are added to it upon execution of any of the following instructions:
The details of the item added to the log differs for each instruction. See the instruction’s description for more information.
The finalize
instruction prohibits further changes to the
transaction log. Every TxVM program must execute finalize
exactly
once.
TxVM defines a family of hash functions, collectively denoted
VMHash(F,X)
, based on SHA-3 Derived Functions as specified by
NIST SP 800-185.
Each hash function has a variable function name string F
that is
appended to a constant customization string S
(in NIST terms) to
allow efficient precomputation of any specific hash function
instance. (This is due to customization strings being padded to 168
bytes that are fully permuted using the Keccak function.)
The value of S
for all hash functions in this specification is
ChainVM.
For a given “function name” F
, VMHash(F,X)
is a secure hash
function that takes an input string X
and outputs a 256-bit hash.
VMHash(F,X) = cSHAKE128(X, L=256, N="", S="ChainVM." || F)
This document gives specific values of F
for different uses of
VMHash
. See details below.
The unique ID of a transaction is computed from the
transaction log after execution of the
finalize instruction, which prohibits further changes to
the log. A TxVM program that does not execute finalize
does not have
a transaction ID.
To compute the transaction ID, each item in the log is serialized and a merkle binary tree is constructed using these serialized items. The ID is the root hash of the tree.
txid = MBTH({serialize(firstitem), ..., serialize(lastitem)})
The seed of a contract is a hash of the program
argument used in
the contract instruction:
contractseed = VMHash("ContractSeed", program)
It remains the same even as a running contract’s changes during its lifecycle (via yield, wrap, or output).
Note 1: The contract seed that a given program will produce can be
computed with the sequence <program> "ContractSeed" vmhash
.
Note 2: The contract seed of the currently running contract is accessible via the self instruction.
Note 3: The contract seed of another contract is accessible via the seed instruction.
When the issue instruction is executed, the resulting value
has an asset ID that is a hash of the seed of the
issuing contract combined with a customization tag
:
assetid = VMHash("AssetID", contractseed || tag)
Note: The asset ID that a given contract and tag will produce can be
computed with the sequence <contractseed> <tag> cat "AssetID" vmhash
.
The snapshot ID of a contract at a given point in its execution is a hash of the serialized snapshot:
snapshotid = VMHash("SnapshotID", serialize(snapshot))
The snapshot ID is used by the output and input instructions.
Execution of a TxVM transaction happens in a virtual machine. Successful completion proves the validity of the transaction and produces a log of global state changes to be applied to the blockchain.
Note: It is important to distinguish validation of a transaction, which happens in isolation, from application of a transaction to the blockchain state. A transaction that is valid in isolation may still be invalid in the context of the blockchain — if, for instance, it tries to spend some value that has already been spent elsewhere.
(Here, “spending value” specifically means “reconstituting a contract with the input instruction whose snapshot ID does not appear in the global blockchain state.”)
The TxVM virtual machine is a state machine with these attributes:
- Extension flag
extension
(boolean) - Finalized flag
finalized
(boolean) - Unwinding flag
unwinding
(boolean) - Runlimit
runlimit
(int) - Caller Seed
caller
(string) - Argument stack
argstack
(a stack of data items) - Current contract
currentcontract
(a tuple of a stack of data items, a seed, and a current program) - Transaction log
log
(a sequence of tuples)
- The VM is initialized with a
transaction witness
txwitness
as follows:extension
flag set to true or false according to the transaction versioning rules fortxwitness.version
,version
integer set totxwitness.version
,finalized
flag set tofalse
,unwinding
flag set tofalse
,runlimit
set to thetxwitness.runlimit
,caller
set to an all-zero 32-byte string,argstack
empty,currentcontract
with:stack
: empty,seed
: an all-zero 32-byte string,program = txwitness.program
.
log
empty
- The VM runs
txwitness.program
. - Execution fails if any of the following is true:
- Remaining runlimit is negative,
- The argument stack or current contract stack is non-empty.
- Results are the VM’s
finalized
flag and itslog
. Iffinalized
is true, a transaction id may be computed from thelog
.
Note 1: Remaining runlimit in step 3 is allowed to be greater than 0, in part to accommodate future extensions.
Note 2: The witness program (i.e., the top-level contract) may end with output, saving itself to the global blockchain state and leaving an empty stack. It may not end with wrap or yield, since that would leave the contract on the argument stack, causing execution failure.
Note 3: After execution, the resulting transaction log is further validated against the blockchain state, as discussed above. That step, called application, is described in the blockchain specification.
A program is a sequence of instructions represented as bytecode. A run executes the instructions one after another. A program counter records the byte position of the next instruction. It begins at zero and advances as each instruction is decoded and executed. The jumpif instruction can set the program counter to a value other than the position of the next instruction, causing execution to branch to the new location.
A new instruction is executed only if vm.unwinding
is false. A run
terminates normally after the program’s last instruction is
executed. It terminates abnormally if vm.unwinding
becomes true.
Each instruction consumes some amount of the VM
runlimit. If vm.runlimit
is exhausted (drops below
zero), execution fails.
Runs may nest, as when one program invokes another via call
or exec. When an inner run terminates normally, the outer run
resumes where it left off. When an inner run terminates abnormally,
the outer run also terminates, unless the outer run was begun with
call
. See call for details. There is no resuming from an
execution failure.
Note 1: The purpose of the vm.unwinding
flag is to terminate programs
early that were started with exec, backing out to the nearest
enclosing call and resuming from there.
Note 2: It is useful to distinguish a program, which is a simple sequence of instructions, from a contract, which contains a program plus some other information. A bare program is executed with exec. The program in a contract is executed with call.
- Each transaction has a version number. Each block also has a version number.
- All TxVM transaction witness tuples must have transaction version 3 or greater. This is to avoid confusion with transactions from earlier iterations of Chain’s blockchain protocol.
- Blocks that include TxVM transactions must have version 3 or greater. This is also to avoid confusion with blocks from earlier versions of Chain software.
- Block version numbers must be monotonically non-decreasing: each block must have a version number equal to or greater than the version of the block before it.
- The current block version is 3. The current transaction version is 3.
Extensions:
- If the block version is equal to the current block version, no transaction in the block may have a version higher than the current transaction version.
- If a transaction’s version is higher than the current transaction
version, the TxVM
extension
flag is set totrue
. Otherwise, theextension
flag is set tofalse
.
The runlimit specified by a transaction witness is an amount of abstract “cost” units that the VM is allowed to use during execution. Every instruction executed decreases the remaining available runlimit. A transaction that exhausts its runlimit fails execution.
Runlimits enable fine-grained accounting for the operational costs of the network.
- Blocks commit to a total runlimit for the transactions they contain. This total is greater than or equal to the sum of the runlimits specified in the block’s transactions. The block runlimit total may exceed the sum of the transaction runlimits if needed for flexibility and future extensions.
- The VM is initialized with the runlimit specified in a transaction witness.
- Each instruction reduces the VM’s runlimit according to the total cost, consisting of:
- Copy- or item-creation costs are described along with each instruction in the sections below, where applicable. The base cost applies to all instructions and is not explicitly described.
- If the runlimit goes below zero before the program counter reaches the length of the program, execution fails.
- Execution of the transaction can leave some runlimit unconsumed. This is allowed for future extensions.
- The runlimit specified in a transaction witness must be equal to or greater than the length of the transaction witness’s program (in bytes). This is to allow early detection and abort when receiving intractably long program strings.
Each instruction immediately costs 1
when executed.
Each created string costs 1 + n
where n
is the length of the
string in bytes.
Each created tuple costs 1 + n
where n
is the number of items in
the tuple.
Each created entry (values, contracts, and wrapped
contracts) costs 128
which reflects the cost of underlying hash
operations.
- Ints have zero copy cost.
- Strings have copy cost equal to string cost.
- Tuples have copy cost equal to tuple cost.
Copy cost does not apply to entries.
Every plain data item can be serialized as a TxVM program fragment that produces the item when executed.
- A string is encoded as a pushdata instruction with the string as the instruction’s “immediate data.”
- Integers in the range 0–19 inclusive are encoded as the appropriate small integer opcode.
- All other integers are first encoded with LEB128. The result is serialized with a pushdata instruction and the encoded integer as immediate data, followed by an int instruction.
- A tuple is encoded recursively as the encoding of each item in the
tuple, followed by the encoding of integer
n
wheren
is the length of the tuple, followed by the tuple instruction.
A serialized data item can be passed to exec to deserialize the item and push it to the current contract stack.
The output and input instructions must represent contracts as plain data, even when their stacks may contain non-plain entries. To do this, they employ a conversion procedure as follows:
- An item of any plain data type is converted to a 2-element tuple: a type code (a string) and a copy of that item. See the table below.
- A contract with
n
items on its stack is converted to ann+3
-item tuple consisting of the type code"C"
, the contract’s seed, the contract’s current program, and converted copies of then
stack items, from bottom-most to top-most. - A wrapped contract is converted as a contract,
but with type code
"W"
. - A value is converted to a four-item tuple: the type code
"V"
, its integer amount, its asset ID (as a string), and its anchor (as a string).
Type | Type code | Example input | Example output |
---|---|---|---|
Int | "Z" |
123 |
{"Z", 123} |
String | "S" |
"abc" |
{"S", "abc"} |
Tuple | "T" |
{123,"abc"} |
{"T", {123,"abc"}} |
Value | "V" |
<Value> |
{"V", <amount>, <assetid>, <anchor>} |
Contract | "C" |
<Contract> |
{"C", <seed>, <program>, convert(bottomitem), ..., convert(topitem) } |
Wrapped Contract | "W" |
<WrappedContract> |
{"W", <seed>, <program>, convert(bottomitem), ..., convert(topitem) } |
Note: Transaction log items resemble converted data items, in that they are tuples with a leading type code. Their codes are chosen to avoid accidental ambiguity with the conversion type codes above.
Logging operation | Type code | Example log entry |
---|---|---|
input | "I" |
{"I", vm.currentcontract.seed, inputid} |
output | "O" |
{"O", vm.caller, outputid} |
log | "L" |
{"L", vm.currentcontract.seed, item} |
timerange | "R" |
{"R", vm.currentcontract.seed, min, max} |
nonce | "N" |
{"N", vm.caller, vm.currentcontract.seed, blockid, exp} |
issue | "A" |
{"A", contextid, amount, assetid, anchor} |
retire | "X" |
{"X", vm.currentcontract.seed, amount, assetid, anchor} |
finalize | "F" |
{"F", vm.currentcontract.seed, vm.version, anchor} |
This table shows the numeric opcode for all instructions up to 0x5f, which is “pushdata 0” (for pushing a zero-byte string to the current contract stack). Higher-numbered opcodes are “pushdata N” instructions, where N is the opcode minus 0x5f.
Smallints | Smallints | Int/Stack | Values/Crypto/Tx | Control flow | Data |
---|---|---|---|---|---|
00 0 / false |
10 16 |
20 int |
30 nonce |
40 verify |
50 eq |
01 1 / true |
11 17 |
21 add |
31 merge |
41 jumpif |
51 dup |
02 2 |
12 18 |
22 neg |
32 split |
42 exec |
52 drop |
03 3 |
13 19 |
23 mul |
33 issue |
43 call |
53 peek |
04 4 |
14 20 |
24 div |
34 retire |
44 yield |
54 tuple |
05 5 |
15 21 |
25 mod |
35 amount |
45 wrap |
55 untuple |
06 6 |
16 22 |
26 gt |
36 assetid |
46 input |
56 len |
07 7 |
17 23 |
27 not |
37 anchor |
47 output |
57 field |
08 8 |
18 24 |
28 and |
38 vmhash |
48 contract |
58 encode |
09 9 |
19 25 |
29 or |
39 sha256 |
49 seed |
59 cat |
0a 10 |
1a 26 |
2a roll |
3a sha3 |
4a self |
5a slice |
0b 11 |
1b 27 |
2b bury |
3b checksig |
4b caller |
5b bitnot |
0c 12 |
1c 28 |
2c reverse |
3c log |
4c contractprogram |
5c bitand |
0d 13 |
1d 29 |
2d get |
3d peeklog |
4d timerange |
5d bitor |
0e 14 |
1e 30 |
2e put |
3e txid |
4e prv |
5e bitxor |
0f 15 |
1f 31 |
2f depth |
3f finalize |
4f ext |
5f+ pushdata |
In the individual instruction descriptions that follow, certain failure conditions are implicit. In particular, if the description says (for example) “pops two ints from the stack,” the instruction is understood to fail execution if fewer than two items are on the stack, or if either is not an int.
Unless otherwise stated, references to “the stack” mean “the current contract stack.”
When an instruction incurs data creation or copy costs, this is
indicated with language like “Creates string h
” or
“Copies item
” and a link to the section on runlimit
costs.
0|...|19 → (0|...|19)
Pushes int n
equal to the instruction’s code to the contract stack.
Note: Instructions 0x00
and 0x01
can be used to push
boolean values false
and true
.
x int → n
- Pops a string
x
from the contract stack. - Decodes it as a
LEB128-encoded unsigned
64-bit integer
u
. - Interprets the 64 bits of
u
as a signed two's complement 64-bit integern
. - Pushes
n
to the contract stack.
Fails execution when x
is not a valid LEB128 encoding of an integer.
a b add → a+b
Pops two ints a
and b
from the stack, adds them, and pushes their
sum a + b
to the stack.
Fails execution on overflow.
a neg → -a
Pops an int a
from the stack, pushes the negated a
to the stack.
Fails execution when -a
overflows (i.e., a
is -2^63
).
a b mul → a·b
Pops two ints a
and b
from the stack, multiplies them, and pushes
their product a · b
to the stack.
Fails execution on overflow.
a b div → a÷b
Pops two ints a
and b
from the stack, divides them truncated
toward 0, and pushes their quotient a ÷ b
to the stack.
Fails execution when:
a÷b
overflows;b = 0
.
a b mod → a mod b
Pops two ints a
and b
from the stack, computes their remainder a % b
, and pushes it to the stack.
The integer quotient q = a b div
and remainder r = a b mod
satisfy
the following relationships: a = q*b + r
and |r| < |b|
Fails execution when:
b = 0
;a = -2^63
andb = -1
.
a b gt → bool
- Pops two ints
a
andb
from the stack. - If
a
is greater thanb
, pushes int1
to the stack. - Otherwise, pushes int
0
.
p not → bool
- Pops a boolean
p
from the stack. - If
p
is0
, pushes int1
. - Otherwise, pushes int
0
.
p q not → bool
- Pops two booleans
p
andq
from the stack. - If both
p
andq
are true, pushes int1
. - Otherwise, pushes int
0
.
p q not → bool
- Pops two booleans
p
andq
from the stack. - If both
p
andq
are false, pushes int0
. - Otherwise, pushes int
1
.
x f vmhash → h
- Pops strings
f
andx
from the contract stack. - Creates string
h
by computing a VMHash:h = VMHash(f,x)
. - Pushes the resulting string
h
to the contract stack.
x sha256 → h
- Pops a string
x
from the contract stack. - Creates string
h
by computing SHA2-256:h = SHA2-256(x)
. - Pushes the resulting string
h
to the contract stack.
x sha3 → h
- Pops a string
x
from the contract stack. - Creates string
h
by computing SHA3-256:h = SHA3(x)
. - Pushes the resulting string
h
to the contract stack.
msg pubkey sig scheme checksig → bool
- Pops plain data item
scheme
and stringssig
,pubkey
andmsg
from the contract stack. - If the string
sig
is empty, pushes int0
to the contract stack. - If the string
sig
is not empty:- Reduces
vm.runlimit
by 2048. - If
scheme
is an int0
:- Fails execution if
pubkey
is not 32 bytes long. - Fails execution if
sig
is not 64 bytes long. - Performs an Ed25519 signature check with
pubkey
as the public key,msg
as the message, andsig
as the signature. - If signature check fails, fail the VM execution.
- Fails execution if
- If
scheme
is any other value andvm.extension
isfalse
, fails execution. - Pushes int
1
to the contract stack.
- Reduces
Note 1: Message is the first argument to simplify construction of multi-signature predicates.
Note 2: As an optimization, the implementation of checksig
may
immediately return a boolean result by checking signature length and
performing verification of all signatures in the transaction in a
batch mode.
x[n] x[n-1] ... x[0] n roll → x[n-1] ... x[0] x[n]
- Pops an int
n
from the contract stack. - Reduces
vm.runlimit
byn
. - Looks past
n
items from the top, and moves the next item to the top of the contract stack.
Fails execution if:
n
is negative;- stack has fewer than
n+1
items remaining.
Note: 0 roll
is a no-op, 1 roll
swaps the top two items.
x[n] ... x[1] x[0] n bury → x[0] x[n] ... x[1]
- Pops an int
n
from the contract stack. - Reduces
vm.runlimit
byn
. - On the contract stack, moves the top item past the
n
items below it.
Fails execution if:
n
is negative;- stack has fewer than
n+1
items remaining.
Note: 0 bury
is a no-op, 1 bury
swaps the top two items.
x[n-1] ... x[0] n reverse → x[0] ... x[n-1]
- Pops a int
n
from the contract stack. - Reduces
vm.runlimit
byn
. - On the contract stack reverses the top
n
items in-place.
Note: 0 reverse
and 1 reverse
are no-ops, 2 reverse
swaps the
top two items.
Argument stack: item get → ø
Contract stack: get → item
- Pops an item from the argument stack.
- Pushes that item to the contract stack.
Contract stack: item put → ø
Argument stack: put → item
- Pops an item from the contract stack.
- Pushes that item to the argument stack.
depth → count
- Counts the number of items
n
on the argument stack. - Pushes the int
n
to the contract stack.
prv → ø
Fails execution.
Note: This instruction is intended to convey TxVM code to a secure CPU enclave or other private execution context.
item ext → ø
Drops plain data item item
.
Fails execution if the vm.extension
flag is false
.
Note: x ext
acts as a NOP which can be assigned some functionality
in the future. If x
is a smallint, x ext
becomes a
compact 2-byte instruction with code x
. x
can also be a string or
a tuple containing both the instruction code and the actual argument
for that instruction.
cond verify → ø
- Pops a boolean
cond
from the contract stack. - Halts VM execution if it is equal to 0.
cond offset jumpif → ø
- Pops an integer
offset
from the contract stack. - Pops a boolean
cond
from the contract stack. - If
cond
is false, does nothing. - If
cond
is true:- Adds
offset
to the current program counter. - Fails if the resulting program counter is negative.
- Fails if the resulting program counter is greater than the length of the current program.
- Adds
Note 1: The program counter has already been advanced and points to
the instruction after the jumpif
before offset
is added to it.
Note 2: Normally the program using jumpif
would be written as
<cond> <offset> jumpif
, but for convenience the TxVM assembly
language permits symbolic jumps using this syntax:
<cond> jumpif:$<destination>
where <destination>
is the name of a label somewhere in the
program. The label itself is marked as $<destination>
among the
instructions. Example:
<cond> jumpif:$xyz ... $xyz ...
Note 3: Unconditional jumps can be implemented as:
1 jumpif:$<destination>
The TxVM assembly language abbreviates this as jump:$<destination>
.
args... program exec → results...
- Pops a string
program
from the contract stack. - Runs
program
.
Caller’s contract stack: contract|wrappedcontract call → ø
Argument stack: args... call → results...
- Pops a contract or
wrapped contract
contract
from the contract stack. Changes the type to contract if needed. - Saves the values of
vm.currentcontract
andvm.caller
. - Sets
vm.caller
tovm.currentcontract.seed
. - Sets
vm.currentcontract
tocontract
. - Runs
contract.program
. - Fails execution if
vm.unwinding
is false, but the contract stack is not empty. - If
vm.unwinding
is true, it is set to false. - Sets
vm.currentcontract
andvm.caller
to the values saved at step 2.
Note: A contract that finishes execution normally (without output, wrap or yield) must have an empty contract stack. Such a contract is discarded in step 8.
Contract stack: stack items... program yield → stack items...
Argument stack: yield → contract
- Pops a string
program
from the contract stack. - Sets
contract.program
toprogram
. - Pushes
vm.currentcontract
to the argument stack. - Sets
vm.unwinding
to true.
Contract stack: stack items... program wrap → stack items...
Argument stack: wrap → wrappedcontract
- Fails execution if any item on the contract stack is not portable.
- Pops a string
program
from the contract stack. - Sets
contract.program
toprogram
. - Changes the type of
vm.currentcontract
to wrapped contract. - Pushes the wrapped contract to the argument stack.
- Sets
vm.unwinding
to true.
snapshot input → contract
- Pops a tuple
snapshot
from the current contract stack. - Creates entry
c
of type contract such thatsnapshot
is the conversion ofc
. - Pushes
c
to the current contract stack. - Creates tuple
in = {"I", vm.currentcontract.seed, inputid}
whereinputid
is the snapshot ID ofc
:VMHash("SnapshotID", serialize(snapshot))
. - Writes
in
to the transaction log.
Fails execution if vm.finalized
is true.
Contract stack: stack items... program output → stack items...
- Fails execution if any item on the contract stack is not portable.
- Pops a string
program
from the contract stack. - Sets
vm.currentcontract.program
toprogram
. - Creates string
snapshotstring = serialize(convert(vm.currentcontract))
using serialization and conversion algorithms. - Creates tuple
out = {"O", vm.caller, outputid}
whereoutputid
is the snapshot ID of the current contract:VMHash("SnapshotID", snapshotstring)
. - Writes
out
to the transaction log. - Sets
vm.unwinding
to true.
Fails execution if vm.finalized
is true.
program contract → contract
- Pops string
program
from the contract stack. - Creates entry
contract
of type contract with the givenprogram
, an empty contract stack, and a contract seed computed fromprogram
. - Pushes
contract
to the current contract stack.
contract|wrappedcontract seed → contract|wrappedcontract seed
- Looks at the contract or
wrapped contract
contract
on top of the contract stack. - Copies
contract.seed
as stringseed
. - Pushes
seed
to the contract stack.
self → seed
- Copies
vm.currentcontract.seed
as stringseed
. - Pushes
seed
to the contract stack.
Note: The contract seed of the witness program (i.e., the top-level contract) is an all-zero 32-byte string.
caller → seed
- Copies
vm.caller
as stringseed
. - Pushes
seed
to the contract stack.
Note: vm.caller
for the witness program (i.e.,
the top-level contract) is an all-zero 32-byte string.
contractprogram → program
- Copies
vm.currentcontract.program
as stringprog
. - Pushes
prog
to the contract stack.
min max timerange → ø
- Pops an integer
max
from the contract stack. - Pops an integer
min
from the contract stack. - Creates tuple
{"R", vm.currentcontract.seed, min, max}
and writes it to the transaction log.
Fails execution if vm.finalized
is true.
item log → ø
- Pops a plain data item
item
from the contract stack. - Creates tuple
{"L", vm.currentcontract.seed, item}
and writes it to the transaction log.
Fails execution if vm.finalized
is true.
i peeklog → item
- Pops integer
i
from the contract stack. - Copies
item
from indexi
in the transaction log (zero-based). - Pushes
item
to the contract stack.
Fails execution if index i
is out of bounds (negative or ≥ than the length of
the log).
txid → txid
- Computes the transaction ID from the items in the transaction log without runlimit cost.
- Copies the transaction ID to a string
txid
. - Pushes
txid
to the contract stack.
Fails execution if vm.finalized
is false.
value finalize → ø
- Pops a value
value
from the contract stack. - Fails execution if
value
is not a zero-amount value. - Fails execution if
vm.finalized
is true. - Sets
vm.finalized
to true. - Creates tuple
{"F", vm.currentcontract.seed, vm.version, value.anchor}
and writes it to the transaction log.
Note: after the transaction has been finalized, no additional items can be added to the log.
blockid exp nonce → a
- Pops int
exp
from the contract stack. - Pops a string
blockid
from the contract stack. - Creates tuple
nonce = {"N", vm.caller, vm.currentcontract.seed, blockid, exp}
. - Creates tuple
timerange = {"R", vm.currentcontract.seed, 0, exp}
. - Creates entry
a
of type value:a.amount = 0
a.assetid = 0x000000...
(32 bytes, all zeroes)a.anchor = VMHash("Nonce", serialize(nonce))
(see Encoding section).
- Writes
nonce
to the transaction log. - Writes
timerange
to the transaction log. - Pushes
a
to the contract stack.
Fails execution if vm.finalized
is true.
Discussion
A nonce serves two purposes:
- It provides a unique anchor for an issuing transaction if other anchors are not available (i.e., from other values already on the stack);
- It binds the entire transaction to a particular blockchain, protecting not only against cross-blockchain replays, but also potential blockchain forks due to compromise of the old block-signing keys. This is a necessary feature for stateless signing devices that rely on blockchain proofs.
The blockid
that is copied to the log in step 3 will be checked
against the IDs of “recent” blocks when the transaction is applied to
the blockchain. It must match a recent block, or the initial block of
the blockchain, or be a string of 32 zero-bytes.
Additionally, the nonce
tuple is checked for uniqueness against
“recent” nonces.
To perform these checks, validators must keep a set of recent nonces
and a set of recent block headers available. For scalability and to
reduce resource demands on the network, these sets must be limited in
size. So block signers can and should impose reasonable limits on the
value of exp
(which is the time by which the transaction must be
included in a block or become invalid).
As a special case for long-living pre-signed transactions, the
protocol allows a nonce to use the initial block’s ID regardless of
the refscount
limit specified in the
block headers.
Another special case is an all-zero blockid
. This makes the nonce
replayable on another chain, but still unique within any one chain.
a b merge → c
- Pops an item
b
of type value from the contract stack. - Pops an item
a
of type value from the contract stack. - Creates entry
c
of type value:c.amount = a.amount + b.amount
c.assetid = a.assetid
c.anchor = VMHash("Merge", a.anchor || b.anchor)
- Pushes
c
to the contract stack.
Fails execution if a.assetid
differs from b.assetid
.
a amount split → b c
- Pops an integer
amount
from the contract stack. - Pops a value
a
from the contract stack. - Creates entry
b
of type value:b.amount = a.amount - amount
b.assetid = a.assetid
b.anchor = VMHash("Split1", a.anchor)
- Creates entry
c
of type value:c.amount = amount
c.assetid = a.assetid
c.anchor = VMHash("Split2", a.anchor)
- Pushes
b
, thenc
to the contract stack.
Fails execution if:
amount
is negative;amount
is greater thana.amount
.
Note: It is possible to create a value whose amount is zero, with
0 split or amount split. The issue
and finalize
instructions consume a zero-valued amount for the unique anchor it
contains.
avalue amount assettag issue → value
- Pops from the contract stack:
- string
assettag
, - integer
amount
, - value
avalue
, which must have an amount of zero.
- string
- Computes asset ID
assetid
with the tagassettag
and current contract’s seedvm.currentcontract.seed
. - Creates entry
v
of type value:v.amount = amount
v.assetid = assetid
v.anchor = avalue.anchor
- Pushes
v
to the contract stack. - Creates tuple
{"A", vm.caller, v.amount, v.assetid, v.anchor}
and writes it to the transaction log.
Fails execution if:
vm.finalized
is true;avalue
is not a zero-amount value;amount
is negative.
Note: issuance uses contract seed instead of the current executing program (or vm.currentcontract.program
) to allow issuance of the same asset ID from different states of the contract. Conversely, to allow issuance of more than one asset ID by the same contract, several assettag
strings can be used to generate distinct asset IDs.
value retire → ø
- Pops an item
value
of type value from the contract stack. - Creates tuple
{"X", vm.currentcontract.seed, value.amount, value.assetid, value.anchor}
and writes it to the transaction log.
Fails execution if vm.finalized
is true.
value amount → value amount
- Looks at value
v
at the top of the contract stack. - Pushes
v.amount
to the contract stack.
value assetid → value assetID
- Looks at value
v
at the top of the contract stack. - Copies
v.assetid
as stringassetid
. - Pushes
assetid
to the contract stack.
value anchor → value anchor
- Looks at value
v
at the top of the contract stack. - Copies
v.anchor
as stringanchor
. - Pushes
anchor
to the contract stack.
x y eq → bool
Tests two plain data items for equality.
- Pops item
y
from the contract stack. - Pops item
x
from the contract stack. - If they have differing types, pushes int
0
to the stack. - If they have the same type:
- if they are tuples, pushes int
0
to the stack; - otherwise, if they are equal, pushes int
1
to the stack; - otherwise, pushes int
0
to the stack.
- if they are tuples, pushes int
Note: entry types are not allowed to be compared directly with eq
in order to avoid dropping them from the stack without authorization.
item dup → item item
- Peeks at the top item on the contract stack,
item
. - Copies the
item
. - Pushes a copy of
item
to the contract stack.
Fails if item
is not a plain data item.
item drop → ø
- Pops an item
item
from the contract stack.
Fails if item
is neither a plain data item nor a
zero-amount value.
n peek → item
- Pops an int
n
from the contract stack. - Looks at the
n
th item,item
on the contract stack. - Copies
item
and pushes it to the contract stack.
Fails if item
is not a plain data item.
Note: 0 peek
is equivalent to dup.
x[0] ... x[n-1] n tuple → { x[0], ..., x[n-1] }
- Pops an integer
n
from the contract stack. - Pops
n
plain data items from the contract stack. - Creates tuple
t
with these items. The topmost item on the stack becomes the last item in the tuple. - Pushes
t
to the contract stack.
{x[0] ... x[n-1]} untuple → x[0] ... x[n-1] n
- Pops a tuple
tuple
from the contract stack. - Reduces
vm.runlimit
byn
, the length of the tuple. - Pushes each of the fields in
tuple
to the stack in order (so that the last item in the tuple ends up on top of the stack). - Pushes int
n
, the length of the tuple, to the contract stack.
item len → length
- Pops a string or tuple
item
from the contract stack. - Pushes int
n
to the contract stack:- If
item
is a tuple,n
is the the number of items in that tuple. - If
item
is a string,n
is the length of that string in bytes.
- If
tuple i field → contents
- Pops an integer
i
from the top of the contract stack. - Pops tuple
tuple
. - Copies the
item
stored in thei
th field oftuple
. - Pushes
item
to the contract stack.
Fails if i
is negative or greater than or equal to the number of
fields in tuple
.
item encode → string
- Pops a plain data item
item
from the contract stack. - Creates string
s
being a serialized copy ofitem
. - Pushes
s
to the contract stack
Note: use peeklog encode
to sign portions of the transaction log.
a b cat → a||b
- Pops string
b
from the contract stack. - Pops string
a
from the contract stack. - Creates string
a||b
as concatenation ofa
andb
. - Pushes the result
a||b
to the contract stack.
str start end slice → str[start:end]
- Pops two integers,
end
, thenstart
, from the contract stack. - Pops a string
str
from the contract stack. - Creates string
str[start:end]
(with the first character being the one at indexstart
, and the last character being the one before indexend
). - Pushes the resulting string
str[start:end]
to the contract stack.
Fails execution if:
start > end
;start < 0
;end > len(str)
.
a bitnot → ~a
- Pops a string
a
from the contract stack. - Creates string
~a
by inverting the bits ofa
. - Pushes the resulting string
~a
to the contract stack.
a b bitand → a&b
- Pops two strings
a
andb
from the contract stack. - Creates string by performing a “bitwise and”
operation on
a
andb
. - Pushes the result
a & b
to the contract stack.
Fails execution if len(a) != len(b)
.
a b bitor → a|b
- Pops two strings
a
andb
from the contract stack. - Creates string by performing a “bitwise or”
operation on
a
andb
. - Pushes the result
a | b
to the contract stack.
Fails execution if len(a) != len(b)
.
a b bitxor → a^b
- Pops two strings
a
andb
from the contract stack. - Creates string by performing a “bitwise xor”
operation on
a
andb
. - Pushes the result
a ^ b
to the contract stack.
Fails execution if len(a) != len(b)
.
(0x5f+)immediatedata → string
- Creates string
string
from the bytesimmediatedata
following the instruction code. - Pushes
string
to the contract stack.
All instructions with code equal or greater than 0x5f (95 in
decimal) are instructions pushing a string to the contract stack. The
string being pushed follows immediately after the instruction code.
The length of the string is n - 95
, where n
is the instruction
code.
Note 1: Code 0x5f pushes an empty string, code 0x60 is followed by a 1-byte string that is pushed to the stack, code 0x61 is followed by a 2-byte string, etc.
Note 2: Strings from 0 to 32 bytes in length require a 1-byte instruction code (from 0x5f through 0x7f). Strings of 33 bytes and longer use multi-byte opcodes because higher numbers occupy more than 1 byte in LEB128 encoding.
Programs can be deferred simply by creating a contract out of a program string.
[<conditions...>] contract
(In TxVM assembly language, a sequence of instructions enclosed in square brackets denotes the bytecode string resulting from assembling those instructions.)
Normally, a deferred program is created within some contract and needs to be passed out to the caller by putting it on the argument stack:
[<conditions...>] contract put
For example, a contract that runs before a transaction is finalized, but that requires a signature check against the transaction ID (available only after finalization), can defer a signature check for later like so:
<normal contract actions...> [txid <pubkey> get 0 checksig verify] contract put
The normal contract actions execute, then a new contract containing
the signature check is created and returned to the caller. That new
contract can be executed (via call) at any time after
finalize
. There is no danger of skipping the signature check, since
the contract remains on the stack until it is invoked and runs to
completion, and stacks must be cleared for the transaction to be
valid.
Note: It’s also possible to return the signature check program as a bare string (not a contract):
<normal contract actions...> [txid <pubkey> get 0 checksig verify] put
The caller would invoke this with exec rather than call. But because this is a plain string of bytes rather than a contract, there is no prohibition on simply dropping it from the stack, so there is no guarantee in this case that the signature check won’t be skipped.
In Pay To Public Key (P2PK) programs, some value is locked in a contract when first called. The next call of the contract unlocks the value and emits a deferred program checking the signature of the transaction ID.
<value> put [get [put [txid <pubkey> get 0 checksig verify] contract put] output] contract call
Explaining this example from the inside out:
... [txid <pubkey> get 0 checksig verify] contract ...
This is the deferred signature check from the previous section.
... [put [txid <pubkey> get 0 checksig verify] contract put] output ...
This persists the contract to the global blockchain state (with
output
) and suspends its execution. When it next runs (via input
)
it will invoke this new program, which does two things:
-
returns an item from the contract’s stack to the argument stack (via
put
); -
constructs a deferred signature check and returns that to the argument stack too.
put [get [...] output] contract call
This put
s a value from the contract stack to the argument
stack. Then it constructs a contract and call
s it. The constructed
contract get
s the value from the argument stack and then persists
itself (together with the value it just consumed) to the global
blockchain state.
Signature programs (P2SP) differ from P2PK programs in that another program is signed instead of the transaction ID.
The value is locked in a contract on the first call. The second call releases the value and defers a signature check. The signature check is against a program string that is executed.
P2SP program:
[get dup <pubkey> get 0 checksig verify exec]
The first get
consumes a program string from the argument stack. The
second get
consumes a signature.
Usage:
<value> put [get [get dup <pubkey> get 0 checksig verify exec] output] contract call
Breaking that down:
<value> put # move value to the argstack
[ #
get # get value to the contract stack
[ #
get dup # gets <prog> <prog>
<pubkey> # pushes <pubkey>
get # gets <sig>
0 checksig verify # checks signature of a program (copy prog from below pubkey and sig)
exec # executes the program
] # saves the next state of the contract that checks the signature
output # publishes the contract
] contract call # creates a contract from the code string and calls it to let it store the value
The value can be unlocked later with a signature sig
and a signed
program prog
. Program prog
will execute on a contract stack that
has value
. If prog
is:
txid <X> eq verify
then this is equivalent to the simple P2PK case above: instead of a
signature covering transaction ID X
, it covers a short program
saying “the transaction ID must be X
.” But the signature program may
include other conditions too.
Note that for extra safety against key reuse, the value’s anchor
should also be covered by the signature. Here is a safer P2SP program
signing program||value.anchor
instead of program
:
contract stack arg stack
-------------- ---------
[... <value>] [... prog sig]
anchor [... <value> value.anchor] [... prog sig]
get dup [... <value> value.anchor prog prog] [... sig]
2 roll [... <value> prog prog value.anchor] [... sig]
cat [... <value> prog (prog||value.anchor)] [... sig]
<pubkey> [... <value> prog (prog||value.anchor) pubkey] [... sig]
get [... <value> prog (prog||value.anchor) pubkey sig]
0 checksig [... <value> prog <checksig-result>]
verify [... <value> prog]
exec
The following example unlocks 3 outputs, re-allocates values and creates 2 new outputs using P2SP redeemed with txid-signing programs.
All values have the same asset ID.
# Unlock 3 outputs, move each value from argstack, but keep
# deferred contracts in the argstack until tx is finalized.
[[txid <txid> eq verify] contract put put] # shared signature program with embedded txid, always on top after each input
<sig1> put dup put
{"C", <id1>, [get dup <pubkey1> get 0 checksig verify exec], {"V", <amount1>, <anchor1>}} input
call get 1 roll
<sig2> put dup put
{"C", <id2>, [get dup <pubkey2> get 0 checksig verify exec], {"V", <amount2>, <anchor2>}} input
call get 1 roll
<sig3> put dup put
{"C", <id3>, [get dup <pubkey3> get 0 checksig verify exec], {"V", <amount3>, <anchor3>}} input
call get 1 roll
drop # drop the signature program
merge merge # merge all unlocked values, all deferred programs are left on the argstack
# Split the merged amount into two new amounts and lock with the new public keys:
<amount4> split
put [get [get dup <pubkey4> get 0 checksig verify exec] output] contract call
<amount5> split
put [get [get dup <pubkey4> get 0 checksig verify exec] output] contract call
# Assuming amount4+amount5 == amount1+amount2+amount3, the
# previous split should have left a zero value on the stack. This
# is consumed by finalize for its anchor.
finalize # use the remaining zero value to anchor the finalized transaction
# pop all deferred contracts from the argstack
get call
get call
get call
The goal of a nonce is to create a new globally unique anchor in the transaction. Recent nonces are remembered in the blockchain state and each new one is checked for uniqueness against the existing ones. (The amount of storage is bounded by a mandatory expiration time.)
Do not create nonces with simply a randomly generated string:
[<random> drop <blockchainid> <exp> nonce put] contract call # THIS IS NOT SAFE
Such nonces are vulnerable to sniping: before the transaction is confirmed, another transaction may steal that nonce and get committed to a block ahead of it, making the original transaction invalid.
In the best case, the transaction simply has to be re-built, re-signed and re-submitted. In the worst case, a chain of unconfirmed transactions spending the current one will break, potentially leading to a loss of funds (depending on specifics of a higher-level protocol).
The safe way to make a nonce is to generate a random public key and sign the transaction ID with it:
[
[txid <pubkey> get checksig verify] contract put
<blockchainid> <exp> nonce put
] contract call
That contract emits a deferred program and a new anchor. The deferred program checks the transaction signature with a random public key that simultaneously acts as a source of entropy for the nonce.
If a nonce has to be generated within the issuance contract, we can avoid doing an additional signature verification by using nonce in the same contract that does issue:
[... nonce ... issue] contract # safe, but subject to race conditions
Unfortunately, the contract seed must be stable in order to have a reusable asset ID, but unique for the nonce. (Expiration time entropy may be not enough to avoid race conditions.)
The way around is to bind nonce to the issuance contract not internally, but externally by checking that the caller contract seed matches the built-in one. This enables us to randomize the nonce-wrapping contract with a plain random string:
[
<random 16 bytes> drop
<issuancecontractseed> caller eq verify
<blockchainid> <exp> nonce put
] contract
put
[
get call get # get the nonce-generating contract, call it, and get the anchor out of it
...
issue
] contract
Nonce is wrapped it its own contract that has a built-in random string with 128 bits of entropy, and a check that it is called from a correct issuance program (so it cannot be sniped). That contract is then passed to the actual issuance contract which calls it and gets the anchor out of it.
Note: the issuance contract itself must be protected from sniping by, for instance, verifying a signature over the transaction ID.
TBD:
- How to perform "read" action (reuse anchor)?
- How to make offer (1 AAPL → 150 USD)?
- How to make collateralized loan?
- How to make a p2sh clause?
- How to make a merkleized clause?
- How to make stateful bond/coupons contract?
- How to make treasury/bond/coupons coordination?
- How to make a fully issuer-defined instrument, governing the entire lifecycle? Use
wrap
. - How to make two independent contracts get unlocked/destroyed atomically? Use contract that does that and authorized via
caller
.