Skip to main content
Version: Next (unstable)

Cryptographic operations

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

Cryptographic operations in Miden VM are supported by the hash, memory, bitwise, and ACE chiplets. The Core AIR removes each typed request from the relevant LogUp relation with negative multiplicity; the AIR or component responsible for that relation adds the matching response with positive multiplicity.

Hasher interactions use typed, domain-separated LogUp messages. For message kind kk, controller address aa, node index nn, and payload pp, write

Hk(a,n,p)=Pk+a+βn+∑i=0∣p∣−1βi+2pi,H_k(a,n,p) = P_k + a + \beta n + \sum_{i=0}^{|p|-1}\beta^{i+2}p_i,

where PkP_k is the fixed bus prefix for that semantic message kind. The separate prefixes domain-separate equal payloads except with the standard random-encoding collision probability.

COMPRESS​

The COMPRESS operation applies one Eidos compression to the top 12 stack elements, arranged as [BLOCK_LO, BLOCK_HI, CV]. The 8-element message block is preserved and the 4-element chaining value is replaced with the updated chaining value:

Before: [BLOCK_LO, BLOCK_HI, CV,  ...]
After: [BLOCK_LO, BLOCK_HI, CV', ...]

The prover supplies the hasher-controller row address in helper register h0h_0. The controller row commits the input state and returned chaining value to one physical 32-row block in the standalone Eidos compression AIR. The decoder removes both typed messages from the chiplet bus.

For COMPRESS, define the input and output values as follows:

vinput=Hlinear_init(h0,0,[s0,…,s11])v_{input} = H_{linear\_init}(h_0, 0, [s_0,\ldots,s_{11}]) voutput=Hreturn(h0,0,[s8′,…,s11′])v_{output} = H_{return}(h_0, 0, [s'_8,\ldots,s'_{11}])

The one-row controller overlays its input and output at the same address; the compression AIR enforces the 32-row computation behind that controller row.

The Core row removes both messages with multiplicity −1-1. Matching positive messages from the hasher controller bind the input state and returned chaining value to a valid Eidos compression.

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

  • No change in positions 00 through 77 and from position 1212 onward.

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 (VV in the below image)
  • Depth of the path, 1 element (dd in the below image)
  • Index of the node, 1 element (ii in the below image)
  • Root of the tree, 4 elements (RR 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.

mpverify

In the above, rr (located in helper register h0h_0) is the row address from the hash chiplet set by the prover non-deterministically. Helper register h1h_1 contains the first Merkle direction bit, denoted bb below.

For the MPVERIFY operation, we define input and output values as follows:

vinput=Hmerkle_verify(h0,s5,[b,s0,…,s3])v_{input} = H_{merkle\_verify}(h_0, s_5, [b,s_0,\ldots,s_3]) voutput=Hreturn(h0+s4−1,0,[s6,…,s9])v_{output} = H_{return}(h_0 + s_4 - 1, 0, [s_6,\ldots,s_9])

The input carries the leaf node and its Merkle index; the output carries the resulting root.

The Core row removes both messages with multiplicity −1-1. Matching controller messages enforce that the input and output rows are present and exactly d−1d - 1 rows apart, where dd is the depth of the node. Each Merkle level contributes one controller row; that row overlays its input and output messages.

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

  • No change starting from position 00.

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 (VV in the below image)
  • depth of the node, 1 element (dd in the below image)
  • index of the node, 1 element (ii in the below image)
  • current root of the tree, 4 elements (RR in the below image)
  • new value of the node, 4 element (NVNV 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.

mrupdate

In the above, rr (located in helper register h0h_0) is the row address from the hash chiplet set by the prover non-deterministically. Helper register h1h_1 contains the first Merkle direction bit, denoted bb below.

For the MRUPDATE operation, we define input and output values as follows:

vinputold=Hmerkle_old(h0,s5,[b,s0,…,s3])v_{inputold} = H_{merkle\_old}(h_0, s_5, [b,s_0,\ldots,s_3]) voutputold=Hreturn(h0+s4−1,0,[s6,…,s9])v_{outputold} = H_{return}(h_0 + s_4 - 1, 0, [s_6,\ldots,s_9]) vinputnew=Hmerkle_new(h0+s4,s5,[b,s10,…,s13])v_{inputnew} = H_{merkle\_new}(h_0 + s_4, s_5, [b,s_{10},\ldots,s_{13}]) voutputnew=Hreturn(h0+2⋅s4−1,0,[s0′,…,s3′])v_{outputnew} = H_{return}(h_0 + 2 \cdot s_4 - 1, 0, [s'_0,\ldots,s'_3])

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 used in both of these computations.

The Core row removes all four messages with multiplicity −1-1. Matching controller messages enforce that the input and output rows for both node/root combinations are present. The old-path output is d−1d - 1 rows after the old-path input, the new-path input starts immediately after that at offset dd, and the new-path output is 2⋅d−12 \cdot d - 1 rows after the initial old-path input. This also ensures that the old computation is immediately followed by the new computation.

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

  • No change for positions starting from 44.

Merkle range checks​

MPVERIFY and MRUPDATE request 16-bit range checks for dd and 1024(d−1)1024(d - 1). Together, the checks accept exactly 1≤d≤641 \le d \le 64.

The path bits must also describe the canonical integer represented by the node-index field element. At each controller row, the hash controller enforces

ik=2ik+1+bki_k = 2i_{k+1} + b_k

where bkb_k is a boolean direction bit. This is a field equality modulo Q=264−232+1Q = 2^{64} - 2^{32} + 1. By itself, it does not distinguish an integer index from that index plus QQ.

The stack/hasher lookup addresses bind the computation to exactly dd levels, and the controller ends with id=0i_d = 0. To prevent a wrapped 64-bit bit string from representing the same field element, CoreAir uses the six decoder helpers

[addr, b, y0, y1, y2, y3]

on each MPVERIFY and MRUPDATE row. Here bb is the first direction bit and

y=y0+216y1+232y2+248y3.y = y_0 + 2^{16}y_1 + 2^{32}y_2 + 2^{48}y_3.

For the stack node index nn, Core enforces bb to be boolean and constrains

n+b+2y=Q−1.n + b + 2y = Q - 1.

The typed Merkle-init request carries bb. Its matching controller response derives the first bit as i0−2i1i_0 - 2i_1, so the lookup binds the Core helper to the actual path direction. The RangeCheck bus checks all four limbs and also checks 2y32y_3. The limb checks give yj<216y_j < 2^{16}, and the additional check gives y3<215y_3 < 2^{15}, hence 0≤y<2630 \le y < 2^{63}. Together with the equation and the controller binding of bb, this rules out the non-canonical depth-64 alias.

All seven Merkle range requests are emitted from existing Core lookup columns:

  • the stack-overflow column checks y0y_0, y1y_1, and y2y_2 for both operations;
  • for MPVERIFY, the chiplet-request column checks y3y_3, while the block-stack/range/log-deferred column checks dd, 1024(d−1)1024(d - 1), and 2y32y_3;
  • for MRUPDATE, the block-stack/range/log-deferred column checks dd, 1024(d−1)1024(d - 1), y3y_3, and 2y32y_3.

The execution tracer replays the corresponding requests into the fixed And8 range-table multiplicities. No canonical-index witness is stored in the native hash controller.

CRYPTOSTREAM​

CRYPTOSTREAM encrypts two words from memory using one Eidos XOF counter block. Its stack transition is

Before: [K_CTR(4), counter, src,   dst,    remaining, ...]
After: [K_CTR(4), counter+1, src+8, dst+16, remaining-1, ...]

The Eidos compression input is [counter, 0, 0, 0, 0, 0, 0, 0, K_CTR]. The raw XOF result supplies sixteen u32 keystream lanes. Each of the eight plaintext field elements is unpacked into low and high u32 limbs, XORed with the corresponding lanes byte by byte, and written as two field elements. This is why eight input elements advance dst by sixteen elements.

The AIR binds three parts of the operation through typed LogUp relations:

  • the core row supplies the clock-tagged Eidos XOF input consumed by the compression trace;
  • two 8-row AEAD-stream trace entries prove the two four-element source reads and four two-element destination writes;
  • the And8 lookup table proves every byte-level XOR used to form the expanded ciphertext limbs.

The core constraints preserve K_CTR and stack positions 88 through 1515, increment the counter and pointers by their fixed amounts, and decrement remaining. Address validation requires word-aligned, non-overlapping source and destination blocks.

FRIE2F4​

The FRIE2F4 operation performs one factor-4 FRI layer fold over the quadratic extension field. It also checks consistency with the previous folded layer and writes the loop state consumed by the next FRI layer.

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

  • The first 88 stack elements contain 44 opened leaf values to be folded. Each value is represented by two field elements. The leaf values are stored in bit-reversed order: q0=(v0,v1)q_0 = (v_0, v_1), q2=(v2,v3)q_2 = (v_2, v_3), q1=(v4,v5)q_1 = (v_4, v_5), q3=(v6,v7)q_3 = (v_6, v_7).
  • The next element f_posf\_pos is the query position in the folded domain. It can be computed as posmod  npos \mod n, where pospos is the position in the source domain, and nn is size of the folded domain.
  • The next element is the natural coset index ⌊posn⌋\lfloor \frac{pos}{n} \rfloor. Since the size of the source domain is always 44 times bigger than the size of the folded domain, possible coset values are 00, 11, 22, and 33.
  • The next element poepoe is a power of the current source-domain generator used to compute the domain point xx.
  • The next two elements contain the result of the previous layer folding - a single element in the extension field denoted as pe=(pe0,pe1)pe = (pe_0, pe_1).
  • The next two elements specify a random verifier challenge α\alpha for the current layer defined as α=(a0,a1)\alpha = (a_0, a_1).
  • The last element on the top of the stack (cptrcptr) is expected to be a memory address of the layer currently being folded.

The diagram below illustrates stack transition for FRIE2F4 operation.

frie2f4

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

  • Computes the domain value xx based on values of poepoe and the coset index.
  • Using xx and α\alpha, folds the query values q0,...,q3q_0, ..., q_3 into a single value rr.
  • Compares the previously folded value pepe to the leaf value selected by the coset index.
  • Computes the new value of poepoe as poe′=poe4poe' = poe^4 (this is done in two steps to keep the constraint degree low).
  • Increments the layer address pointer by 88.
  • Shifts the stack by 11 to the left. This moves an element from the stack overflow table into the last position on the stack top.

To keep the constraint degree low, the operation uses all 66 helper registers and the first 88 next-state stack elements as degree-reduction intermediates. Callers should treat those 88 output elements as scratch.

TODO: add detailed constraint descriptions. See discussion here.

The effect on the rest of the stack is:

  • Left shift starting from position 1616.

HORNERBASE​

The HORNERBASE operation performs 88 steps of the Horner method for evaluating a polynomial with coefficients over the base field at a point in the quadratic extension field. More precisely, it performs the following updates to the accumulator on the stack:

tmp0=((acc⋅α+c0)⋅α)+c1tmp1=((((tmp0⋅α)+c2)⋅α+c3)⋅α)+c4acc′=((((tmp1⋅α+c5)⋅α+c6)⋅α)+c7)\begin{align*} \mathsf{tmp0} &= ((\mathsf{acc} \cdot \alpha + c_0) \cdot \alpha) + c_1 \\ \mathsf{tmp1} &= ((((\mathsf{tmp0} \cdot \alpha) + c_2) \cdot \alpha + c_3) \cdot \alpha) + c_4 \\ \mathsf{acc}^{'} &= ((((\mathsf{tmp1} \cdot \alpha + c_5) \cdot \alpha + c_6) \cdot \alpha) + c_7) \end{align*}

where cic_i are the coefficients of the polynomial, α\alpha the evaluation point, acc\mathsf{acc} the current accumulator value, acc′\mathsf{acc}^{'} the updated accumulator value, and tmp0\mathsf{tmp0}, tmp1\mathsf{tmp1} are helper variables used for constraint degree reduction.

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

  • The first 88 stack elements (positions 0-7) are the 88 base field elements representing the current 8-element batch of coefficients for the polynomial being evaluated, arranged as [c0,c1,c2,c3,c4,c5,c6,c7][c_0, c_1, c_2, c_3, c_4, c_5, c_6, c_7] where c0c_0 is at position 0 (top of stack). Here c0c_0 is the highest-degree coefficient (α7\alpha^7 term) and c7c_7 is the constant term.
  • The next 55 stack elements are irrelevant for the operation and unaffected by it.
  • The next stack element contains the word-aligned address alpha_ptr pointing to the word [α0,α1,0,0][\alpha_0, \alpha_1, 0, 0], which contains the evaluation point α=(α0,α1)\alpha = (\alpha_0, \alpha_1).
  • The next 22 stack elements contain the value of the current accumulator acc=(acc0,acc1)\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1).

Execution fails if either padding element is nonzero; the AIR enforces the same requirement.

The diagram below illustrates the stack transition for HORNERBASE.

horner_eval_base

After calling the operation:

  • Helper registers contain [α0,α1,tmp10,tmp11,tmp00,tmp01][\alpha_0, \alpha_1, \mathsf{tmp1}_0, \mathsf{tmp1}_1, \mathsf{tmp0}_0, \mathsf{tmp0}_1].
  • Stack elements 1414 and 1515 contain the updated accumulator acc′\mathsf{acc}^{'}.

More specifically, the stack transition for this operation must satisfy the following constraints. Here α=(α0,α1)\alpha = (\alpha_0, \alpha_1) is an element of Fp2\mathbb{F}_{p^2} with u2=7u^2 = 7. We write c0=(c0,0,c0,1)c_0 = (c_{0,0}, c_{0,1}), c1=(c1,0,c1,1)c_1 = (c_{1,0}, c_{1,1}), c2=(c2,0,c2,1)c_2 = (c_{2,0}, c_{2,1}), and c3=(c3,0,c3,1)c_3 = (c_{3,0}, c_{3,1}) for the extension-field coefficients.

α2=(α02,α12)=(α02+7α12,2α0α1)α3=(α03,α13)=(α03+21α0α12,3α02α1+7α13)tmp00=acc0⋅α02+acc1⋅(7α12)+c0α0+c1tmp01=acc0⋅α12+acc1⋅α02+c0α1tmp10=tmp00⋅α03+tmp01⋅(7α13)+c2α02+c3α0+c4tmp11=tmp00⋅α13+tmp01⋅α03+c2α12+c3α1acc0′=tmp10⋅α03+tmp11⋅(7α13)+c5α02+c6α0+c7acc1′=tmp10⋅α13+tmp11⋅α03+c5α12+c6α1\begin{align*} \alpha^2 &= (\alpha^2_0, \alpha^2_1) = (\alpha_0^2 + 7 \alpha_1^2, 2 \alpha_0 \alpha_1) \\ \alpha^3 &= (\alpha^3_0, \alpha^3_1) = (\alpha_0^3 + 21 \alpha_0 \alpha_1^2, 3 \alpha_0^2 \alpha_1 + 7 \alpha_1^3) \\ \mathsf{tmp0}_0 &= \mathsf{acc}_0 \cdot \alpha^2_0 + \mathsf{acc}_1 \cdot (7 \alpha^2_1) + c_0 \alpha_0 + c_1 \\ \mathsf{tmp0}_1 &= \mathsf{acc}_0 \cdot \alpha^2_1 + \mathsf{acc}_1 \cdot \alpha^2_0 + c_0 \alpha_1 \\ \\ \mathsf{tmp1}_0 &= \mathsf{tmp0}_0 \cdot \alpha^3_0 + \mathsf{tmp0}_1 \cdot (7 \alpha^3_1) + c_2 \alpha^2_0 + c_3 \alpha_0 + c_4 \\ \mathsf{tmp1}_1 &= \mathsf{tmp0}_0 \cdot \alpha^3_1 + \mathsf{tmp0}_1 \cdot \alpha^3_0 + c_2 \alpha^2_1 + c_3 \alpha_1 \\ \\ \mathsf{acc}_0^{'} &= \mathsf{tmp1}_0 \cdot \alpha^3_0 + \mathsf{tmp1}_1 \cdot (7 \alpha^3_1) + c_5 \alpha^2_0 + c_6 \alpha_0 + c_7 \\ \mathsf{acc}_1^{'} &= \mathsf{tmp1}_0 \cdot \alpha^3_1 + \mathsf{tmp1}_1 \cdot \alpha^3_0 + c_5 \alpha^2_1 + c_6 \alpha_1 \end{align*}

HORNERBASE removes one typed MemoryReadWord message with payload [ctx, s13, clk, h0, h1, 0, 0]. Matching it against the memory-chiplet response also constrains the unused half of the word to zero:

Mread_word(ctx,s13,clk,[h0,h1,0,0]).M_{read\_word}(ctx,s_{13},clk,[h_0,h_1,0,0]).

The effect on the rest of the stack is:

  • No change.

HORNEREXT​

The HORNEREXT operation performs 44 steps of the Horner method for evaluating a polynomial with coefficients over the quadratic extension field at a point in the quadratic extension field. More precisely, it performs the following update to the accumulator on the stack tmp=(acc⋅α+c3)⋅α+c2\mathsf{tmp} = (\mathsf{acc} \cdot \alpha + c_3) \cdot \alpha + c_2 acc′=(tmp⋅α+c1)⋅α+c0\mathsf{acc}^{'} = (\mathsf{tmp} \cdot \alpha + c_1) \cdot \alpha + c_0

where cic_i are the coefficients of the polynomial, α\alpha the evaluation point, acc\mathsf{acc} the current accumulator value, acc′\mathsf{acc}^{'} the updated accumulator value, and tmp\mathsf{tmp} is a helper variable used for constraint degree reduction.

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

  • The first 88 stack elements contain 88 base field elements that make up the current 4-element batch of coefficients, in the quadratic extension field, for the polynomial being evaluated. We interpret these coefficients as c0=(s0,s1)c_0 = (s_0, s_1), c1=(s2,s3)c_1 = (s_2, s_3), c2=(s4,s5)c_2 = (s_4, s_5), and c3=(s6,s7)c_3 = (s_6, s_7).
  • The next 55 stack elements are irrelevant for the operation and unaffected by it.
  • The next stack element contains the word-aligned address alpha_ptr pointing to the word [α0,α1,0,0][\alpha_0, \alpha_1, 0, 0], which contains the evaluation point α=(α0,α1)\alpha = (\alpha_0, \alpha_1).
  • The next 22 stack elements contain the value of the current accumulator acc=(acc0,acc1)\textsf{acc} = (\textsf{acc}_0, \textsf{acc}_1).

Execution fails if either padding element is nonzero; the AIR enforces the same requirement.

The diagram below illustrates the stack transition for HORNEREXT.

horner_eval_ext

After calling the operation:

  • Helper registers h0h_0 and h1h_1 contain (α0,α1)(\alpha_0, \alpha_1), h2h_2 and h3h_3 are unused, and h4h_4 and h5h_5 contain the intermediate extension-field value tmp\mathsf{tmp}.
  • Stack elements 1414 and 1515 contain the updated accumulator acc′\mathsf{acc}^{'}.

More specifically, the stack transition for this operation must satisfy the following constraints. Here α=(α0,α1)\alpha = (\alpha_0, \alpha_1) is an element of Fp2\mathbb{F}_{p^2} with u2=7u^2 = 7.

α2=(α02,α12)=(α02+7α12,2α0α1)tmp0=acc0⋅α02+acc1⋅(7α12)+c0,0α0+7c0,1α1+c1,0tmp1=acc0⋅α12+acc1⋅α02+c0,0α1+c0,1α0+c1,1acc0′=tmp0⋅α02+tmp1⋅(7α12)+c2,0α0+7c2,1α1+c3,0acc1′=tmp0⋅α12+tmp1⋅α02+c2,0α1+c2,1α0+c3,1\begin{align*} \alpha^2 &= (\alpha^2_0, \alpha^2_1) = (\alpha_0^2 + 7 \alpha_1^2, 2 \alpha_0 \alpha_1) \\ \mathsf{tmp}_0 &= \mathsf{acc}_0 \cdot \alpha^2_0 + \mathsf{acc}_1 \cdot (7 \alpha^2_1) + c_{0,0} \alpha_0 + 7 c_{0,1} \alpha_1 + c_{1,0} \\ \mathsf{tmp}_1 &= \mathsf{acc}_0 \cdot \alpha^2_1 + \mathsf{acc}_1 \cdot \alpha^2_0 + c_{0,0} \alpha_1 + c_{0,1} \alpha_0 + c_{1,1} \\ \\ \mathsf{acc}_0^{'} &= \mathsf{tmp}_0 \cdot \alpha^2_0 + \mathsf{tmp}_1 \cdot (7 \alpha^2_1) + c_{2,0} \alpha_0 + 7 c_{2,1} \alpha_1 + c_{3,0} \\ \mathsf{acc}_1^{'} &= \mathsf{tmp}_0 \cdot \alpha^2_1 + \mathsf{tmp}_1 \cdot \alpha^2_0 + c_{2,0} \alpha_1 + c_{2,1} \alpha_0 + c_{3,1} \end{align*}

The effect on the rest of the stack is:

  • No change.

HORNEREXT removes the same typed MemoryReadWord message, which also constrains the unused half of the word to zero:

Mread_word(ctx,s13,clk,[h0,h1,0,0]).M_{read\_word}(ctx,s_{13},clk,[h_0,h_1,0,0]).

EVALCIRCUIT​

The EVALCIRCUIT operation evaluates an arithmetic circuit, given its circuit description and a set of input values, using the ACE chiplet and asserts that the evaluation is equal to zero.

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

  • A pointer to the circuit description with the expected layout by the ACE chiplet.
  • The number of quadratic extension field elements that are read during the READ phase of circuit evaluation.
  • The number of base field elements representing the encodings of instructions that make up the circuit being evaluated during the EVAL phase of circuit evaluation.

The diagram below illustrates this graphically.

evalcircuit

Calling the operation has no effect on the stack or on helper registers. Instead, it removes one typed AceInit message with multiplicity −1-1:

vace=Pace_init+clk+βctx+β2ptr+β3nread+β4neval.v_{ace} = P_{ace\_init} + clk + \beta ctx + \beta^2 ptr + \beta^3 n_{read} + \beta^4 n_{eval}.

where:

  • ctxctx is the memory context from which the operation was initiated,
  • clkclk is the clock cycle at which the operation was initiated,
  • ptrptr, nreadn_{read} and nevaln_{eval} are as above.

The ACE chiplet adds the matching message with multiplicity +1+1.

LOG_DEFERRED​

The log_deferred operation folds a verified statement digest STMNT into the rolling deferred root. The update is the structural digest of Node::and(ROOT_PREV, STMNT), computed as one Eidos compression using the fixed chaining value derived from the registered deferred-AND domain: ROOT_NEW = Eidos::compress(DEFERRED_AND_INIT_CV, ROOT_PREV || STMNT). The VM STARK authenticates the final root as one public value.

Operation Overview​

The stack is expected to be arranged as [STMNT, ...]. STMNT must already be present in the processor's deferred state and evaluate to TRUE; otherwise execution fails when the opcode attempts to log it. Core-library and precompile support code wrap this low-level opcode by registering nodes and logging statement digests.

Additionally, the processor maintains a persistent rolling deferred root that is updated with each LOG_DEFERRED invocation. The previous root is provided non‑deterministically via helper registers and is denoted ROOT_PREV. The hasher bus links the constrained Eidos compression to the stack transition, while the deferred state enforces that the logged statement evaluates to TRUE.

The operation has the following stack transition:

Before:  [STMNT,    ...]
After: [ROOT_NEW, ...]

The top word is replaced with ROOT_NEW; the remaining stack is preserved. Wrappers usually drop ROOT_NEW after the opcode.

The operation uses the following helper registers:

  • h0h_0: Hasher chiplet row address
  • h1,h2,h3,h4h_1, h_2, h_3, h_4: Previous deferred root ROOT_PREV

Note: helper registers expose ROOT_PREV for bus constraints only; the VM maintains the deferred root internally between invocations.

Bus Communication​

Hasher chiplet​

The following two messages are sent to the hasher controller, ensuring the validity of the compression. Let sis_i denote the ii-th stack column at that row (top of stack is s0s_0). The elements appearing on the bus are:

ROOTiprev=hi+1(helper registers)STMNTi=si(stack slots 0..3)CVi=DEFERRED_AND_INIT_CVi(registered Eidos chaining value)i∈{0,1,2,3}.\begin{aligned} \mathsf{ROOT}^{\text{prev}}_i &= h_{i+1} &&\text{(helper registers)}\\ \mathsf{STMNT}_i &= s_i &&\text{(stack slots 0..3)}\\ \mathsf{CV}_i &= \mathsf{DEFERRED\_AND\_INIT\_CV}_i &&\text{(registered Eidos chaining value)} \end{aligned} \qquad i \in \{0,1,2,3\}.

The input message reduces the Eidos compression state in the canonical order [ROOT_PREV, STMNT, DEFERRED_AND_INIT_CV]:

vinput=Hlinear_init(h0,0,[ROOTprev,STMNT,CV]).v_{\text{input}} = H_{linear\_init}(h_0, 0, [\mathsf{ROOT}^{\text{prev}}, \mathsf{STMNT}, \mathsf{CV}]).

The same one-row controller entry returns the updated chaining word with a typed return message. Denote the stack after the instruction by si′s'_i:

ROOTinew=si′i∈{0,1,2,3}.\mathsf{ROOT}^{\text{new}}_i = s'_{i} \qquad i \in \{0,1,2,3\}.

and the response message is

voutput=Hreturn(h0,0,ROOTnew).v_{\text{output}} = H_{return}(h_0, 0, \mathsf{ROOT}^{\text{new}}).

The Core row removes both messages with multiplicity −1-1. Matching positive controller messages enforce that they occur on one hasher-controller row backed by the same physical Eidos compression cycle.

Deferred-root Initialization​

Inside the VM, the deferred root is tracked by the typed LogDeferredRoot virtual-table relation: each log_deferred update removes the previous root before inserting the next one.

Let D(r)=Plog_deferred+∑j=03βjrjD(r) = P_{log\_deferred} + \sum_{j=0}^{3}\beta^j r_j. We denote the messages for removing and inserting the root as

vrem=D(ROOT_PREV)v_{rem} = D(\mathsf{ROOT\_PREV}) vins=D(ROOT_NEW)v_{ins} = D(\mathsf{ROOT\_NEW})

The previous-root message has multiplicity −1-1 and the new-root message has multiplicity +1+1. The verifier supplies two boundary contributions so the chain has fixed endpoints: it adds the initial TRUE_DIGEST (the zero word) and removes the final four-felt deferred root committed by the VM trace.

The boundary messages are therefore

vins,init=D([0,0,0,0]),v_{ins,init} = D([0,0,0,0]), vrem,last=D(ROOT_FINAL).v_{rem,last} = D(\mathsf{ROOT\_FINAL}).

Because this compression uses the domain-derived DEFERRED_AND_INIT_CV and outputs an updated chaining word directly, that word is the deferred root at every step. The final deferred root is a fixed four-field-element value committed by VmProof, not a variable-length request transcript.