Skip to main content

mk_codec/
consts.rs

1//! Locked constants for `mk1` per `design/SPEC_mk_v0_1.md` v0.1.
2//!
3//! All values are closure-locked (see
4//! `docs/superpowers/specs/2026-04-29-mk1-open-questions-closure-design.md`).
5//! Reproducer for the NUMS-derived target constants is documented in
6//! the BIP draft's "Why new target constants?" section.
7
8/// HRP for `mk1` strings (BIP 173 separator `1` follows: prefix is `mk1`).
9pub const HRP: &str = "mk";
10
11/// Domain string for NUMS-derived target constants (closure Q-1).
12///
13/// The string itself is the audit trail: any reader can recompute the
14/// SHA-256 and verify the constants follow from it.
15pub const NUMS_DOMAIN: &[u8] = b"shibbolethnumskey";
16
17/// Top 65 bits of `SHA-256(NUMS_DOMAIN)`. Regular-code target residue.
18pub const MK_REGULAR_CONST: u128 = 0x1062435f91072fa5c;
19
20/// Top 75 bits of `SHA-256(NUMS_DOMAIN)`. Long-code target residue.
21pub const MK_LONG_CONST: u128 = 0x41890d7e441cbe97273;
22
23/// Maximum components in an explicit-path encoding (closure Q-3).
24///
25/// Real BIP-style derivations top out at 6 (BIP 48 multisig is 4); 10
26/// gives margin without locking out plausibly real paths.
27pub const MAX_PATH_COMPONENTS: u8 = 10;
28
29/// Single-string regular-code payload bytes.
30pub const SINGLE_STRING_REGULAR_BYTES: usize = 48;
31
32/// Single-string long-code payload bytes.
33pub const SINGLE_STRING_LONG_BYTES: usize = 56;
34
35/// Chunked-fragment regular-code payload bytes per chunk.
36pub const CHUNKED_FRAGMENT_REGULAR_BYTES: usize = 45;
37
38/// Chunked-fragment long-code payload bytes per chunk.
39pub const CHUNKED_FRAGMENT_LONG_BYTES: usize = 53;
40
41/// Maximum chunks per card.
42pub const MAX_CHUNKS: u8 = 32;
43
44/// Cross-chunk integrity hash size in bytes.
45pub const CROSS_CHUNK_HASH_BYTES: usize = 4;
46
47/// Family-stable generator string (closure Q-10) for vector-corpus
48/// SHA-256 anchoring. Patch-version bumps don't roll the token; minor-
49/// or major-version bumps do. (Honesty note: the v0.3 and v0.4 minor
50/// bumps missed their token rolls; this token was corrected to
51/// `"mk-codec 0.4"` in the same regeneration that added the depth-0
52/// vector — that vector decodes only under mk-codec v0.4.0+, so the
53/// corpus must not carry an older family token.)
54pub const GENERATOR_FAMILY: &str = "mk-codec 0.4";
55
56/// Compact-73 xpub byte size (closure Q-7).
57pub const XPUB_COMPACT_BYTES: usize = 73;
58
59/// Policy ID stub size in bytes (closure Q-2).
60pub const POLICY_ID_STUB_BYTES: usize = 4;
61
62/// Origin fingerprint size in bytes.
63pub const ORIGIN_FINGERPRINT_BYTES: usize = 4;
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use bitcoin::hashes::{Hash, sha256};
69
70    /// Verifies that the locked hex constants reproduce from the
71    /// documented derivation rule. Catches accidental drift if either
72    /// the domain string or the constants are edited without updating
73    /// the other.
74    #[test]
75    fn nums_constants_reproduce_from_domain() {
76        let digest = sha256::Hash::hash(NUMS_DOMAIN);
77        let bytes = digest.as_byte_array();
78        // Stage the leading 128 bits of the 256-bit digest as a
79        // big-endian u128.
80        let hi: u128 = u128::from_be_bytes(bytes[0..16].try_into().unwrap());
81
82        // Top 65 bits: shift the leading 128 bits right by (128 - 65).
83        let derived_regular = hi >> 63;
84        assert_eq!(
85            derived_regular, MK_REGULAR_CONST,
86            "MK_REGULAR_CONST drift from SHA-256(NUMS_DOMAIN) top-65-bits",
87        );
88
89        // Top 75 bits: shift right by (128 - 75).
90        let derived_long = hi >> 53;
91        assert_eq!(
92            derived_long, MK_LONG_CONST,
93            "MK_LONG_CONST drift from SHA-256(NUMS_DOMAIN) top-75-bits",
94        );
95    }
96
97    #[test]
98    fn nums_string_differs_from_md1() {
99        assert_ne!(
100            NUMS_DOMAIN, b"shibbolethnums",
101            "mk1 NUMS string MUST differ from md1's per closure D-10",
102        );
103    }
104
105    #[test]
106    fn capacity_constants_match_spec() {
107        // Sanity: confirms the four capacity numbers carry the values
108        // pinned in SPEC §2.4 / BIP §"Length envelope".
109        assert_eq!(SINGLE_STRING_REGULAR_BYTES, 48);
110        assert_eq!(SINGLE_STRING_LONG_BYTES, 56);
111        assert_eq!(CHUNKED_FRAGMENT_REGULAR_BYTES, 45);
112        assert_eq!(CHUNKED_FRAGMENT_LONG_BYTES, 53);
113        assert_eq!(MAX_CHUNKS, 32);
114    }
115
116    #[test]
117    fn xpub_compact_size_is_73() {
118        // 4 (version) + 4 (parent_fingerprint) + 32 (chain_code) + 33 (public_key) = 73.
119        assert_eq!(XPUB_COMPACT_BYTES, 4 + 4 + 32 + 33);
120    }
121
122    #[test]
123    fn path_cap_is_ten() {
124        // closure Q-3 lock; not 32.
125        assert_eq!(MAX_PATH_COMPONENTS, 10);
126    }
127}