ms_codec/error.rs
1//! ms-codec error taxonomy. Variants mirror SPEC §4 decoder validity rules
2//! plus the encoder-side validation surface from SPEC §3.5 / §3.5.1.
3
4use std::fmt;
5
6/// ms-codec error type.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum Error {
10 /// Upstream codex32 parse / checksum failure (delegated from rust-codex32).
11 Codex32(codex32::Error),
12 /// Mnem wordlist-language byte was not in the valid range 0..=9 (SPEC v0.2 §3).
13 MnemUnknownLanguage(u8),
14 /// HRP was not "ms" (SPEC §4 rule 2).
15 WrongHrp {
16 /// The HRP that was observed.
17 got: String,
18 },
19 /// Threshold was not 0 (SPEC §4 rule 3).
20 ThresholdNotZero {
21 /// The threshold-position byte (ASCII digit) that was observed.
22 got: u8,
23 },
24 /// Share-index was not 's' — BIP-93 requires 's' for threshold=0 (SPEC §4 rule 4).
25 ShareIndexNotSecret {
26 /// The share-index character that was observed.
27 got: char,
28 },
29 /// Tag bytes were not in the codex32 alphabet (SPEC §4 rule 5).
30 TagInvalidAlphabet {
31 /// The 4-byte id-field bytes that failed alphabet validation.
32 got: [u8; 4],
33 },
34 /// Tag was structurally valid but not in RESERVED_TAG_TABLE (SPEC §4 rule 6).
35 UnknownTag {
36 /// The 4-byte tag that was not recognized.
37 got: [u8; 4],
38 },
39 /// Tag was in RESERVED_TAG_TABLE but reserved-not-emitted in v0.1 (SPEC §4 rule 7,
40 /// SPEC §3.5.1 encoder symmetry).
41 ReservedTagNotEmittedInV01 {
42 /// The 4-byte reserved tag (one of seed/xprv/mnem/prvk in v0.1).
43 got: [u8; 4],
44 },
45 /// Reserved-prefix byte was not 0x00 (SPEC §4 rule 8).
46 ReservedPrefixViolation {
47 /// The non-zero prefix byte that was observed.
48 got: u8,
49 },
50 /// Total string length was outside the v0.1 emittable set (SPEC §4 rule 9).
51 UnexpectedStringLength {
52 /// The total string length that was observed.
53 got: usize,
54 /// The set of v0.1-emittable lengths.
55 allowed: &'static [usize],
56 },
57 /// Payload byte length did not match the tag's spec (SPEC §3.5, §4 rule 10).
58 PayloadLengthMismatch {
59 /// The 4-byte tag whose length set was checked against.
60 tag: [u8; 4],
61 /// The set of valid byte lengths for this tag.
62 expected: &'static [usize],
63 /// The observed payload byte length (after stripping the prefix byte).
64 got: usize,
65 },
66 /// BCH error-correction (`bch_decode`) reported the input is uncorrectable
67 /// — the number of symbol errors exceeds the regular code's `t = 4`
68 /// correction capacity (singleton bound `d = 8`). Surfaced by
69 /// [`crate::decode_with_correction`] when `bch_decode::decode_regular_errors`
70 /// returns `None`, or when a post-correction re-verification step fails
71 /// (catches pathological 5+-error patterns that fool the decoder into
72 /// producing a "consistent" but invalid locator). Added v0.2.0 per plan
73 /// §1 D29 + §2.B.2.
74 ///
75 /// `bound = 8` is the BCH(93,80,8) singleton bound. ms1 is single-chunk
76 /// only — no `chunk_index` field (cf. md-codec's `TooManyErrors` which
77 /// carries chunk-set context).
78 TooManyErrors {
79 /// Singleton bound for the BCH regular code (always 8).
80 bound: u8,
81 },
82}
83
84impl fmt::Display for Error {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 Error::Codex32(e) => write!(f, "codex32 parse error: {:?}", e),
88 Error::MnemUnknownLanguage(code) => {
89 write!(f, "unknown mnem wordlist-language code: {0}", code)
90 }
91 Error::WrongHrp { got } => write!(f, "wrong HRP: got {:?}, expected \"ms\"", got),
92 Error::ThresholdNotZero { got } => {
93 write!(
94 f,
95 "threshold not 0 (got '{}'); v0.1 is single-string only",
96 *got as char
97 )
98 }
99 Error::ShareIndexNotSecret { got } => {
100 write!(
101 f,
102 "share-index not 's' (got '{}'); BIP-93 requires 's' for threshold=0",
103 got
104 )
105 }
106 Error::TagInvalidAlphabet { got } => {
107 write!(f, "tag bytes not in codex32 alphabet: {:?}", got)
108 }
109 Error::UnknownTag { got } => write!(
110 f,
111 "unknown tag {:?}; not a member of RESERVED_TAG_TABLE",
112 std::str::from_utf8(got).unwrap_or("<non-utf8>")
113 ),
114 Error::ReservedTagNotEmittedInV01 { got } => write!(
115 f,
116 "tag {:?} reserved-not-emitted in v0.1; deferred to v0.2+",
117 std::str::from_utf8(got).unwrap_or("<non-utf8>")
118 ),
119 Error::ReservedPrefixViolation { got } => {
120 write!(f, "reserved-prefix byte was 0x{:02x}, expected 0x00", got)
121 }
122 Error::UnexpectedStringLength { got, allowed } => {
123 write!(f, "string length {} outside v0.1 set {:?}", got, allowed)
124 }
125 Error::PayloadLengthMismatch { tag, expected, got } => write!(
126 f,
127 "tag {:?} payload length {} not in expected set {:?}",
128 std::str::from_utf8(tag).unwrap_or("<non-utf8>"),
129 got,
130 expected
131 ),
132 Error::TooManyErrors { bound } => {
133 write!(f, "more than {} errors; uncorrectable", bound)
134 }
135 }
136 }
137}
138
139impl std::error::Error for Error {
140 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
141 // codex32::Error doesn't impl std::error::Error in v0.1.0; chain stops here.
142 None
143 }
144}
145
146impl From<codex32::Error> for Error {
147 fn from(e: codex32::Error) -> Self {
148 Error::Codex32(e)
149 }
150}
151
152/// Result alias for ms-codec.
153pub type Result<T> = std::result::Result<T, Error>;