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 > MAX_PATH_COMPONENTS {
115 return Err(Error::PathTooDeep(count));
116 }
117 let mut components: Vec<ChildNumber> = Vec::with_capacity(count as usize);
120 for _ in 0..count {
121 let raw = leb128_decode_u32(cursor)?;
122 let cn = if raw & 0x8000_0000 != 0 {
123 ChildNumber::from_hardened_idx(raw & 0x7FFF_FFFF)
124 .map_err(|e| Error::InvalidPathComponent(format!("{e}")))?
125 } else {
126 ChildNumber::from_normal_idx(raw)
127 .map_err(|e| Error::InvalidPathComponent(format!("{e}")))?
128 };
129 components.push(cn);
130 }
131 Ok(DerivationPath::from(components))
132}
133
134fn leb128_encode(mut value: u32, out: &mut Vec<u8>) {
135 loop {
136 let mut byte = (value & 0x7F) as u8;
137 value >>= 7;
138 if value != 0 {
139 byte |= 0x80;
140 out.push(byte);
141 } else {
142 out.push(byte);
143 break;
144 }
145 }
146}
147
148fn leb128_decode_u32(cursor: &mut &[u8]) -> Result<u32> {
149 let mut result: u64 = 0;
150 let mut shift: u32 = 0;
151 loop {
152 let byte = read_u8(cursor)?;
153 result |= ((byte & 0x7F) as u64) << shift;
154 if byte & 0x80 == 0 {
155 break;
156 }
157 shift += 7;
158 if shift >= 35 {
160 return Err(Error::InvalidPathComponent(format!(
161 "LEB128 overflow at shift {shift}"
162 )));
163 }
164 }
165 if result > u32::MAX as u64 {
166 return Err(Error::InvalidPathComponent(format!(
167 "LEB128 value {result} > u32::MAX"
168 )));
169 }
170 Ok(result as u32)
171}
172
173fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
174 if cursor.is_empty() {
175 return Err(Error::UnexpectedEnd);
176 }
177 let b = cursor[0];
178 *cursor = &cursor[1..];
179 Ok(b)
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185 use std::str::FromStr;
186
187 #[test]
188 fn round_trip_all_standard_paths() {
189 for (indicator, path_str) in STANDARD_PATHS {
190 let path = DerivationPath::from_str(path_str).unwrap();
191 let encoded = encode_path(&path);
192 assert_eq!(encoded, vec![*indicator], "round-trip {path_str}");
193 let mut cursor: &[u8] = &encoded;
194 let decoded = decode_path(&mut cursor).unwrap();
195 assert_eq!(decoded, path, "round-trip parsed {path_str}");
196 assert!(cursor.is_empty());
197 }
198 }
199
200 #[test]
201 fn round_trip_explicit_path_simple() {
202 let path = DerivationPath::from_str("m/0/1/2").unwrap();
203 let encoded = encode_path(&path);
204 assert_eq!(encoded[0], 0xFE);
206 assert_eq!(encoded[1], 3);
207 let mut cursor: &[u8] = &encoded;
208 let decoded = decode_path(&mut cursor).unwrap();
209 assert_eq!(decoded, path);
210 }
211
212 #[test]
213 fn round_trip_explicit_path_all_hardened() {
214 let path = DerivationPath::from_str("m/9999'/1234'/56'/7'").unwrap();
216 let encoded = encode_path(&path);
217 assert_eq!(encoded[0], 0xFE);
218 assert_eq!(encoded[1], 4);
219 assert_eq!(encoded.len(), 1 + 1 + 4 * 5);
221 let mut cursor: &[u8] = &encoded;
222 let decoded = decode_path(&mut cursor).unwrap();
223 assert_eq!(decoded, path);
224 }
225
226 #[test]
227 fn round_trip_explicit_path_at_cap() {
228 let path = DerivationPath::from_str("m/0'/1'/2'/3'/4'/5'/6'/7'/8'/9'").unwrap();
230 let encoded = encode_path(&path);
231 let mut cursor: &[u8] = &encoded;
232 let decoded = decode_path(&mut cursor).unwrap();
233 assert_eq!(decoded, path);
234 }
235
236 #[test]
237 fn rejects_path_too_deep() {
238 let mut bytes = vec![0xFE, 11u8];
240 for i in 0..11 {
241 bytes.push(i); }
243 let mut cursor: &[u8] = &bytes;
244 assert!(matches!(
245 decode_path(&mut cursor),
246 Err(Error::PathTooDeep(11)),
247 ));
248 }
249
250 #[test]
251 fn accepts_path_count_zero_as_empty_path() {
252 let bytes = vec![0xFE, 0u8];
255 let mut cursor: &[u8] = &bytes;
256 let decoded = decode_path(&mut cursor).unwrap();
257 assert_eq!(decoded.into_iter().count(), 0, "empty path");
258 assert!(cursor.is_empty());
259 }
260
261 #[test]
262 fn round_trip_empty_path() {
263 let path = DerivationPath::from_str("m").unwrap(); let encoded = encode_path(&path);
265 assert_eq!(encoded, vec![0xFE, 0x00]);
266 let mut cursor: &[u8] = &encoded;
267 let decoded = decode_path(&mut cursor).unwrap();
268 assert_eq!(decoded, path);
269 assert!(cursor.is_empty());
270 }
271
272 #[test]
273 fn rejects_reserved_indicator_zero() {
274 let bytes = vec![0x00];
275 let mut cursor: &[u8] = &bytes;
276 assert!(matches!(
277 decode_path(&mut cursor),
278 Err(Error::InvalidPathIndicator(0x00)),
279 ));
280 }
281
282 #[test]
283 fn round_trip_indicator_0x16_added_in_v0_2() {
284 let path = DerivationPath::from_str("m/48'/1'/0'/1'").unwrap();
291 let encoded = encode_path(&path);
292 assert_eq!(encoded, vec![0x16]);
293 let mut cursor: &[u8] = &encoded;
294 let decoded = decode_path(&mut cursor).unwrap();
295 assert_eq!(decoded, path);
296 assert!(cursor.is_empty());
297 }
298
299 #[test]
300 fn rejects_reserved_indicator_high_range() {
301 let bytes = vec![0xFD];
303 let mut cursor: &[u8] = &bytes;
304 assert!(matches!(
305 decode_path(&mut cursor),
306 Err(Error::InvalidPathIndicator(0xFD)),
307 ));
308 let bytes = vec![0xFF];
310 let mut cursor: &[u8] = &bytes;
311 assert!(matches!(
312 decode_path(&mut cursor),
313 Err(Error::InvalidPathIndicator(0xFF)),
314 ));
315 }
316
317 #[test]
318 fn rejects_truncated_explicit_path() {
319 let bytes = vec![0xFE, 2u8, 0u8];
321 let mut cursor: &[u8] = &bytes;
322 assert!(matches!(
323 decode_path(&mut cursor),
324 Err(Error::UnexpectedEnd),
325 ));
326 }
327
328 #[test]
329 fn leb128_encode_examples() {
330 let mut out = Vec::new();
332 leb128_encode(0, &mut out);
333 assert_eq!(out, vec![0]);
334 let mut out = Vec::new();
336 leb128_encode(127, &mut out);
337 assert_eq!(out, vec![0x7F]);
338 let mut out = Vec::new();
340 leb128_encode(128, &mut out);
341 assert_eq!(out, vec![0x80, 0x01]);
342 let mut out = Vec::new();
344 leb128_encode(0x8000_0000, &mut out);
345 assert_eq!(out, vec![0x80, 0x80, 0x80, 0x80, 0x08]);
346 }
347}