#! Top-level module for internal MASM precompile support.
#!
#! Concrete precompile support modules live here and in submodules. The helper procedures in this
#! module are shared by generated and hand-written wrappers.
pub mod hashes
pub mod fields
pub mod curves
pub mod u256
# Framework data tag used for generic CHUNKS nodes: Tag::CHUNKS = [2, 0, 0, 0].
const CHUNKS_TAG = [2, 0, 0, 0]
#! Returns whether two words are equal, consuming both words.
#!
#! This helper keeps generated precompile modules independent of `miden-core`. Importing
#! `miden::core::word::eq` here would create a dependency cycle because `miden-core` dynamically
#! depends on `miden-precompiles`.
#!
#! Inputs: [RHS, LHS]
#! Outputs: [is_equal]
#!
#! Where:
#! - RHS is the first word to compare.
#! - LHS is the second word to compare.
#! - is_equal is one if the words are equal, and zero otherwise.
#!
#! Invocation: exec
pub proc word_eq
movup.4 eq swap
movup.4 eq and swap
movup.3 eq and
movdn.2 eq and
end
# ===== DEFERRED-DAG HELPERS =======================================================================
#
# NOTE: `register_value`, `register_mem`, and `log_deferred` are adapted from `miden::core::sys`
# (crates/lib/core/asm/sys/mod.masm) so the internal `miden::precompiles` support namespace can be
# assembled without depending on `miden-core`. This keeps the package dependency one-way because
# `miden-core` depends on `miden-precompiles`. These helpers should be deduplicated against a single
# shared source once the precompile proof-model migration settles.
#
# `register_value` / `register_mem` bind their registration inputs by deriving the node digest with
# VM instructions from the exact same tag and payload. There is intentionally no `evaluate` proc
# here — deferred evaluation returns an *unbound* host hint (`adv.evaluate_deferred_payload` for
# payload-only callers, or `adv.evaluate_deferred` when the canonical tag is also needed), and using
# it soundly requires re-hashing with VM instructions and logging a predicate the verifier
# re-checks.
# That discipline is precompile-specific, so each precompile wraps the raw event itself rather than
# calling a deceptively "safe" shared helper.
#! Registers the expression node currently laid out in Poseidon2 sponge order and returns its
#! content-addressed digest.
#!
#! `adv.register_deferred` interns the node host-side (no stack or advice output); `hperm` then
#! derives the digest inside the VM from the same operand-stack payload. The helper expects the
#! stack in Poseidon2 sponge layout — `[R0=PAYLOAD_LO, R1=PAYLOAD_HI, C=TAG]` — so one `hperm`
#! produces the permuted state and the digest is its rate0 word (`state[0..4]`), matching
#! `Node::digest`.
#!
#! Input: [PAYLOAD_LO, PAYLOAD_HI, TAG, ...]
#! Output: [NODE_DIGEST, ...]
pub proc digest_expr
adv.register_deferred
hperm
swapw.2 dropw dropw
end
#! Registers an expression-bodied node `(tag, payload)` in the deferred-computation DAG and returns
#! its content-addressed digest on top of the operand stack.
#!
#! Input: [TAG, PAYLOAD_LO, PAYLOAD_HI, ...]
#! Output: [NODE_DIGEST, ...]
pub proc register_expr
movdnw.2
# => [PAYLOAD_LO, PAYLOAD_HI, TAG, ...]
exec.digest_expr
end
#! Registers an expression-bodied node `(tag, payload)` in the deferred-computation DAG and returns
#! its content-addressed digest on top of the operand stack.
#!
#! This is the root-module compatibility helper for value-node callers. It is equivalent to
#! `register_expr`.
#!
#! Input: [TAG, PAYLOAD_LO, PAYLOAD_HI, ...]
#! Output: [NODE_DIGEST, ...]
pub proc register_value
exec.register_expr
end
#! Registers a data-bodied node (`n` rate-sized blocks of bulk data at `ptr`) in the
#! deferred-computation DAG and returns its content-addressed digest on top of the operand stack.
#!
#! `adv.register_deferred_data` interns the node host-side by reading `8n` felts from memory at
#! `ptr`, where `n` is the stack-supplied `n_chunks`; it produces no stack or advice output. The
#! event leaves those stack arguments unchanged, but its direct host read adds no AIR memory access.
#! This wrapper binds the registration input by applying a Poseidon2 linear hash with VM instructions
#! to the exact same `TAG` and ordered chunk sequence, using `TAG` as the initial capacity and one
#! `hperm` per 8-felt block loaded by `mem_stream`. The rate0 word matches `Node::digest`. Empty data
#! bodies are forbidden, so `n ≥ 1` and the linear-hash loop always runs at least one permutation.
#!
#! Input: [TAG, ptr, n_chunks, ...]
#! Output: [NODE_DIGEST, ...]
pub proc register_mem
adv.register_deferred_data
# => [TAG, ptr, n_chunks, ...]
# Replace n_chunks with end_addr = ptr + 8 * n_chunks, leaving [TAG, ptr, end_addr, ...].
movup.5 mul.8 dup.5 add movdn.5
# => [TAG, ptr, end_addr, ...]
# Build the sponge state: rate = 0, capacity = TAG, with [start_addr, end_addr] beneath it.
padw padw
# => [R0=0w, R1=0w, C=TAG, start_addr=ptr, end_addr, ...]
# Linear hash: absorb one 8-felt block per iteration until start_addr reaches end_addr.
# Data bodies are non-empty, so use a do-while shape and avoid a redundant pre-loop compare.
push.1
while.true
mem_stream hperm
dup.13 dup.13 neq
end
# => [R0', R1', C', end_addr, end_addr, ...]
swapw.2 dropw dropw
# => [NODE_DIGEST, end_addr, end_addr, ...] (digest = rate0)
movup.4 drop movup.4 drop
# => [NODE_DIGEST, ...]
end
#! Registers memory as framework CHUNKS data and returns its content-addressed digest.
#!
#! Input: [ptr, n_chunks, ...]
#! Output: [CHUNKS_DIGEST, ...]
pub proc register_chunks_mem
push.CHUNKS_TAG
# => [Tag::CHUNKS=[2,0,0,0], ptr, n_chunks, ...]
exec.register_mem
end
#! Registers exactly one 8-felt memory chunk as framework CHUNKS data.
#!
#! Input: [ptr, ...]
#! Output: [CHUNKS_DIGEST, ...]
pub proc register_chunks_mem_1
push.1 swap push.CHUNKS_TAG
# => [Tag::CHUNKS=[2,0,0,0], ptr, n_chunks=1, ...]
adv.register_deferred_data
padw padw
# => [R0=0w, R1=0w, C=Tag::CHUNKS, ptr, n_chunks=1, ...]
mem_stream hperm
# => [R0', R1', C', ptr+8, n_chunks=1, ...]
swapw.2 dropw dropw
# => [CHUNKS_DIGEST, ptr+8, n_chunks=1, ...]
movup.4 drop movup.4 drop
# => [CHUNKS_DIGEST, ...]
end
#! Registers exactly two contiguous 8-felt memory chunks as framework CHUNKS data.
#!
#! Input: [ptr, ...]
#! Output: [CHUNKS_DIGEST, ...]
pub proc register_chunks_mem_2
push.2 swap push.CHUNKS_TAG
# => [Tag::CHUNKS=[2,0,0,0], ptr, n_chunks=2, ...]
adv.register_deferred_data
padw padw
# => [R0=0w, R1=0w, C=Tag::CHUNKS, ptr, n_chunks=2, ...]
repeat.2
mem_stream hperm
end
# => [R0'', R1'', C'', ptr+16, n_chunks=2, ...]
swapw.2 dropw dropw
# => [CHUNKS_DIGEST, ptr+16, n_chunks=2, ...]
movup.4 drop movup.4 drop
# => [CHUNKS_DIGEST, ...]
end
#! Registers exactly three contiguous 8-felt memory chunks as framework CHUNKS data.
#!
#! Input: [ptr, ...]
#! Output: [CHUNKS_DIGEST, ...]
pub proc register_chunks_mem_3
push.3 swap push.CHUNKS_TAG
# => [Tag::CHUNKS=[2,0,0,0], ptr, n_chunks=3, ...]
adv.register_deferred_data
padw padw
# => [R0=0w, R1=0w, C=Tag::CHUNKS, ptr, n_chunks=3, ...]
repeat.3
mem_stream hperm
end
# => [R0''', R1''', C''', ptr+24, n_chunks=3, ...]
swapw.2 dropw dropw
# => [CHUNKS_DIGEST, ptr+24, n_chunks=3, ...]
movup.4 drop movup.4 drop
# => [CHUNKS_DIGEST, ...]
end
#! Folds a deferred node digest into the rolling deferred root using `log_deferred`.
#! The digest must reference a node that will reduce to TRUE under the installed precompiles.
#!
#! Pads the operand stack so `log_deferred`'s implicit `[_, STMNT, _, ...]` shape sees
#! NODE_DIGEST at stack[4..8] (the HPERM rate1 lanes). The opcode folds with framework AND
#! capacity `[1, 0, 0, 0]`, then this helper drops the three words it writes to stack[0..12].
#!
#! Input: [NODE_DIGEST, ...]
#! Output: `[...]` (the deferred commitment root advances; node is folded in)
pub proc log_deferred
padw padw movdnw.2
# => [_, NODE_DIGEST, _, ...]
log_deferred
# => [STATE_NEW, OUT_RATE1, OUT_CAP, ...]
dropw dropw dropw
end