Skip to main content

miden_core/program/
domain.rs

1//! Registered domain selectors for protocol-visible hash commitments.
2//!
3//! This module follows the Miden domain-separation RFC
4//! (<https://github.com/0xMiden/crypto/pull/1026>): consensus-critical domains use **registered
5//! numeric identifiers** rather than hashed strings, packed as
6//!
7//! ```text
8//! selector = (domain_id << 8) | version
9//! ```
10//!
11//! with `domain_id` a registered 24-bit integer (`>= 1`) and `version` an 8-bit per-domain
12//! version (`>= 1`). The selector rides in the second capacity element of the Poseidon2 sponge
13//! (`hash_elements_in_domain`); the first capacity element is hash-owned and carries the padding
14//! rule, mirroring the RFC's frame/selector lane split. Unused parameter lanes are zero.
15//!
16//! # Provisional registry entries
17//!
18//! The RFC's draft registry allocates `0x010000..0x01ffff` to miden-vm, with concrete entries
19//! delegated to this repository. These are the range's first entries, to be migrated into the
20//! machine-readable registry when it lands:
21//!
22//! | domain_id  | version | domain |
23//! |------------|---------|-------------------------------------------|
24//! | `0x010000` | 1       | kernel commitment ([`KERNEL_DOMAIN_TAG`](super::KERNEL_DOMAIN_TAG)) |
25//! | `0x010001` | 1       | execution claim ([`CLAIM_DOMAIN_TAG`](super::CLAIM_DOMAIN_TAG)) |
26//! | `0x010002` | 1       | proof request key ([`REQUEST_DOMAIN_TAG`](super::REQUEST_DOMAIN_TAG)) |
27//!
28//! Selectors share one capacity namespace with the `merge_in_domain` values used for MAST
29//! control-block hashing. Those are opcode-sized (`< 256`) while every registered selector is
30//! `>= 257` (`domain_id >= 1`), so those two ranges cannot collide. Distinctness among registered
31//! selectors is the registry's responsibility: each `domain_id` is allocated once within its
32//! maintainer's range, and the three defined here are pinned distinct by
33//! `registry_entries_are_valid_and_distinct_selectors`.
34
35use crate::Felt;
36
37/// Registered domain id for the kernel commitment.
38pub const KERNEL_COMMITMENT_DOMAIN_ID: u32 = 0x010000;
39
40/// Registered domain id for the execution-claim commitment.
41pub const EXECUTION_CLAIM_DOMAIN_ID: u32 = 0x010001;
42
43/// Registered domain id for the proof-request key.
44pub const PROOF_REQUEST_DOMAIN_ID: u32 = 0x010002;
45
46/// Packs a registered domain id and per-domain version into a domain selector.
47///
48/// The result is a small integer (`domain_id << 8 | version`), used as the domain element of
49/// `hash_elements_in_domain`.
50pub const fn domain_selector(domain_id: u32, version: u8) -> Felt {
51    assert!(
52        domain_id >= 1 && domain_id < (1 << 24),
53        "domain_id must be a registered 24-bit id"
54    );
55    assert!(version >= 1, "per-domain versions start at 1");
56    Felt::new_unchecked(((domain_id as u64) << 8) | version as u64)
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn registry_entries_are_valid_and_distinct_selectors() {
65        use crate::program::{CLAIM_DOMAIN_TAG, KERNEL_DOMAIN_TAG, REQUEST_DOMAIN_TAG};
66
67        let entries = [
68            (KERNEL_COMMITMENT_DOMAIN_ID, KERNEL_DOMAIN_TAG),
69            (EXECUTION_CLAIM_DOMAIN_ID, CLAIM_DOMAIN_TAG),
70            (PROOF_REQUEST_DOMAIN_ID, REQUEST_DOMAIN_TAG),
71        ];
72        for (i, (id, tag)) in entries.iter().enumerate() {
73            assert!(*id >= 1 && *id < (1 << 24), "domain id out of the registered range");
74            assert_eq!(
75                tag.as_canonical_u64(),
76                (u64::from(*id) << 8) | 1,
77                "tag is not the packed selector"
78            );
79            for (other_id, other_tag) in entries.iter().skip(i + 1) {
80                assert_ne!(id, other_id, "registered domain ids must be unique");
81                assert_ne!(tag, other_tag, "registered tags must be unique");
82            }
83        }
84    }
85}