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.
50pub const GENERATOR_FAMILY: &str = "mk-codec 0.2";
51
52/// Compact-73 xpub byte size (closure Q-7).
53pub const XPUB_COMPACT_BYTES: usize = 73;
54
55/// Policy ID stub size in bytes (closure Q-2).
56pub const POLICY_ID_STUB_BYTES: usize = 4;
57
58/// Origin fingerprint size in bytes.
59pub const ORIGIN_FINGERPRINT_BYTES: usize = 4;
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use bitcoin::hashes::{Hash, sha256};
65
66 /// Verifies that the locked hex constants reproduce from the
67 /// documented derivation rule. Catches accidental drift if either
68 /// the domain string or the constants are edited without updating
69 /// the other.
70 #[test]
71 fn nums_constants_reproduce_from_domain() {
72 let digest = sha256::Hash::hash(NUMS_DOMAIN);
73 let bytes = digest.as_byte_array();
74 // Stage the leading 128 bits of the 256-bit digest as a
75 // big-endian u128.
76 let hi: u128 = u128::from_be_bytes(bytes[0..16].try_into().unwrap());
77
78 // Top 65 bits: shift the leading 128 bits right by (128 - 65).
79 let derived_regular = hi >> 63;
80 assert_eq!(
81 derived_regular, MK_REGULAR_CONST,
82 "MK_REGULAR_CONST drift from SHA-256(NUMS_DOMAIN) top-65-bits",
83 );
84
85 // Top 75 bits: shift right by (128 - 75).
86 let derived_long = hi >> 53;
87 assert_eq!(
88 derived_long, MK_LONG_CONST,
89 "MK_LONG_CONST drift from SHA-256(NUMS_DOMAIN) top-75-bits",
90 );
91 }
92
93 #[test]
94 fn nums_string_differs_from_md1() {
95 assert_ne!(
96 NUMS_DOMAIN, b"shibbolethnums",
97 "mk1 NUMS string MUST differ from md1's per closure D-10",
98 );
99 }
100
101 #[test]
102 fn capacity_constants_match_spec() {
103 // Sanity: confirms the four capacity numbers carry the values
104 // pinned in SPEC §2.4 / BIP §"Length envelope".
105 assert_eq!(SINGLE_STRING_REGULAR_BYTES, 48);
106 assert_eq!(SINGLE_STRING_LONG_BYTES, 56);
107 assert_eq!(CHUNKED_FRAGMENT_REGULAR_BYTES, 45);
108 assert_eq!(CHUNKED_FRAGMENT_LONG_BYTES, 53);
109 assert_eq!(MAX_CHUNKS, 32);
110 }
111
112 #[test]
113 fn xpub_compact_size_is_73() {
114 // 4 (version) + 4 (parent_fingerprint) + 32 (chain_code) + 33 (public_key) = 73.
115 assert_eq!(XPUB_COMPACT_BYTES, 4 + 4 + 32 + 33);
116 }
117
118 #[test]
119 fn path_cap_is_ten() {
120 // closure Q-3 lock; not 32.
121 assert_eq!(MAX_PATH_COMPONENTS, 10);
122 }
123}