Skip to main content

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}