specs
assembly
draft
Version: v0.2
Miden assembly is a simple, low-level language for writing programs for Miden VM. It stands just above raw Miden VM instruction set, and in fact, many instructions of Miden assembly map directly to raw instructions of Miden VM.
Before Miden assembly can be executed on Miden VM, it needs to be compiled into a Program MAST (Merkelized Abstract Syntax Tree) which is a binary tree of code blocks each containing raw Miden VM instructions.
As compared to raw Miden VM instructions, Miden assembly has several advantages:
The last two points also make Miden assembly much more concise as compared to the raw program MAST. This may be important in the blockchain context where pubic programs need to be stored on chain.
The design of Miden assembly tries to achieve the following goals:
In order to achieve the first goal, Miden assembly exposes a set of native operations over 32-bit integers and supports linear read-write memory. Thus, from the stand-point of a higher-level language compiler, Miden VM can be viewed as a regular 32-bit stack machine with linear read-write memory.
In order to achieve the second and third goals, Miden assembly facilitates flow control via high-level constructs like while
loops, if-else
statements, and function calls with statically defined targets. Thus, for example, there are no explicit jump
instructions.
In order to achieve the fourth goal, Miden assembly retains direct access to the VM stack rather than abstracting it away with higher-level constructs and named variables.
Lastly, in order to achieve the fifth goal, each instruction of Miden assembly can be encoded using a single byte. The resulting byte-code is simply a one-to-one mapping of instructions to their binary values.
Miden VM is a stack machine. The base data type of the MV is a field element in a 64-bit prime field defined by modulus \(p = 2^{64} - 2^{32} + 1\). This means that all values that the VM operates with are field elements in this field (i.e., values between \(0\) and \(2^{64} - 2^{32}\), both inclusive).
Throughout this document, we use lower-case letters to refer to individual field elements (e.g., \(a\)). Sometimes it is convenient to describe operations over groups of elements. For these purposes we define a word to be a group of four elements. We use upper-case letters to refer to words (e.g., \(A\)). To refer to individual elements within a word, we use numerical subscripts. For example, \(a_0\) is the first element of word \(A\), \(b_3\) is the last element of word \(B\), etc.
Miden VM consists of three high-level components as illustrated below.
These components are:
In the future, additional components (e.g., storage, logs) may be added to the VM.
In this note we use the following terms and notations:
A Miden assembly program is just a sequence of instructions each describing a specific directive or an operation. You can use any combination of whitespace characters to separate one instruction from another.
In turn, Miden assembly instructions are just keywords which can be parameterized by zero or more parameters. The notation for specifying parameters is keyword.param1.param2 - i.e., the parameters are separated by periods. For example, push.123
instruction denotes a push
operation which is parameterized by value 123
.
Currently, Miden assembly provides two types of code organization blocks: scripts and procedures. In the future, additional blocks such as modules and functions will be added.
A script block is used to define an executable program. A script must start with a begin
instruction and terminate with an end
instruction. For example:
begin
<instructions>
end
When Miden assembly code is executed, the execution starts at the first instruction following the begin
instruction of the script. A script block is expected to be the last block in a program and can be followed only by comment blocks.
A procedure block is used to define a frequently-used sequence of instructions. A procedure must start with a proc.<label>.<number of locals>
instruction and terminate with an end
instruction. For example:
proc.foo.2
<instructions>
end
A procedure label must start with a letter and can contain any combination of numbers, ASCII letters, and underscores (_
).
The number of locals specifies the number of memory-based local words a procedure can access (via load.local
, store.local
, and other instructions). If a procedure doesn't need any memory-based locals, this parameter can be omitted or set to 0
. The number of locals per procedure is not limited, but the total number of locals available to all procedures at runtime must be smaller than \(2^{32}\).
To execute a procedure an exec.<label>
instruction should be used. For example:
exec.foo
During compilation, procedures are inlined at their call sites. Thus, from the standpoint of the final program, executing procedures is indistinguishable from manually including procedure code in place of the exec
instruction.
A procedure may execute any other previously defined procedure from the same source, but it cannot execute itself or any of the subsequent procedures. Thus, recursive procedure calls are not possible. For example, the following code block defines a script with two procedures:
proc.foo
<instructions>
end
proc.bar
<instructions>
exec.foo
<instructions>
end
begin
<instructions>
exec.bar
<instructions>
exec.foo
end
Miden assembly allows annotating code with simple comments. The comments must be placed between two #
(pound) characters which must be surrounded by whitespace on both sides. For example:
# this is a comment #
Using a pound character within a comment is not allowed.
When Miden assembly is serialized into binary format, comments are not retained.
External inputs can be provided to Miden VM in two way:
After a program finishes executing up to 16 elements can remain on the stack. These elements then become the outputs of the program.
Having only 16 elements to describe public inputs and outputs of a program may seem limiting, however, just 4 elements are sufficient to represent a root of a Merkle tree which can be expanded into an arbitrary number of values.
For example, if we wanted to provide a thousand public input values to the VM, we could put these values into a Merkle tree, initialize the stack with the root of this tree, initialize the advice provides with the tree itself, and then retrieve values from the tree during program execution using mtree.get
instruction (described here).
In the future, other ways of providing public inputs and reading public outputs (e.g., storage commitments) may be added to the VM.
The advice provider component is responsible for supplying non-deterministic inputs to the VM. These inputs need to be known only to the prover (i.e., they do not need to be shared with the verifier).
The advice provider consists of two components:
The prover initializes the advice provider prior to executing a program, and from that point on the advice provider is manipulated solely by executing operations on the VM.
As mentioned above, Miden assembly provides high-level constructs to facilitate flow control. These constructs are:
Conditional execution in Miden VM can be accomplished with if-else statements. These statements look like so:
if.true
<instructions>
else
<instructions>
end
where instructions
can be a sequence of any instructions, including nested control structures; the else
clause is optional. The above does the following:
if.true
branch are executed.else
branch are executed.A note on performance: using if-else statements incurs a small, but non-negligible overhead. Thus, for simple conditional statements, it may be more efficient to compute the result of both branches, and then select the result using conditional drop instructions.
Executing a sequence of instructions a predefined number of times can be accomplished with repeat statements. These statements look like so:
repeat.<count>
<instructions>
end
where:
instructions
can be a sequence of any instructions, including nested control structures.count
is the number of times the instructions
sequence should be repeated (e.g. repeat.10
). count
must be an integer greater than \(0\).Executing a sequence of instructions zero or more times based on some condition can be accomplished with while loop expressions. These expressions look like so:
while.true
<instructions>
end
where instructions
can be a sequence of any instructions, including nested control structures. The above does the following:
instructions
in the loop body are executed.Miden assembly provides a set of instructions which can perform operations with raw field elements. These instructions are described in the tables below.
While most operations place no restrictions on inputs, some operations expect inputs to be binary values, and fail if executed with non-binary inputs.
For instructions where one or more operands can be provided as immediate parameters (e.g., add
and add.b
), we provide stack transition diagrams only for the non-immediate version. For the immediate version, it can be assumed that the operand with the specified name is not present on the stack.
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
assert | [a, …] | […] | If \(a = 1\), removes it from the stack. Fails if \(a \ne 1\) |
assert.eq | [b, a, …] | […] | If \(a = b\), removes them from the stack. Fails if \(a \ne b\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
add add.b |
[b, a, …] | [c, …] | \(c \leftarrow (a + b) \mod p\) |
sub sub.b |
[b, a, …] | [c, …] | \(c \leftarrow (a - b) \mod p\) |
mul mul.b |
[b, a, …] | [c, …] | \(c \leftarrow (a \cdot b) \mod p\) |
div div.b |
[b, a, …] | [c, …] | \(c \leftarrow (a \cdot b^{-1}) \mod p\) Fails if \(b = 0\) |
neg | [a, …] | [b, …] | \(b \leftarrow -a \mod p\) |
inv | [a, …] | [b, …] | \(b \leftarrow a^{-1} \mod p\) Fails if \(a = 0\) |
pow2 | [a, …] | [b, …] | \(b \leftarrow 2^a\) Fails if \(a > 63\) |
not | [a, …] | [b, …] | \(b \leftarrow 1 - a\) Fails if \(a > 1\) |
and | [b, a, …] | [c, …] | \(c \leftarrow a \cdot b\) Fails if \(max(a, b) > 1\) |
or | [b, a, …] | [c, …] | \(c \leftarrow a + b - a \cdot b\) Fails if \(max(a, b) > 1\) |
xor | [b, a, …] | [c, …] | \(c \leftarrow a + b - 2 \cdot a \cdot b\) Fails if \(max(a, b) > 1\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
eq eq.b |
[b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}\) |
neq neq.b |
[b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}\) |
lt | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}\) |
lte | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}\) |
gt | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}\) |
gte | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}\) |
eqw | [A, B, …] | [c, A, B, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a_i = b_i \; \forall i \in \{0, 1, 2, 3\} \\ 0, & \text{otherwise}\ \end{cases}\) |
Miden assembly provides a set of instructions which can perform operations on regular 32-bit integers. These instructions are described in the tables below.
Many instructions have safe and unsafe versions. The safe versions ensure that input values are 32-bit integers, and fail if that's not the case. The unsafe versions do not perform these checks, and thus, should be used only if the inputs are known to be 32-bit integers. Supplying inputs which are greater than or equal to \(2^{32}\) to unsafe operations results in undefined behavior.
The primary benefit of using unsafe operations is performance: they can frequently be executed \(2\) or \(3\) times faster than their safe counterparts. In general, vast majority of the unsafe operations listed below can be executed in a single VM cycle.
For instructions where one or more operands can be provided as immediate parameters (e.g., u32add
and u32add.b
), we provide stack transition diagrams only for the non-immediate version. For the immediate version, it can be assumed that the operand with the specified name is not present on the stack.
Instruction | Stack input | Stack output | Notes |
---|---|---|---|
u32test | [a, …] | [b, a, …] | \(b \leftarrow \begin{cases} 1, & \text{if}\ a < 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) |
u32testw | [A, …] | [b, A, …] | \(b \leftarrow \begin{cases} 1, & \text{if}\ \forall\ i \in \{0, 1, 2, 3\}\ a_i < 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) |
u32assert | [a, …] | [a, …] | Fails if \(a \ge 2^{32}\) |
u32assertw | [A, …] | [A, …] | Fails if \(\exists\ i \in \{0, 1, 2, 3\} \ni a_i \ge 2^{32}\) |
u32cast | [a, …] | [b, …] | \(b \leftarrow a \mod 2^{32}\) |
u32split | [a, …] | [c, b, …] | \(b \leftarrow a \mod 2^{32}\), \(c \leftarrow \lfloor{a / 2^{32}}\rfloor\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
u32add u32add.b |
[b, a, …] | [c, …] | \(c \leftarrow a + b\) Fails if \(max(a, b, c) \ge 2^{32}\) |
u32add.full | [b, a, …] | [d, c, …] | \(c \leftarrow (a + b) \mod 2^{32}\) \(d \leftarrow \begin{cases} 1, & \text{if}\ (a + b) \ge 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32add.unsafe | [b, a, …] | [d, c, …] | \(c \leftarrow (a + b) \mod 2^{32}\), \(d \leftarrow \begin{cases} 1, & \text{if}\ (a + b) \ge 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32addc | [b, a, c, …] | [e, d, …] | \(d \leftarrow (a + b + c) \mod 2^{32}\) \(e \leftarrow \begin{cases} 1, & \text{if}\ (a + b + c) \ge 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) or \(c > 1\) |
u32addc.unsafe | [b, a, c, …] | [e, d, …] | \(d \leftarrow (a + b + c) \mod 2^{32}\), \(e \leftarrow \begin{cases} 1, & \text{if}\ (a + b + c) \ge 2^{32} \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) Fails if \(c > 1\) |
u32sub u32sub.b |
[b, a, …] | [c, …] | \(c \leftarrow (a - b)\) Fails if \(max(a, b) \ge 2^{32}\) or \(a < b\) |
u32sub.full | [b, a, …] | [d, c, …] | \(c \leftarrow (a - b) \mod 2^{32}\) \(d \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32sub.unsafe | [b, a, …] | [d, c, …] | \(c \leftarrow (a - b) \mod 2^{32}\) \(d \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32mul u32mul.b |
[b, a, …] | [c, …] | \(c \leftarrow a \cdot b\) Fails if \(max(a, b, c) \ge 2^{32}\) |
u32mul.full | [b, a, …] | [d, c, …] | \(c \leftarrow (a \cdot b) \mod 2^{32}\) \(d \leftarrow \lfloor(a \cdot b) / 2^{32}\rfloor\) Fails if \(max(a, b) \ge 2^{32}\) |
u32mul.unsafe | [b, a, …] | [d, c, …] | \(c \leftarrow (a \cdot b) \mod 2^{32}\) \(d \leftarrow \lfloor(a \cdot b) / 2^{32}\rfloor\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32madd | [b, a, c, …] | [e, d, …] | \(d \leftarrow (a \cdot b + c) \mod 2^{32}\) \(e \leftarrow \lfloor(a \cdot b + c) / 2^{32}\rfloor\) Fails if \(max(a, b, c) \ge 2^{32}\) |
u32madd.unsafe | [b, a, c, …] | [e, d, …] | \(d \leftarrow (a \cdot b + c) \mod 2^{32}\) \(e \leftarrow \lfloor(a \cdot b + c) / 2^{32}\rfloor\) Undefined if \(max(a, b, c) \ge 2^{32}\) |
u32div u32div.b |
[b, a, …] | [c, …] | \(c \leftarrow \lfloor a / b\rfloor\) Fails if \(max(a, b) \ge 2^{32}\) or \(b = 0\) |
u32div.full | [b, a, …] | [d, c, …] | \(c \leftarrow \lfloor a / b\rfloor\) \(d \leftarrow a \mod b\) Fails if \(max(a, b) \ge 2^{32}\) or \(b = 0\) |
u32div.unsafe | [b, a, …] | [d, c, …] | \(c \leftarrow \lfloor a / b\rfloor\) \(d \leftarrow a \mod b\) Fails if \(b = 0\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32mod u32mod.b |
[b, a, …] | [c, …] | \(c \leftarrow a \mod b\) Fails if \(max(a, b) \ge 2^{32}\) or \(b = 0\) |
u32mod.unsafe | [b, a, …] | [c, …] | \(c \leftarrow a \mod b\) Fails if \(b = 0\) Undefined if \(max(a, b) \ge 2^{32}\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
u32and | [b, a, …] | [c, …] | Computes \(c\) as a bitwise AND of binary representations of \(a\) and \(b\). Fails if \(max(a,b) \ge 2^{32}\) |
u32or | [b, a, …] | [c, …] | Computes \(c\) as a bitwise OR of binary representations of \(a\) and \(b\). Fails if \(max(a,b) \ge 2^{32}\) |
u32xor | [b, a, …] | [c, …] | Computes \(c\) as a bitwise XOR of binary representations of \(a\) and \(b\). Fails if \(max(a,b) \ge 2^{32}\) |
u32not | [a, …] | [b, …] | Computes \(b\) as a bitwise NOT of binary representation of \(a\). Fails if \(a \ge 2^{32}\) |
u32shl u32shl.b |
[b, a, …] | [c, …] | \(c \leftarrow (a \cdot 2^b) \mod 2^{32}\) Fails if \(a \ge 2^{32}\) or \(b > 31\) |
u32shr u32shr.b |
[b, a, …] | [c, …] | \(c \leftarrow \lfloor a/2^b \rfloor\) Fails if \(a \ge 2^{32}\) or \(b > 31\) |
u32rotl u32rotl.b |
[b, a, …] | [c, …] | Computes \(c\) by rotating a 32-bit representation of \(a\) to the left by \(b\) bits. Fails if \(a \ge 2^{32}\) or \(b > 31\) |
u32rotr u32rotr.b |
[b, a, …] | [c, …] | Computes \(c\) by rotating a 32-bit representation of \(a\) to the right by \(b\) bits. Fails if \(a \ge 2^{32}\) or \(b > 31\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
u32.eq u32.eq.b |
[b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a=b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) Note: unsafe version is not provided because it is equivalent to simple eq . |
u32.neq u32.neq.b |
[b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \ne b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) Note: unsafe version is not provided because it is equivalent to simple neq . |
u32lt | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32lt.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a < b \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32lte | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32lte.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \le b \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32gt | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32gt.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a > b \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32gte | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32gte.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} 1, & \text{if}\ a \ge b \\ 0, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32min | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} a, & \text{if}\ a < b \\ b, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32min.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} a, & \text{if}\ a < b \\ b, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
u32max | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} a, & \text{if}\ a > b \\ b, & \text{otherwise}\ \end{cases}\) Fails if \(max(a, b) \ge 2^{32}\) |
u32max.unsafe | [b, a, …] | [c, …] | \(c \leftarrow \begin{cases} a, & \text{if}\ a > b \\ b, & \text{otherwise}\ \end{cases}\) Undefined if \(max(a, b) \ge 2^{32}\) |
Miden VM stack is a push-down stack of field elements. The stack has a maximum depth of \(2^{16}\), but only the top \(16\) elements are directly accessible via the instructions listed below.
In addition to the typical stack manipulation instructions such as drop
, dup
, swap
etc., Miden assembly provides several conditional instructions which can be used to manipulate the stack based on some condition - e.g., conditional swap cswap
or conditional drop cdrop
.
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
drop | [a, … ] | [ … ] | Deletes the top stack item. |
dropw | [A, … ] | [ … ] | Deletes a word (4 elements) from the top of the stack. |
padw | [ … ] | [0, 0, 0, 0, … ] | Pushes four \(0\) values onto the stack. Note: simple pad is not provided because push.0 does the same thing. |
dup.n | [ …, a, … ] | [a, …, a, … ] | Pushes a copy of the \(n\)th stack item onto the stack. dup and dup.0 are the same instruction. Valid for \(n \in \{0, ..., 15\}\) |
dupw.n | [ …, A, … ] | [A, …, A, … ] | Pushes a copy of the \(n\)th stack word onto the stack. dupw and dupw.0 are the same instruction. Valid for \(n \in \{0, 1, 2, 3\}\) |
swap.n | [a, …, b, … ] | [b, …, a, … ] | Swaps the top stack item with the \(n\)th stack item. swap and swap.1 are the same instruction. Valid for \(n \in \{1, ..., 15\}\) |
swapw.n | [A, …, B, … ] | [B, …, A, … ] | Swaps the top stack word with the \(n\)th stack word. swapw and swapw.1 are the same instruction. Valid for \(n \in \{1, 2, 3\}\) |
movup.n | [ …, a, … ] | [a, … ] | Moves the \(n\)th stack item to the top of the stack. Valid for \(n \in \{2, ..., 15\}\) |
movupw.n | [ …, A, … ] | [A, … ] | Moves the \(n\)th stack word to the top of the stack. Valid for \(n \in \{2, 3\}\) |
movdn.n | [a, … ] | [ …, a, … ] | Moves the top stack item to the \(n\)th position of the stack. Valid for \(n \in \{2, ..., 15\}\) |
movdnw.n | [A, … ] | [ …, A, … ] | Moves the top stack word to the \(n\)th word position of the stack. Valid for \(n \in \{2, 3\}\) |
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
cswap | [c, b, a, … ] | [e, d, … ] | \(d = \begin{cases} a, & \text{if}\ c = 0 \\ b, & \text{if}\ c = 1\ \end{cases}\) \(e = \begin{cases} b, & \text{if}\ c = 0 \\ a, & \text{if}\ c = 1\ \end{cases}\) Fails if \(c > 1\) |
cswapw | [c, B, A, … ] | [E, D, … ] | \(D = \begin{cases} A, & \text{if}\ c = 0 \\ B, & \text{if}\ c = 1\ \end{cases}\) \(E = \begin{cases} B, & \text{if}\ c = 0 \\ A, & \text{if}\ c = 1\ \end{cases}\) Fails if \(c > 1\) |
cdrop | [c, b, a, … ] | [d, … ] | \(d = \begin{cases} a, & \text{if}\ c = 0 \\ b, & \text{if}\ c = 1\ \end{cases}\) Fails if \(c > 1\) |
cdropw | [c, B, A, … ] | [D, … ] | \(D = \begin{cases} A, & \text{if}\ c = 0 \\ B, & \text{if}\ c = 1\ \end{cases}\) Fails if \(c > 1\) |
Miden assembly provides a set of instructions for moving data between the stack and several other sources. These sources include:
In the future several other sources such as storage and logs may be added.
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
push.a push.a.b push.a.b.c… |
[…] | [a, …] [b, a, …] [c, b, a, …] |
Pushes values \(a\), \(b\), \(c\) etc. onto the stack. Up to \(16\) values can be specified. All values must be valid field elements in decimal (e.g., \(123\)) or hexadecimal (e.g., \(0x7b\)) representation. |
When specifying values in hexadecimal format, it is possible to omit the periods between individual values as long as total number of specified bytes is a multiple of \(8\). That is, the following are semantically equivalent:
push.0x1234.0xabcd
push.0x0000000000001234000000000000abcd
In both case the values must still encode valid field elements.
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
push.env.sdepth | […] | [d, …] | \(d \leftarrow stack.depth()\) Pushes the current depth of the stack onto the stack. |
push.env.locaddr.i | […] | [a, …] | \(a \leftarrow address\_of(i)\) Pushes the absolute memory address of local memory at index \(i\) onto the stack. |
Instruction | Stack_input_ | Stack_output | Notes |
---|---|---|---|
push.adv.n | [ … ] | [a, … ] | \(a \leftarrow tape.next()\) Removes the next \(n\) values from advice tape and pushes them onto the stack. Valid for \(n \in \{1, ..., 16\}\). Fails if the advice tape has fewer than \(n\) values. |
loadw.adv | [0, 0, 0, 0, … ] | [A, … ] | \(A \leftarrow tape.next\_word()\) Removes the next word (4 elements) from the advice tape and overwrites the top four stack elements with it. Fails if the advice tape has fewer than \(4\) values. |
As mentioned above, there are two ways to access memory in Miden VM. The first way is via memory addresses using the instructions listed below. The addresses are absolute - i.e., they don't depend on the procedure context. Memory addresses can be in the range \([0, 2^{32})\).
Memory is guaranteed to be initialized to zeros. Thus, when reading from memory address which hasn't been written to previously, zero elements will be returned.
Instruction | Stack_input___ | Stack_output | Notes |
---|---|---|---|
push.mem push.mem.a |
[a, … ] | [v, … ] | \(a \leftarrow mem[a][0]\) Reads a word (4 elements) from memory at address a, and pushes the first element of the word onto the stack. If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
pushw.mem pushw.mem.a |
[a, … ] | [A, … ] | \(A \leftarrow mem[a]\) Reads a word from memory at address \(a\) and pushes it onto the stack. If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
loadw.mem loadw.mem.a |
[a, 0, 0, 0, 0, …] | [A, … ] | \(A \leftarrow mem[a]\) Reads a word from memory at address \(a\) and overwrites top four stack elements with it. If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
pop.mem pop.mem.a |
[a, v, …] | [ … ] | \([v, 0, 0, 0] \rightarrow mem[a]\) Pops an element off the stack and stores it as the first element of the word in memory at address \(a\). All other elements of the word are set to \(0\). If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
popw.mem popw.mem.a |
[a, A, …] | [ … ] | \(A \rightarrow mem[a]\) Pops the top four elements off the stack and stores them in memory at address \(a\). If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
storew.mem storew.mem.a |
[a, A, …] | [A, … ] | \(A \rightarrow mem[a]\) Stores the top four elements of the stack in memory at address \(a\). If \(a\) is provided via the stack, it is removed from the stack first. Fails if \(a \ge 2^{32}\) |
The second way to access memory is via procedure locals using the instructions listed below. These instructions are available only in procedure context. The number of locals available to a given procedure must be specified at procedure declaration time, and trying to access more locals than was declared will result in a compile-time error. The number of locals per procedure is not limited, but the total number of locals available to all procedures at runtime must be smaller than \(2^{32}\).
Instruction | Stack_input_ | Stack_output | Notes |
---|---|---|---|
push.local.i | [ … ] | [v, … ] | \(v \leftarrow local[i][0]\) Reads a word (4 elements) from local memory at index i, and pushes the first element of the word onto the stack. |
pushw.local.i | […] | [A, … ] | \(A \leftarrow local[i]\) Reads a word from local memory at index \(i\) and pushes it onto the stack. |
loadw.local.i | [0, 0, 0, 0, …] | [A, … ] | \(A \leftarrow local[i]\) Reads a word from local memory at index \(i\) and overwrites top four stack elements with it. |
pop.local.i | [v, …] | [ … ] | \([v, 0, 0, 0] \rightarrow local[i]\) Pops an element off the stack and stores it as the first element of the word in local memory at index \(i\). All other elements of the word are set to \(0\). |
popw.local.i | [A, …] | [ … ] | \(A \rightarrow local[i]\) Pops the top four elements off the stack and stores them in local memory at index \(i\). |
storew.local.i | [A, …] | [A, … ] | \(A \rightarrow local[i]\) Stores the top four elements of the stack in local memory at index \(i\). |
Unlike regular memory, procedure locals are not guaranteed to be initialized to zeros. Thus, when working with locals, one must assume that before a local memory address has been written to, it contains "garbage".
Internally in the VM, procedure locals are stored at memory offset stating at \(2^{30}\). Thus, every procedure local has an absolute address in regular memory. The push.env.locaddr
is provided specifically to map an index of a procedure's local to an absolute address so that it can be passed to downstream procedures, when needed.
Miden assembly provides a set of instructions for performing common cryptographic operations. These instructions are listed in the table below.
Rescue Prime is the native hash function of Miden VM. The parameters of the hash function were chosen to provide 128-bit security level against preimage and collision attacks. The function operates over a state of 12 field elements, and requires 7 rounds for a single permutation. However, due to its special status within the VM, computing Rescue Prime hashes can be done very efficiently. For example, applying a permutation of the hash function can be done in a single VM cycle.
Instruction | Stack_input | Stack_output | Notes |
---|---|---|---|
rpperm | [C, B, A, …] | [F, E, D, …] | \(\{D, E, F\} \leftarrow permute(A, B, C)\) where, \(permute()\) computes a Rescue Prime permutation. |
rphash | [B, A, …] | [C, …] | \(C \leftarrow hash(A,B)\) where, \(hash()\) computes a 2-to-1 Rescue Prime hash. |
mtree.get | [d, i, R, …] | [V, R, …] | Verifies that a Merkle tree with root \(R\) opens to node \(V\) at depth \(d\) and index \(i\). Merkle tree with root \(R\) must be present in the advice provider, otherwise execution fails. |
mtree.set | [d, i, V, R, …] | [V, R', …] | Updates a node in the Merkle tree with root \(R\) at depth \(d\) and index \(i\) to value \(V\). \(R'\) is the Merkle root of the resulting tree. Merkle tree with root \(R\) must be present in the advice provider, otherwise execution fails. At the end of the operation Merkle tree with root \(R\) is removed from the advice provider. |
mtree.cwm | [d, i, V, R, …] | [V, R', R, …] | Copies a Merkle tree with root \(R\) and updates a node at depth \(d\) and index \(i\) in the copied tree to value \(V\). \(R'\) is the Merkle root of the new tree. Merkle tree with root \(R\) must be present in the advice provider, otherwise execution fails. At the end of the operation the advice provider will contain both Merkle trees. |
To support basic debugging capabilities, Miden assembly provides debug
instruction. This instruction prints out the state of the VM at the time when the debug
instruction is executed. Debug instruction can be parameterized as follows:
debug.all
prints out the entire state of the VM (stack and RAM).debug.stack
prints out the entire contents of the stack.debug.stack.<n>
prints out the top \(n\) items of the stack. \(n\) must be an integer greater than \(0\).debug.mem
prints out the entire contents of RAM.debug.mem.<n>
prints out contents of memory at address \(n\).debug.mem.<n>.<m>
prints out the contents of memory starting at address \(n\) and ending at address \(m\) (both inclusive). \(m\) must be greater than \(n\).debug.local
prints out all locals of the currently executing procedure.debug.local.<n>
prints out contents of the local at index \(n\) for the currently executing procedure.Debug instructions do not affect VM state, do not change program hash, and are omitted when Miden assembly is serialized into binary format.