# Cryptographic operations

In this section we describe the AIR constraints for Miden VM cryptographic operations.

Cryptographic operations in Miden VM are performed by the Hash chiplet. Communication between the stack and the hash chiplet is accomplished via the chiplet bus $b_{chip}$. To make requests to and to read results from the chiplet bus we need to divide its current value by the value representing the request.

Thus, to describe AIR constraints for the cryptographic operations, we need to define how to compute these input and output values within the stack. We do this in the following sections.

## HPERM

The `HPERM`

operation applies Rescue Prime Optimized permutation to the top $12$ elements of the stack. The stack is assumed to be arranged so that the $8$ elements of the rate are at the top of the stack. The capacity word follows, with the number of elements to be hashed at the deepest position in stack. The diagram below illustrates this graphically.

In the above, $r$ (located in the helper register $h_{0}$) is the row address from the hash chiplet set by the prover non-deterministically.

For the `HPERM`

operation, we define input and output values as follows:

$v_{input}=α_{0}+α_{1}⋅op_{linhash}+α_{2}⋅h_{0}+j=0∑11 (α_{j+4}⋅s_{11−j})$

$v_{output}=α_{0}+α_{1}⋅op_{retstate}+α_{2}⋅(h_{0}+7)+j=0∑11 (α_{j+4}⋅s_{11−j})$

In the above, $op_{linhash}$ and $op_{retstate}$ are the unique operation labels for initiating a linear hash and reading the full state of the hasher respectively. Also note that the term for $α_{3}$ is missing from the above expressions because for Rescue Prime Optimized permutation computation the index column is expected to be set to $0$.

Using the above values, we can describe the constraint for the chiplet bus column as follows:

$b_{chip}⋅v_{input}⋅v_{output}=b_{chip}| degree=3$

The above constraint enforces that the specified input and output rows must be present in the trace of the hash chiplet, and that they must be exactly $7$ rows apart.

The effect of this operation on the rest of the stack is:

**No change**starting from position $12$.

## MPVERIFY

The `MPVERIFY`

operation verifies that a Merkle path from the specified node resolves to the specified root. This operation can be used to prove that the prover knows a path in the specified Merkle tree which starts with the specified node.

Prior to the operation, the stack is expected to be arranged as follows (from the top):

- Value of the node, 4 elements ($V$ in the below image)
- Depth of the path, 1 element ($d$ in the below image)
- Index of the node, 1 element ($i$ in the below image)
- Root of the tree, 4 elements ($R$ in the below image)

The Merkle path itself is expected to be provided by the prover non-deterministically (via the advice provider). If the prover is not able to provide the required path, the operation fails. Otherwise, the state of the stack does not change. The diagram below illustrates this graphically.

In the above, $r$ (located in the helper register $h_{0}$) is the row address from the hash chiplet set by the prover non-deterministically.

For the `MPVERIFY`

operation, we define input and output values as follows:

$v_{input}=α_{0}+α_{1}⋅op_{mpver}+α_{2}⋅h_{0}+α_{3}⋅s_{5}+j=0∑3 α_{j+8}⋅s_{3−j}$

$v_{output}=α_{0}+α_{1}⋅op_{rethash}+α_{2}⋅(h_{0}+8⋅s_{4}−1)+j=0∑3 α_{j+8}⋅s_{9−j}$

In the above, $op_{mpver}$ and $op_{rethash}$ are the unique operation labels for initiating a Merkle path verification computation and reading the hash result respectively. The sum expression for inputs computes the value of the leaf node, while the sum expression for the output computes the value of the tree root.

Using the above values, we can describe the constraint for the chiplet bus column as follows:

$b_{chip}⋅v_{input}⋅v_{output}=b_{chip}| degree=3$

The above constraint enforces that the specified input and output rows must be present in the trace of the hash chiplet, and that they must be exactly $8⋅d−1$ rows apart, where $d$ is the depth of the node.

The effect of this operation on the rest of the stack is:

**No change**starting from position $0$.

## MRUPDATE

The `MRUPDATE`

operation computes a new root of a Merkle tree where a node at the specified position is updated to the specified value.

The stack is expected to be arranged as follows (from the top):

- old value of the node, 4 element ($V$ in the below image)
- depth of the node, 1 element ($d$ in the below image)
- index of the node, 1 element ($i$ in the below image)
- current root of the tree, 4 elements ($R$ in the below image)
- new value of the node, 4 element ($NV$ in the below image)

The Merkle path for the node is expected to be provided by the prover non-deterministically (via merkle sets). At the end of the operation, the old node value is replaced with the new root value computed based on the provided path. Everything else on the stack remains the same. The diagram below illustrates this graphically.

In the above, $r$ (located in the helper register $h_{0}$) is the row address from the hash chiplet set by the prover non-deterministically.

For the `MRUPDATE`

operation, we define input and output values as follows:

$v_{inputold}=α_{0}+α_{1}⋅op_{mruold}+α_{2}⋅h_{0}+α_{3}⋅s_{5}+j=0∑3 α_{j+8}⋅s_{3−j}$

$v_{outputold}=α_{0}+α_{1}⋅op_{rethash}+α_{2}⋅(h_{0}+8⋅s_{4}−1)+j=0∑3 α_{j+8}⋅s_{9−j}$

$v_{inputnew}=α_{0}+α_{1}⋅op_{mrunew}+α_{2}⋅(h_{0}+8⋅s_{4})+α_{3}⋅s_{5}+j=0∑3 α_{j+8}⋅s_{13−j}$

$v_{outputnew}=α_{0}+α_{1}⋅op_{rethash}+α_{2}⋅(h_{0}+2⋅8⋅s_{4}−1)+j=0∑3 α_{j+8}⋅s_{3−j}$

In the above, the first two expressions correspond to inputs and outputs for verifying the Merkle path between the old node value and the old tree root, while the last two expressions correspond to inputs and outputs for verifying the Merkle path between the new node value and the new tree root. The hash chiplet ensures the same set of sibling nodes are uses in both of these computations.

The $op_{mruold}$, $op_{mrunew}$, and $op_{rethash}$ are the unique operation labels used by the above computations.

$b_{chip}⋅v_{inputold}⋅v_{outputold}⋅v_{inputnew}⋅v_{outputnew}=b_{chip}| degree=5$

The above constraint enforces that the specified input and output rows for both, the old and the new node/root combinations, must be present in the trace of the hash chiplet, and that they must be exactly $8⋅d−1$ rows apart, where $d$ is the depth of the node. It also ensures that the computation for the old node/root combination is immediately followed by the computation for the new node/root combination.

The effect of this operation on the rest of the stack is:

**No change**for positions starting from $4$.

## FRIE2F4

The `FRIE2F4`

operation performs FRI layer folding by a factor of 4 for FRI protocol executed in a degree 2 extension of the base field. It also performs several computations needed for checking correctness of the folding from the previous layer as well as simplifying folding of the next FRI layer.

The stack for the operation is expected to be arranged as follows:

- The first $8$ stack elements contain $4$ query points to be folded. Each point is represented by two field elements because points to be folded are in the extension field. We denote these points as $q_{0}=(v_{0},v_{1})$, $q_{1}=(v_{2},v_{3})$, $q_{2}=(v_{4},v_{5})$, $q_{3}=(v_{6},v_{7})$.
- The next element $f_pos$ is the query position in the folded domain. It can be computed as $posmodn$, where $pos$ is the position in the source domain, and $n$ is size of the folded domain.
- The next element $d_seg$ is a value indicating domain segment from which the position in the original domain was folded. It can be computed as $⌊npos ⌋$. Since the size of the source domain is always $4$ times bigger than the size of the folded domain, possible domain segment values can be $0$, $1$, $2$, or $3$.
- The next element $poe$ is a power of initial domain generator which aid in a computation of the domain point $x$.
- The next two elements contain the result of the previous layer folding - a single element in the extension field denoted as $pe=(pe_{0},pe_{1})$.
- The next two elements specify a random verifier challenge $α$ for the current layer defined as $α=(a_{0},a_{1})$.
- The last element on the top of the stack ($cptr$) is expected to be a memory address of the layer currently being folded.

The diagram below illustrates stack transition for `FRIE2F4`

operation.

At the high-level, the operation does the following:

- Computes the domain value $x$ based on values of $poe$ and $d_seg$.
- Using $x$ and $α$, folds the query values $q_{0},...,q_{3}$ into a single value $r$.
- Compares the previously folded value $pe$ to the appropriate value of $q_{0},...,q_{3}$ to verify that the folding of the previous layer was done correctly.
- Computes the new value of $poe$ as $poe_{′}=poe_{4}$ (this is done in two steps to keep the constraint degree low).
- Increments the layer address pointer by $2$.
- Shifts the stack by $1$ to the left. This moves an element from the stack overflow table into the last position on the stack top.

To keep the degree of the constraints low, a number of intermediate values are used. Specifically, the operation relies on all $6$ helper registers, and also uses the first $10$ elements of the stack at the next state for degree reduction purposes. Thus, once the operation has been executed, the top $10$ elements of the stack can be considered to be "garbage".

TODO: add detailed constraint descriptions. See discussion here.

The effect on the rest of the stack is:

**Left shift**starting from position $16$.

## RCOMBBASE

The `RCOMBBASE`

operation performs a single step in the computation of the random linear combination defining the DEEP composition polynomial i.e., the input to the FRI protocol. More precisely, the sum in question is:
$i=0∑k α_{i}⋅(x−zT_{i}(x)−T_{i}(z) +x−g⋅zT_{i}(x)−T_{i}(g⋅z) )$
where $x$ is the current query to the DEEP composition polynomial for which we are computing the above random linear combination.
The `RCOMBBASE`

instruction computes the numerators $α_{i}⋅(T_{i}(x)−T_{i}(z))$ and $α_{i}⋅(T_{i}(x)−T_{i}(g⋅z))$ and stores the values in two accumulators $p$ and $r$, respectively. This instruction is specialized to main trace columns i.e. the values $T_{i}(x)$ are base field elements. The instruction works in combination with the `mem_stream`

instruction where it is called 8 times in a row for each call to `mem_stream`

.

The stack for the operation is expected to be arranged as follows:

- The first $8$ stack elements contain $8$ base field elements $T_{0},⋯,T_{7}$ representing the values of $T_{i}(x)$ for the current query $x$ and the current batch of $8$ column values of the main trace for query $x$.
- The next $2$ elements contain the current value of the accumulator $p$ as a quadratic extension field element.
- The next $2$ elements contain the current value of the accumulator $r$ as a quadratic extension field element.
- The next element contains the value of the memory pointer
`x_ptr`

to the next batch of $8$ column values for query $x$. - The next element contains the value of the memory pointer
`z_ptr`

to the $i$-th OOD evaluations at z and gz i.e. $T_{i}(z)=(T_{i}(z)_{0},T_{i}(z)_{1})$ and $T_{i}(gz)=(T_{i}(gz)_{0},T_{i}(gz)_{1})$. - The next element contains the value of the memory pointer
`a_ptr`

to the $i$-th random value $α_{i}=(α_{i,0},α_{i,1})$. The remaining elements of the word are expected to be empty.

The diagram below illustrates the stack transition for `RCOMBBASE`

operation.

After calling the `mem_stream `

with `x_ptr`

, the operation does the following:

- Populates the helper registers with $[T_{i}(z)_{0},T_{i}(z)_{1},T_{i}(gz)_{0},T_{i}(gz)_{1},α_{i,0},α_{i,1}]$ using the pointers
`z_ptr`

and`a_ptr`

. - Updates the accumulators $p+=α_{i}⋅(T_{i}(x)−T_{i}(z))$ and $r+=α_{i}⋅(T_{i}(x)−T_{i}(gz)).$
- Increments the pointers
`z_ptr`

and`a_ptr`

by $1$. - The top $8$ base field elements $T_{0},⋯,T_{7}$ are circularly shifted so that
`T_0`

becomes the element at the top of the operand stack.

TODO: add detailed constraint descriptions. See discussion here.

The effect on the rest of the stack is:

**No change.**

The `RCOMBBASE`

makes two memory access request. To simplify the description of these, we first define the following variables :

$v_{1}=i=0∑3 α_{i+5}⋅h_{i}$

$v_{2}=i=0∑1 α_{i+5}⋅h_{i+4}$

Using the above variables, we define the values representing the memory access request as follows:

$u_{mem,1}=α_{0}+α_{1}⋅op_{mem_read}+α_{2}⋅ctx+α_{3}⋅s_{13}+α_{4}⋅clk+v_{1}$

$u_{mem,2}=α_{0}+α_{1}⋅op_{mem_read}+α_{2}⋅ctx+α_{3}⋅s_{14}+α_{4}⋅clk+v_{2}$

$u_{mem}=u_{mem,1}⋅u_{mem,2}$