Skip to main content
Version: Next (unstable)

Cryptographic hashes

Namespace miden::core::crypto::hashes contains modules for commonly used cryptographic hash functions.

BLAKE3​

Module miden::core::crypto::hashes::blake3 contains procedures for computing hashes using BLAKE3 hash function. The input and output elements are assumed to contain one 32-bit value per element.

ProcedureDescription
hashComputes BLAKE3 1-to-1 hash.

Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).

Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
mergeComputes BLAKE3 2-to-1 hash.

Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).

Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element)

Keccak256​

Module miden::core::crypto::hashes::keccak256 contains procedures for computing hashes using Keccak256.

Data is represented using u32 arrays and u8 arrays with the following conventions:

  • VALUE_U32[n] = arrays of n u32 values, denoted as [v_0, ..., v_{n-1}]
  • VALUE_U8[n] = arrays of n u8 values, denoted as [b_0, ..., b_{n-1}]
  • Conversion: v_i = u32::from_le_bytes([b_{4i}, b_{4i+1}, b_{4i+2}, b_{4i+3}])

All stack inputs and output digests are represented on the stack as u32 arrays with the least significant element at the top. For example, a 256-bit digest is defined as DIGEST_U32[8] = [d_0, ..., d_7] and is placed on the stack as [d_0, ..., d_7] with d_0 at the top. Memory inputs follow the same convention with the least significant u32 value at the lowest address.

ProcedureDescription
hash_bytesComputes Keccak256 hash of data stored in memory.

Input: [ptr, len_bytes, ...]
Output: [DIGEST_U32[8], ...]

Where:
- ptr: word-aligned memory address containing INPUT_U32[len_u32] where len_u32=⌈len_bytes/4⌉
- len_bytes: number of bytes to hash
- INPUT_U32[len_u32] ~ INPUT_U8[len_bytes] with u32 packing (unused bytes in final u32 must be 0)
- DIGEST_U32[8] = [d_0, ..., d_7] = Keccak256(INPUT_U8[len_bytes])
hashComputes Keccak256 hash of a single 256-bit input.

Input: [INPUT_U32[8], ...]
Output: [DIGEST_U32[8], ...]

Where:
- DIGEST_U32[8] = [d_0, ..., d_7] = Keccak256(INPUT_U8[32])
- INPUT_U32[8] = [i_0, ..., i_7] = [INPUT_LO, INPUT_HI] ~ INPUT_U8[32] with u32 packing
mergeMerges two 256-bit digests via Keccak256 hash.

Input: [INPUT_L_U32[8], INPUT_R_U32[8], ...]
Output: [DIGEST_U32[8], ...]

Where:
- INPUT_L_U32[8] = [l_0, ..., l_7] = [INPUT_L_LO, INPUT_L_HI] ~ INPUT_L_U8[32]
- INPUT_R_U32[8] = [r_0, ..., r_7] = [INPUT_R_LO, INPUT_R_HI] ~ INPUT_R_U8[32]
- DIGEST_U32[8] = [d_0, ..., d_7] = Keccak256(INPUT_L_U8[32] concatenated with INPUT_R_U8[32])

SHA512​

Module miden::core::precompiles::hashes::sha512 provides deferred SHA-512 hashing. Its hash_bytes_mem procedure consumes [in_ptr, len_bytes, out_ptr, ...] and writes the complete 64-byte digest as sixteen little-endian packed u32 felts at out_ptr.

Both pointers must be word-aligned. Input bytes use the same packed representation, with zero unused bytes and felts in the final 32-byte chunk. The byte length must fit the configured max_hash_len_bytes execution limit. Input and output ranges may overlap. The logged deferred assertion binds the exact input length and all 64 output bytes; completing the execution proof also proves the SHA-512 computation.

SHA256 precompile​

Module miden::core::precompiles::hashes::sha256 provides deferred SHA-256 hashing. Its hash_bytes_mem procedure consumes [in_ptr, len_bytes, out_ptr, ...] and writes the 32-byte digest as eight little-endian packed u32 felts at out_ptr.

Both pointers must be word-aligned. Input bytes use the same packed representation, with zero unused bytes and felts in the final 32-byte chunk. The byte length must fit the configured max_hash_len_bytes execution limit. Input and output ranges may overlap. The logged deferred assertion binds the exact input length and all 32 output bytes; completing the execution proof also proves the SHA-256 computation. The miden::core::crypto::hashes::sha256 module below computes SHA-256 directly in MASM instead.

SHA256​

Module miden::core::crypto::hashes::sha256 contains procedures for computing hashes using SHA256 hash function. The input and output elements are assumed to contain one 32-bit value per element.

ProcedureDescription
hashComputes SHA256 1-to-1 hash.

Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element).

Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
mergeComputes SHA256 2-to-1 hash.

Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element).

Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element).
hash_bytesGiven a memory address and a message length in bytes, computes its SHA256 digest. There must be space for writing the padding after the message in memory, and the padding space must be all zeros before the procedure is called.

Input: [addr, len, ...]
Output: [dig0, dig1, ..., dig7, ...]

Panics if any loaded message word is not a valid 32-bit unsigned integer or padding range checks fail.

Eidos​

Module miden::core::crypto::hashes::eidos contains the VM-native Eidos hashing helpers. Eidos frames a domain tag and three domain-defined parameters in an initial chaining word, then absorbs 8-field-element blocks with Eidos compression. A digest is one word (4 field elements).

The total input length is fixed before the first compression. For a manual chain, use init for the generic Felt-sequence domain or init_in_domain for an explicit domain. These procedures only initialize the state; an empty message still requires one zero-block compression. The one-shot hashing procedures handle the empty and partial-block rules themselves.

ProcedureDescription
init_chaining_word_with_paramsConstructs an initial chaining word from a domain tag and three domain-defined parameters. Every input may use the complete u32 range.

Input: [domain_tag, param0, param1, param2, ...]
Output: [CV, ...]
empty_felt_sequence_digestReturns the Eidos digest of an empty generic Felt sequence.

Input: [...]
Output: [DIGEST, ...]
init_chaining_wordConstructs the initial chaining word for n generic Felts. n must fit in a u32.

Input: [n, ...]
Output: [CV, ...]
init_chaining_word_in_domainConstructs the initial chaining word for n Felts under domain_tag. Both inputs must fit in a u32; the caller must validate registry membership.

Input: [domain_tag, n, ...]
Output: [CV, ...]
init_with_chaining_wordAdds two zero block words above a caller-supplied chaining word.

Input: [CV, ...]
Output: [BLOCK_LO=0w, BLOCK_HI=0w, CV, ...]
initInitializes a three-word state for a known-length generic Felt sequence.

Input: [num_elements, ...]
Output: [BLOCK_LO=0w, BLOCK_HI=0w, CV, ...]
init_in_domainInitializes a three-word state for a known-length message in the given domain.

Input: [domain, num_elements, ...]
Output: [BLOCK_LO=0w, BLOCK_HI=0w, CV, ...]
compressPerforms one Eidos compression and updates the chaining word.

Input: [BLOCK_LO, BLOCK_HI, CV, ...]
Output: [BLOCK_LO, BLOCK_HI, CV', ...]
digestDrops the two block words and returns the current chaining word. The returned word is a final digest only after the framed Eidos schedule is complete.

Input: [BLOCK_LO, BLOCK_HI, CV, ...]
Output: [CV, ...]
copy_digestCopies the current chaining word without consuming the live state.

Input: [BLOCK_LO, BLOCK_HI, CV, ...]
Output: [CV_COPY, BLOCK_LO, BLOCK_HI, CV, ...]
absorb_double_words_from_memoryContinues a live chain over zero or more complete 8-Felt blocks. An empty range leaves the state unchanged.

Input: [BLOCK_LO, BLOCK_HI, CV, start_addr, end_addr, ...]
Output: [BLOCK_LO', BLOCK_HI', CV', end_addr, end_addr, ...]
hash_words_in_domainHashes the word-aligned memory range [start_addr, end_addr) with a domain identifier. The input length is bound into the initial chaining word.

Input: [domain, start_addr, end_addr, ...]
Output: [H, ...]
hash_wordsHashes a word-aligned memory range under the generic Felt-sequence domain.

Input: [start_addr, end_addr, ...]
Output: [H, ...]
prepare_hasher_statePrepares the state consumed by hash_elements_with_state. A zero padding flag binds num_elements; a one flag binds the length rounded up to a complete block and replaces unused final lanes with zeros. For empty input, the prepared CV already includes the required zero-block compression.

Input: [ptr, num_elements, pad_inputs_flag, ...]
Output: [BLOCK_LO, BLOCK_HI, CV, ptr, end_pairs_addr, num_elements%8, ...]
hash_elements_with_stateHashes a prepared memory range and returns its current chaining word. An empty continuation returns the supplied CV unchanged.

Input: [BLOCK_LO, BLOCK_HI, CV, ptr, end_pairs_addr, num_elements%8, ...]
Output: [HASH, ...]
hash_elementsHashes num_elements field elements from word-aligned memory under the generic Felt-sequence domain.

Input: [ptr, num_elements, ...]
Output: [HASH, ...]
hash_elements_in_domainHashes num_elements field elements from word-aligned memory and binds both their exact count and domain.

Input: [domain, ptr, num_elements, ...]
Output: [HASH, ...]
pad_and_hash_elementsHashes after extending the logical input with zeros to the next 8-felt block. The padded length, rather than the unpadded length, is committed.

Input: [ptr, num_elements, ...]
Output: [HASH, ...]
hashComputes the VM-native hash of one word.

Input: [A, ...]
Output: [B, ...]
hash_double_wordHashes A || B as a generic eight-Felt sequence. This is the core-library form of hdword.

Input: [A, B, ...]
Output: [C, ...]
compress_double_wordCompresses one eight-Felt block with a caller-supplied chaining word and returns the updated word. Adds no domain or length framing.

Input: [CV, A, B, ...]
Output: [CV', ...]
hash_double_word_in_domainHashes A || B under domain, binding length eight. The caller must validate registry membership.

Input: [domain, A, B, ...]
Output: [C, ...]

Use init_with_chaining_word only when the surrounding protocol defines the supplied CV. For ordinary protocol commitments, prefer the length-bound initializers or the one-shot hashing procedures.

MASM procedures ending in _in_domain take the domain on top of the stack. Rust hashing APIs take the input first and the typed domain last. For the same Felts, domain, and logical length, stack and memory hashing produce the same digest. In particular, hash_double_word and hash_words agree on an eight-Felt input. hash_words hashes the entire range as one sequence, binding its total length.

For Merkle internal nodes, the fixed construction used by Eidos::merge and the VM's native Merkle operations sets the domain tag and all three initialization parameters to zero. Trees verified by these operations must use this construction; applications can define their own leaf commitments.

hash_double_word_in_domain sets the first initialization parameter to eight, the input length. Passing domain zero to this MASM procedure therefore produces a different initial chaining word from Eidos::merge. compress_double_word uses the caller-supplied chaining word directly.