mk_codec/bytecode/
path.rs1use bitcoin::bip32::{ChildNumber, DerivationPath};
23
24use crate::consts::MAX_PATH_COMPONENTS;
25use crate::error::{Error, Result};
26
27pub const EXPLICIT_PATH_INDICATOR: u8 = 0xFE;
29
30pub const STANDARD_PATHS: &[(u8, &str)] = &[
39 (0x01, "m/44'/0'/0'"), (0x02, "m/49'/0'/0'"), (0x03, "m/84'/0'/0'"), (0x04, "m/86'/0'/0'"), (0x05, "m/48'/0'/0'/2'"), (0x06, "m/48'/0'/0'/1'"), (0x07, "m/87'/0'/0'"), (0x11, "m/44'/1'/0'"),
49 (0x12, "m/49'/1'/0'"),
50 (0x13, "m/84'/1'/0'"),
51 (0x14, "m/86'/1'/0'"),
52 (0x15, "m/48'/1'/0'/2'"),
53 (0x16, "m/48'/1'/0'/1'"), (0x17, "m/87'/1'/0'"),
55];
56
57pub fn lookup_indicator(indicator: u8) -> Option<DerivationPath> {
61 STANDARD_PATHS
62 .iter()
63 .find(|(b, _)| *b == indicator)
64 .and_then(|(_, p)| p.parse().ok())
65}
66
67pub fn lookup_path(path: &DerivationPath) -> Option<u8> {
73 STANDARD_PATHS
74 .iter()
75 .find(|(_, p)| {
76 p.parse::<DerivationPath>()
77 .map(|table_path| &table_path == path)
78 .unwrap_or(false)
79 })
80 .map(|(b, _)| *b)
81}
82
83pub fn encode_path(path: &DerivationPath) -> Vec<u8> {
86 if let Some(indicator) = lookup_path(path) {
87 return vec![indicator];
88 }
89 let mut out = Vec::with_capacity(2 + 5 * MAX_PATH_COMPONENTS as usize);
90 out.push(EXPLICIT_PATH_INDICATOR);
91 let components: Vec<ChildNumber> = path.into_iter().copied().collect();
92 out.push(components.len() as u8);
93 for cn in components {
94 let raw: u32 = u32::from(cn);
95 leb128_encode(raw, &mut out);
96 }
97 out
98}
99
100pub fn decode_path(cursor: &mut &[u8]) -> Result<DerivationPath> {
102 let indicator = read_u8(cursor)?;
103 if indicator == EXPLICIT_PATH_INDICATOR {
104 return decode_explicit_path(cursor);
105 }
106 if let Some(path) = lookup_indicator(indicator) {
107 return Ok(path);
108 }
109 Err(Error::InvalidPathIndicator(indicator))
110}
111
112fn decode_explicit_path(cursor: &mut &[u8]) -> Result<DerivationPath> {
113 let count = read_u8(cursor)?;
114 if count == 0 || count > MAX_PATH_COMPONENTS {
115 return Err(Error::PathTooDeep(count));
116 }
117 let mut components: Vec<ChildNumber> = Vec::with_capacity(count as usize);
118 for _ in 0..count {
119 let raw = leb128_decode_u32(cursor)?;
120 let cn = if raw & 0x8000_0000 != 0 {
121 ChildNumber::from_hardened_idx(raw & 0x7FFF_FFFF)
122 .map_err(|e| Error::InvalidPathComponent(format!("{e}")))?
123 } else {
124 ChildNumber::from_normal_idx(raw)
125 .map_err(|e| Error::InvalidPathComponent(format!("{e}")))?
126 };
127 components.push(cn);
128 }
129 Ok(DerivationPath::from(components))
130}
131
132fn leb128_encode(mut value: u32, out: &mut Vec<u8>) {
133 loop {
134 let mut byte = (value & 0x7F) as u8;
135 value >>= 7;
136 if value != 0 {
137 byte |= 0x80;
138 out.push(byte);
139 } else {
140 out.push(byte);
141 break;
142 }
143 }
144}
145
146fn leb128_decode_u32(cursor: &mut &[u8]) -> Result<u32> {
147 let mut result: u64 = 0;
148 let mut shift: u32 = 0;
149 loop {
150 let byte = read_u8(cursor)?;
151 result |= ((byte & 0x7F) as u64) << shift;
152 if byte & 0x80 == 0 {
153 break;
154 }
155 shift += 7;
156 if shift >= 35 {
158 return Err(Error::InvalidPathComponent(format!(
159 "LEB128 overflow at shift {shift}"
160 )));
161 }
162 }
163 if result > u32::MAX as u64 {
164 return Err(Error::InvalidPathComponent(format!(
165 "LEB128 value {result} > u32::MAX"
166 )));
167 }
168 Ok(result as u32)
169}
170
171fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
172 if cursor.is_empty() {
173 return Err(Error::UnexpectedEnd);
174 }
175 let b = cursor[0];
176 *cursor = &cursor[1..];
177 Ok(b)
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use std::str::FromStr;
184
185 #[test]
186 fn round_trip_all_standard_paths() {
187 for (indicator, path_str) in STANDARD_PATHS {
188 let path = DerivationPath::from_str(path_str).unwrap();
189 let encoded = encode_path(&path);
190 assert_eq!(encoded, vec![*indicator], "round-trip {path_str}");
191 let mut cursor: &[u8] = &encoded;
192 let decoded = decode_path(&mut cursor).unwrap();
193 assert_eq!(decoded, path, "round-trip parsed {path_str}");
194 assert!(cursor.is_empty());
195 }
196 }
197
198 #[test]
199 fn round_trip_explicit_path_simple() {
200 let path = DerivationPath::from_str("m/0/1/2").unwrap();
201 let encoded = encode_path(&path);
202 assert_eq!(encoded[0], 0xFE);
204 assert_eq!(encoded[1], 3);
205 let mut cursor: &[u8] = &encoded;
206 let decoded = decode_path(&mut cursor).unwrap();
207 assert_eq!(decoded, path);
208 }
209
210 #[test]
211 fn round_trip_explicit_path_all_hardened() {
212 let path = DerivationPath::from_str("m/9999'/1234'/56'/7'").unwrap();
214 let encoded = encode_path(&path);
215 assert_eq!(encoded[0], 0xFE);
216 assert_eq!(encoded[1], 4);
217 assert_eq!(encoded.len(), 1 + 1 + 4 * 5);
219 let mut cursor: &[u8] = &encoded;
220 let decoded = decode_path(&mut cursor).unwrap();
221 assert_eq!(decoded, path);
222 }
223
224 #[test]
225 fn round_trip_explicit_path_at_cap() {
226 let path = DerivationPath::from_str("m/0'/1'/2'/3'/4'/5'/6'/7'/8'/9'").unwrap();
228 let encoded = encode_path(&path);
229 let mut cursor: &[u8] = &encoded;
230 let decoded = decode_path(&mut cursor).unwrap();
231 assert_eq!(decoded, path);
232 }
233
234 #[test]
235 fn rejects_path_too_deep() {
236 let mut bytes = vec![0xFE, 11u8];
238 for i in 0..11 {
239 bytes.push(i); }
241 let mut cursor: &[u8] = &bytes;
242 assert!(matches!(
243 decode_path(&mut cursor),
244 Err(Error::PathTooDeep(11)),
245 ));
246 }
247
248 #[test]
249 fn rejects_path_count_zero() {
250 let bytes = vec![0xFE, 0u8];
253 let mut cursor: &[u8] = &bytes;
254 assert!(matches!(
255 decode_path(&mut cursor),
256 Err(Error::PathTooDeep(0)),
257 ));
258 }
259
260 #[test]
261 fn rejects_reserved_indicator_zero() {
262 let bytes = vec![0x00];
263 let mut cursor: &[u8] = &bytes;
264 assert!(matches!(
265 decode_path(&mut cursor),
266 Err(Error::InvalidPathIndicator(0x00)),
267 ));
268 }
269
270 #[test]
271 fn round_trip_indicator_0x16_added_in_v0_2() {
272 let path = DerivationPath::from_str("m/48'/1'/0'/1'").unwrap();
279 let encoded = encode_path(&path);
280 assert_eq!(encoded, vec![0x16]);
281 let mut cursor: &[u8] = &encoded;
282 let decoded = decode_path(&mut cursor).unwrap();
283 assert_eq!(decoded, path);
284 assert!(cursor.is_empty());
285 }
286
287 #[test]
288 fn rejects_reserved_indicator_high_range() {
289 let bytes = vec![0xFD];
291 let mut cursor: &[u8] = &bytes;
292 assert!(matches!(
293 decode_path(&mut cursor),
294 Err(Error::InvalidPathIndicator(0xFD)),
295 ));
296 let bytes = vec![0xFF];
298 let mut cursor: &[u8] = &bytes;
299 assert!(matches!(
300 decode_path(&mut cursor),
301 Err(Error::InvalidPathIndicator(0xFF)),
302 ));
303 }
304
305 #[test]
306 fn rejects_truncated_explicit_path() {
307 let bytes = vec![0xFE, 2u8, 0u8];
309 let mut cursor: &[u8] = &bytes;
310 assert!(matches!(
311 decode_path(&mut cursor),
312 Err(Error::UnexpectedEnd),
313 ));
314 }
315
316 #[test]
317 fn leb128_encode_examples() {
318 let mut out = Vec::new();
320 leb128_encode(0, &mut out);
321 assert_eq!(out, vec![0]);
322 let mut out = Vec::new();
324 leb128_encode(127, &mut out);
325 assert_eq!(out, vec![0x7F]);
326 let mut out = Vec::new();
328 leb128_encode(128, &mut out);
329 assert_eq!(out, vec![0x80, 0x01]);
330 let mut out = Vec::new();
332 leb128_encode(0x8000_0000, &mut out);
333 assert_eq!(out, vec![0x80, 0x80, 0x80, 0x80, 0x08]);
334 }
335}