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//! `MS_REGULAR_CONST` is the BIP-93 codex32 short-code target residue
13//! ("SECRETSHARE32"); ms-codec is its single source of truth. (The toolkit's
14//! former vendored copy was deleted in its v0.23.0 migration, which now
15//! delegates to this crate. That copy held the pre-v0.2.1 WRONG value paired
16//! with a wrong `POLYMOD_INIT` — see
17//! `design/BUG_decode_with_correction_length_divergence.md`.)
18
19/// BCH(93,80,8) generator polynomial coefficients (5 × 65-bit).
20///
21/// Identical across mk/ms/md (the polynomial is BIP-93's; only the per-HRP
22/// target residue differs).
23pub const GEN_REGULAR: [u128; 5] = [
24    0x19dc500ce73fde210,
25    0x1bfae00def77fe529,
26    0x1fbd920fffe7bee52,
27    0x1739640bdeee3fdad,
28    0x07729a039cfc75f5a,
29];
30
31/// MS-domain target residue: codex32's "SECRETSHARE32" Fe-vec packed
32/// big-endian in 5-bit chunks — the value [`polymod_run`] (started from the
33/// codex32 initial residue [`POLYMOD_INIT`]) produces for ANY valid ms1
34/// input, independent of entropy length.
35///
36/// `SECRETSHARE32 = [s,e,c,r,e,t,s,h,a,r,e,3,2] = [16,25,24,3,25,11,16,23,29,3,25,17,10]`
37/// packed as `Σ vᵢ << (5·(12−i))` → `0x10ce0795c2fd1e62a` (bit 64 set). This is
38/// the BIP-93 codex32 short-code target the `rust-codex32` engine (which
39/// `envelope.rs` uses to encode) checks against, so the hand-rolled path here
40/// is byte-equivalent to codex32 for all ms1 lengths.
41///
42/// NOTE (v0.2.1 fix): the previous value `0x962958058f2c192a`, paired with a
43/// wrong `POLYMOD_INIT`, was empirically lifted from a single 12-word vector
44/// and only validated 16-byte seeds — see
45/// `design/BUG_decode_with_correction_length_divergence.md`.
46pub const MS_REGULAR_CONST: u128 = 0x10ce0795c2fd1e62a;
47
48/// codex32 initial polymod residue: the field element `1` (BIP-173/BIP-93
49/// bech32-style start state), processed against `hrp_expand("ms") || data`.
50/// (Was wrongly `0x23181b3`, which made `polymod_run` length-variant for valid
51/// codewords — the root cause of the 20/24/28/32-byte correction bug.)
52const POLYMOD_INIT: u128 = 0x1;
53const REGULAR_SHIFT: u32 = 60;
54const REGULAR_MASK: u128 = 0x0fffffffffffffff;
55
56fn polymod_step(residue: u128, value: u128) -> u128 {
57    let b = residue >> REGULAR_SHIFT;
58    let mut new_residue = ((residue & REGULAR_MASK) << 5) ^ value;
59    for (i, &g) in GEN_REGULAR.iter().enumerate() {
60        if (b >> i) & 1 != 0 {
61            new_residue ^= g;
62        }
63    }
64    new_residue
65}
66
67/// Run the BCH polymod over `values` starting from the BIP-93 initial residue.
68///
69/// Returns the final residue; callers XOR against the per-HRP target
70/// constant ([`MS_REGULAR_CONST`]) to produce a checksum or to verify
71/// one. Inputs are 5-bit symbols (`u8` in `0..32`); larger values are
72/// reduced modulo 32 by the underlying step.
73pub fn polymod_run(values: &[u8]) -> u128 {
74    let mut residue = POLYMOD_INIT;
75    for &v in values {
76        residue = polymod_step(residue, v as u128);
77    }
78    residue
79}
80
81/// BIP 173-style HRP expansion: `[c >> 5 for c in hrp] ++ [0] ++ [c & 31 for c in hrp]`.
82pub fn hrp_expand(hrp: &str) -> Vec<u8> {
83    let bytes = hrp.as_bytes();
84    let mut out = Vec::with_capacity(bytes.len() * 2 + 1);
85    for &c in bytes {
86        out.push(c >> 5);
87    }
88    out.push(0);
89    for &c in bytes {
90        out.push(c & 31);
91    }
92    out
93}
94
95/// 13-symbol regular-code BCH checksum over `hrp_expand(hrp) || data || [0; 13]`.
96pub fn bch_create_checksum_regular(hrp: &str, data: &[u8]) -> [u8; 13] {
97    let mut input = hrp_expand(hrp);
98    input.extend_from_slice(data);
99    input.extend(std::iter::repeat_n(0, 13));
100    let polymod = polymod_run(&input) ^ MS_REGULAR_CONST;
101    let mut out = [0u8; 13];
102    for (i, slot) in out.iter_mut().enumerate() {
103        *slot = ((polymod >> (5 * (12 - i))) & 0x1F) as u8;
104    }
105    out
106}
107
108/// Verify a regular-code BCH checksum over the data-part-with-checksum.
109pub fn bch_verify_regular(hrp: &str, data_with_checksum: &[u8]) -> bool {
110    if data_with_checksum.len() < 13 {
111        return false;
112    }
113    let mut input = hrp_expand(hrp);
114    input.extend_from_slice(data_with_checksum);
115    polymod_run(&input) == MS_REGULAR_CONST
116}