ms_codec/consts.rs
1//! v0.1 wire-format constants.
2//!
3//! **Naming convention:** ASCII byte literals (`b'0'`, `b's'`) are used for
4//! values whose semantic meaning is the *character* on the wire (threshold
5//! digit, share-index letter); hex literals (`0x00`) are used for values
6//! whose semantic meaning is the *byte* on the wire (the reserved-prefix
7//! byte). Both produce `u8`; the form chosen reflects which mental model
8//! is more natural at the use site.
9
10/// HRP for ms1 strings (BIP-93 codex32 HRP).
11pub const HRP: &str = "ms";
12
13/// BIP-93 separator character.
14pub const SEPARATOR: char = '1';
15
16/// v0.1 reserved-prefix byte (becomes the v0.2 type discriminator).
17pub const RESERVED_PREFIX: u8 = 0x00;
18
19/// v0.1 emit-side threshold value (ASCII).
20pub const THRESHOLD_V01: u8 = b'0';
21
22/// v0.1 emit-side share-index value (ASCII; "s" denotes the unshared secret per BIP-93).
23pub const SHARE_INDEX_V01: u8 = b's';
24
25/// Short codex32 checksum length in characters.
26pub const CHECKSUM_LEN_SHORT: usize = 13;
27
28/// Allowed v0.1 entr entropy byte lengths (bijective with BIP-39 word counts {12,15,18,21,24}).
29pub const VALID_ENTR_LENGTHS: &[usize] = &[16, 20, 24, 28, 32];
30
31/// Allowed v0.1 total ms1 string lengths (HRP+sep+threshold+id+share+payload+cksum).
32/// Computed: 9 fixed + ceil((entropy_bytes + 1) * 8 / 5) payload symbols + 13 cksum.
33pub const VALID_STR_LENGTHS: &[usize] = &[50, 56, 62, 69, 75];
34
35/// 4-byte type tag — v0.1 emit (also accept).
36pub const TAG_ENTR: [u8; 4] = *b"entr";
37
38/// v0.2 mnem-prefix byte (type discriminator for Mnem payloads).
39pub const MNEM_PREFIX: u8 = 0x02;
40
41/// Allowed v0.2 mnem total ms1 string lengths (byte-aligned: prefix + lang + entropy).
42/// Computed: 9 fixed + ceil((entropy_bytes + 2) * 8 / 5) payload symbols + 13 cksum.
43pub const VALID_MNEM_STR_LENGTHS: &[usize] = &[51, 58, 64, 70, 77];
44
45/// BIP-39 wordlist language names indexed by language byte (0 = English).
46/// This order MUST match ms-cli's `CliLanguage` declaration order (Phase 2 depends on it).
47pub const MNEM_LANGUAGE_NAMES: [&str; 10] = [
48 "english",
49 "japanese",
50 "korean",
51 "spanish",
52 "chinese-simplified",
53 "chinese-traditional",
54 "french",
55 "italian",
56 "czech",
57 "portuguese",
58];
59
60/// 4-byte type tags reserved-not-emitted in v0.1 (decoder rejects).
61/// `mnem` is no longer reserved-not-emitted: it is emitted in v0.2+ as Payload::Mnem.
62pub const RESERVED_NOT_EMITTED_V01: &[[u8; 4]] = &[*b"seed", *b"xprv", *b"prvk"];
63
64/// Anti-collision blocklist for the random 4-char `id` of a v0.2 K-of-N
65/// share-set (SPEC_ms_v0_2_kofn §2 consts / design-review I4). A share-set's
66/// `id` is random-per-set; re-roll while it lands in this set so a share-set
67/// `id` never collides with a v0.1 type-tag-shaped value.
68///
69/// **DISTINCT from `RESERVED_NOT_EMITTED_V01`** (the decoder-reject set, which
70/// dropped `mnem` in Cycle 1): `mnem` MUST stay in this id-blocklist.
71pub const RESERVED_ID_BLOCKLIST: &[[u8; 4]] = &[*b"entr", *b"seed", *b"xprv", *b"mnem", *b"prvk"];
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 /// Locks the bijection between VALID_ENTR_LENGTHS and VALID_STR_LENGTHS so
78 /// that a future edit to one without the other fails CI loudly.
79 /// Formula per SPEC §2.4: total = 9 fixed (HRP+sep+threshold+id+share) +
80 /// ceil((entropy_bytes + 1) * 8 / 5) payload symbols + 13 short checksum.
81 #[test]
82 fn valid_str_lengths_match_entr_lengths_via_bijection() {
83 assert_eq!(VALID_ENTR_LENGTHS.len(), VALID_STR_LENGTHS.len());
84 for (i, &entropy_bytes) in VALID_ENTR_LENGTHS.iter().enumerate() {
85 let data_bits = (entropy_bytes + 1) * 8; // +1 for the 0x00 prefix byte
86 let payload_symbols = data_bits.div_ceil(5);
87 let total = 9 + payload_symbols + CHECKSUM_LEN_SHORT;
88 assert_eq!(
89 total, VALID_STR_LENGTHS[i],
90 "entropy {} B -> expected str.len {}, got {} (bijection drift)",
91 entropy_bytes, VALID_STR_LENGTHS[i], total
92 );
93 }
94 }
95
96 /// Locks the bijection between VALID_ENTR_LENGTHS and VALID_MNEM_STR_LENGTHS.
97 /// Mnem payload = [0x02 prefix] + [lang byte] + entropy = entropy_bytes + 2 bytes.
98 /// Formula: total = 9 fixed + ceil((entropy_bytes + 2) * 8 / 5) payload symbols
99 /// + 13 short checksum.
100 #[test]
101 fn valid_mnem_str_lengths_match_entr_lengths_via_bijection() {
102 assert_eq!(VALID_ENTR_LENGTHS.len(), VALID_MNEM_STR_LENGTHS.len());
103 for (i, &entropy_bytes) in VALID_ENTR_LENGTHS.iter().enumerate() {
104 let data_bits = (entropy_bytes + 2) * 8; // +2 for 0x02 prefix + lang byte
105 let payload_symbols = data_bits.div_ceil(5);
106 let total = 9 + payload_symbols + CHECKSUM_LEN_SHORT;
107 assert_eq!(
108 total, VALID_MNEM_STR_LENGTHS[i],
109 "entropy {} B -> expected mnem str.len {}, got {} (bijection drift)",
110 entropy_bytes, VALID_MNEM_STR_LENGTHS[i], total
111 );
112 }
113 }
114}