Skip to main content

ms_codec/
bch.rs

1//! BIP 93 codex32 BCH primitives for HRP `"ms"` (regular code only).
2//!
3//! Vendored from md-codec's structure at the v0.34.0 promotion (descriptor-mnemonic
4//! commit `94069ea`) per plan §2.B.2 / D22. ms1 strings are all regular-code length
5//! per `consts::VALID_STR_LENGTHS`, so the long-code primitives are intentionally
6//! absent (mk-codec carries the long-code variants).
7//!
8//! All public per plan D22 (no `pub(crate)` half-private items in ms-codec): the
9//! downstream `bch_decode` module (B.4) re-declares the 3 internal consts locally
10//! per the Q3 lock — they stay bare-private here.
11//!
12//! The `MS_REGULAR_CONST` value is byte-exact with the toolkit's vendored copy at
13//! `mnemonic-toolkit/crates/mnemonic-toolkit/src/repair.rs:42` per Phase B.0 (e)
14//! cross-check. The toolkit's v0.23.0 migration (Phase B.7) deletes its local
15//! copy and delegates to this crate.
16
17/// BCH(93,80,8) generator polynomial coefficients (5 × 65-bit).
18///
19/// Identical across mk/ms/md (the polynomial is BIP-93's; only the per-HRP
20/// target residue differs).
21pub const GEN_REGULAR: [u128; 5] = [
22    0x19dc500ce73fde210,
23    0x1bfae00def77fe529,
24    0x1fbd920fffe7bee52,
25    0x1739640bdeee3fdad,
26    0x07729a039cfc75f5a,
27];
28
29/// MS-domain target residue: codex32's "SECRETSHARE32" Fe-vec packed in
30/// big-endian 5-bit chunks (the natural u128 representation that
31/// [`polymod_run`] produces for a valid ms1 input).
32///
33/// Empirical-stable across distinct valid ms1 strings — see the toolkit's
34/// `ms_nums_target_is_stable_across_distinct_valid_strings` cell at
35/// `mnemonic-toolkit/crates/mnemonic-toolkit/src/repair.rs` for the
36/// stability derivation. Byte-exact with the toolkit per Phase B.0 (e).
37pub const MS_REGULAR_CONST: u128 = 0x962958058f2c192a;
38
39const POLYMOD_INIT: u128 = 0x23181b3;
40const REGULAR_SHIFT: u32 = 60;
41const REGULAR_MASK: u128 = 0x0fffffffffffffff;
42
43fn polymod_step(residue: u128, value: u128) -> u128 {
44    let b = residue >> REGULAR_SHIFT;
45    let mut new_residue = ((residue & REGULAR_MASK) << 5) ^ value;
46    for (i, &g) in GEN_REGULAR.iter().enumerate() {
47        if (b >> i) & 1 != 0 {
48            new_residue ^= g;
49        }
50    }
51    new_residue
52}
53
54/// Run the BCH polymod over `values` starting from the BIP-93 initial residue.
55///
56/// Returns the final residue; callers XOR against the per-HRP target
57/// constant ([`MS_REGULAR_CONST`]) to produce a checksum or to verify
58/// one. Inputs are 5-bit symbols (`u8` in `0..32`); larger values are
59/// reduced modulo 32 by the underlying step.
60pub fn polymod_run(values: &[u8]) -> u128 {
61    let mut residue = POLYMOD_INIT;
62    for &v in values {
63        residue = polymod_step(residue, v as u128);
64    }
65    residue
66}
67
68/// BIP 173-style HRP expansion: `[c >> 5 for c in hrp] ++ [0] ++ [c & 31 for c in hrp]`.
69pub fn hrp_expand(hrp: &str) -> Vec<u8> {
70    let bytes = hrp.as_bytes();
71    let mut out = Vec::with_capacity(bytes.len() * 2 + 1);
72    for &c in bytes {
73        out.push(c >> 5);
74    }
75    out.push(0);
76    for &c in bytes {
77        out.push(c & 31);
78    }
79    out
80}
81
82/// 13-symbol regular-code BCH checksum over `hrp_expand(hrp) || data || [0; 13]`.
83pub fn bch_create_checksum_regular(hrp: &str, data: &[u8]) -> [u8; 13] {
84    let mut input = hrp_expand(hrp);
85    input.extend_from_slice(data);
86    input.extend(std::iter::repeat_n(0, 13));
87    let polymod = polymod_run(&input) ^ MS_REGULAR_CONST;
88    let mut out = [0u8; 13];
89    for (i, slot) in out.iter_mut().enumerate() {
90        *slot = ((polymod >> (5 * (12 - i))) & 0x1F) as u8;
91    }
92    out
93}
94
95/// Verify a regular-code BCH checksum over the data-part-with-checksum.
96pub fn bch_verify_regular(hrp: &str, data_with_checksum: &[u8]) -> bool {
97    if data_with_checksum.len() < 13 {
98        return false;
99    }
100    let mut input = hrp_expand(hrp);
101    input.extend_from_slice(data_with_checksum);
102    polymod_run(&input) == MS_REGULAR_CONST
103}