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}