mk_codec/bytecode/
decode.rs1use bitcoin::bip32::Fingerprint;
7
8use crate::bytecode::header::BytecodeHeader;
9use crate::bytecode::path::decode_path;
10use crate::bytecode::xpub_compact::{decode_xpub_compact, reconstruct_xpub};
11use crate::consts::{ORIGIN_FINGERPRINT_BYTES, POLICY_ID_STUB_BYTES};
12use crate::error::{Error, Result};
13use crate::key_card::KeyCard;
14
15pub fn decode_bytecode(bytes: &[u8]) -> Result<KeyCard> {
20 let mut cursor: &[u8] = bytes;
21
22 let header_byte = read_u8(&mut cursor)?;
23 let header = BytecodeHeader::parse(header_byte)?;
24
25 let stub_count = read_u8(&mut cursor)?;
26 if stub_count == 0 {
27 return Err(Error::InvalidPolicyIdStubCount);
28 }
29 let mut policy_id_stubs: Vec<[u8; 4]> = Vec::with_capacity(stub_count as usize);
30 for _ in 0..stub_count {
31 let stub: [u8; POLICY_ID_STUB_BYTES] = read_array(&mut cursor)?;
32 policy_id_stubs.push(stub);
33 }
34
35 let origin_fingerprint = if header.fingerprint_flag {
36 let fp_bytes: [u8; ORIGIN_FINGERPRINT_BYTES] = read_array(&mut cursor)?;
37 Some(Fingerprint::from(fp_bytes))
38 } else {
39 None
40 };
41
42 let origin_path = decode_path(&mut cursor)?;
43 let compact = decode_xpub_compact(&mut cursor)?;
44 let xpub = reconstruct_xpub(&compact, &origin_path)?;
45
46 if !cursor.is_empty() {
47 return Err(Error::TrailingBytes);
48 }
49
50 Ok(KeyCard {
51 policy_id_stubs,
52 origin_fingerprint,
53 origin_path,
54 xpub,
55 })
56}
57
58fn read_u8(cursor: &mut &[u8]) -> Result<u8> {
59 if cursor.is_empty() {
60 return Err(Error::UnexpectedEnd);
61 }
62 let b = cursor[0];
63 *cursor = &cursor[1..];
64 Ok(b)
65}
66
67fn read_array<const N: usize>(cursor: &mut &[u8]) -> Result<[u8; N]> {
68 if cursor.len() < N {
69 return Err(Error::UnexpectedEnd);
70 }
71 let mut buf = [0u8; N];
72 buf.copy_from_slice(&cursor[..N]);
73 *cursor = &cursor[N..];
74 Ok(buf)
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::bytecode::encode::encode_bytecode;
81 use crate::bytecode::test_helpers::synthetic_xpub;
82 use bitcoin::bip32::DerivationPath;
83 use std::str::FromStr;
84
85 fn fixture_card_1stub_with_fp() -> KeyCard {
86 let path = DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
87 KeyCard {
88 policy_id_stubs: vec![[0xAA; 4]],
89 origin_fingerprint: Some(Fingerprint::from([0xD3, 0x4D, 0xB3, 0x3F])),
90 xpub: synthetic_xpub(&path),
91 origin_path: path,
92 }
93 }
94
95 fn fixture_card_3stubs_no_fp() -> KeyCard {
96 let path = DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
97 KeyCard {
98 policy_id_stubs: vec![[0xAA; 4], [0xBB; 4], [0xCC; 4]],
99 origin_fingerprint: None,
100 xpub: synthetic_xpub(&path),
101 origin_path: path,
102 }
103 }
104
105 fn fixture_card_explicit_path() -> KeyCard {
106 let path = DerivationPath::from_str("m/9999'/1234'/56'/7'").unwrap();
107 KeyCard {
108 policy_id_stubs: vec![[0x11, 0x22, 0x33, 0x44]],
109 origin_fingerprint: Some(Fingerprint::from([0xAB, 0xCD, 0xEF, 0x01])),
110 xpub: synthetic_xpub(&path),
111 origin_path: path,
112 }
113 }
114
115 #[test]
116 fn round_trip_1stub_with_fp() {
117 let card = fixture_card_1stub_with_fp();
118 let wire = encode_bytecode(&card).unwrap();
119 let decoded = decode_bytecode(&wire).unwrap();
120 assert_eq!(decoded, card);
121 }
122
123 #[test]
124 fn round_trip_3stubs_no_fp() {
125 let card = fixture_card_3stubs_no_fp();
126 let wire = encode_bytecode(&card).unwrap();
127 let decoded = decode_bytecode(&wire).unwrap();
128 assert_eq!(decoded, card);
129 }
130
131 #[test]
132 fn round_trip_explicit_path() {
133 let card = fixture_card_explicit_path();
134 let wire = encode_bytecode(&card).unwrap();
135 let decoded = decode_bytecode(&wire).unwrap();
136 assert_eq!(decoded, card);
137 }
138
139 #[test]
140 fn rejects_unsupported_version() {
141 let card = fixture_card_1stub_with_fp();
142 let mut wire = encode_bytecode(&card).unwrap();
143 wire[0] = 0x10; assert!(matches!(
145 decode_bytecode(&wire),
146 Err(Error::UnsupportedVersion(1)),
147 ));
148 }
149
150 #[test]
151 fn rejects_reserved_bits_set() {
152 let card = fixture_card_1stub_with_fp();
153 let mut wire = encode_bytecode(&card).unwrap();
154 wire[0] |= 0b0000_0010; assert!(matches!(
156 decode_bytecode(&wire),
157 Err(Error::ReservedBitsSet),
158 ));
159 }
160
161 #[test]
162 fn rejects_zero_stub_count() {
163 let card = fixture_card_1stub_with_fp();
164 let mut wire = encode_bytecode(&card).unwrap();
165 wire[1] = 0; assert!(matches!(
167 decode_bytecode(&wire),
168 Err(Error::InvalidPolicyIdStubCount),
169 ));
170 }
171
172 #[test]
173 fn rejects_invalid_path_indicator() {
174 let card = fixture_card_1stub_with_fp();
175 let mut wire = encode_bytecode(&card).unwrap();
176 wire[10] = 0x18;
182 assert!(matches!(
183 decode_bytecode(&wire),
184 Err(Error::InvalidPathIndicator(0x18)),
185 ));
186 }
187
188 #[test]
189 fn rejects_invalid_xpub_version() {
190 let card = fixture_card_1stub_with_fp();
191 let mut wire = encode_bytecode(&card).unwrap();
192 wire[11] = 0xDE;
194 wire[12] = 0xAD;
195 wire[13] = 0xBE;
196 wire[14] = 0xEF;
197 assert!(matches!(
198 decode_bytecode(&wire),
199 Err(Error::InvalidXpubVersion(0xDEADBEEF)),
200 ));
201 }
202
203 #[test]
204 fn rejects_trailing_bytes() {
205 let card = fixture_card_1stub_with_fp();
206 let mut wire = encode_bytecode(&card).unwrap();
207 wire.push(0xFF); assert!(matches!(decode_bytecode(&wire), Err(Error::TrailingBytes),));
209 }
210
211 #[test]
212 fn rejects_truncated_mid_stub() {
213 let card = fixture_card_1stub_with_fp();
214 let wire = encode_bytecode(&card).unwrap();
215 let truncated = &wire[..4]; assert!(matches!(
217 decode_bytecode(truncated),
218 Err(Error::UnexpectedEnd),
219 ));
220 }
221
222 #[test]
223 fn rejects_path_too_deep_at_top_level() {
224 let card = fixture_card_1stub_with_fp();
227 let wire = encode_bytecode(&card).unwrap();
228 let header_and_pre_path = &wire[..10]; let xpub_compact_tail = &wire[11..]; let mut new_wire: Vec<u8> = header_and_pre_path.to_vec();
234 new_wire.push(0xFE); new_wire.push(11); for i in 0..11 {
237 new_wire.push(i); }
239 new_wire.extend_from_slice(xpub_compact_tail);
240 assert!(matches!(
241 decode_bytecode(&new_wire),
242 Err(Error::PathTooDeep(11)),
243 ));
244 }
245
246 #[test]
247 fn rejects_invalid_path_component_at_top_level() {
248 let card = fixture_card_1stub_with_fp();
251 let wire = encode_bytecode(&card).unwrap();
252 let header_and_pre_path = &wire[..10];
253 let xpub_compact_tail = &wire[11..];
254 let mut new_wire: Vec<u8> = header_and_pre_path.to_vec();
255 new_wire.push(0xFE); new_wire.push(1); new_wire.extend_from_slice(&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80]);
259 new_wire.extend_from_slice(xpub_compact_tail);
260 assert!(matches!(
261 decode_bytecode(&new_wire),
262 Err(Error::InvalidPathComponent(_)),
263 ));
264 }
265
266 #[test]
267 fn rejects_invalid_xpub_public_key() {
268 let card = fixture_card_1stub_with_fp();
271 let mut wire = encode_bytecode(&card).unwrap();
272 let pub_key_offset = 11 + 40;
275 wire[pub_key_offset] = 0x05;
277 for i in 1..33 {
279 wire[pub_key_offset + i] = 0xFF;
280 }
281 assert!(matches!(
282 decode_bytecode(&wire),
283 Err(Error::InvalidXpubPublicKey(_)),
284 ));
285 }
286}