mk_codec/bytecode/
encode.rs1use crate::bytecode::header::BytecodeHeader;
15use crate::bytecode::path::encode_path;
16use crate::bytecode::xpub_compact::{XpubCompact, encode_xpub_compact};
17use crate::error::{Error, Result};
18use crate::key_card::KeyCard;
19
20pub fn encode_bytecode(card: &KeyCard) -> Result<Vec<u8>> {
22 if card.policy_id_stubs.is_empty() {
23 return Err(Error::InvalidPolicyIdStubCount);
24 }
25 if card.policy_id_stubs.len() > u8::MAX as usize {
26 return Err(Error::InvalidPolicyIdStubCount);
27 }
28
29 let header = BytecodeHeader {
30 version: 0,
31 fingerprint_flag: card.origin_fingerprint.is_some(),
32 };
33
34 let mut out: Vec<u8> = Vec::new();
35 out.push(header.to_byte());
36 out.push(card.policy_id_stubs.len() as u8);
37 for stub in &card.policy_id_stubs {
38 out.extend_from_slice(stub);
39 }
40 if let Some(fp) = &card.origin_fingerprint {
41 out.extend_from_slice(fp.as_bytes());
42 }
43 out.extend_from_slice(&encode_path(&card.origin_path));
44 let compact = XpubCompact::from_xpub(&card.xpub);
45 encode_xpub_compact(&compact, &mut out);
46 Ok(out)
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::bytecode::test_helpers::synthetic_xpub;
53 use bitcoin::bip32::{DerivationPath, Fingerprint};
54 use std::str::FromStr;
55
56 fn fixture_card_1stub_with_fp() -> KeyCard {
57 let path = DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
58 KeyCard {
59 policy_id_stubs: vec![[0xAA; 4]],
60 origin_fingerprint: Some(Fingerprint::from([0xD3, 0x4D, 0xB3, 0x3F])),
61 xpub: synthetic_xpub(&path),
62 origin_path: path,
63 }
64 }
65
66 #[test]
67 fn encodes_typical_1stub_card_to_84_bytes() {
68 let card = fixture_card_1stub_with_fp();
69 let wire = encode_bytecode(&card).unwrap();
70 assert_eq!(wire.len(), 84);
72 assert_eq!(wire[0], 0x04, "fingerprint flag set");
73 assert_eq!(wire[1], 1, "stub_count = 1");
74 assert_eq!(&wire[2..6], &[0xAA; 4], "stub bytes");
75 assert_eq!(&wire[6..10], &[0xD3, 0x4D, 0xB3, 0x3F], "fp bytes");
76 assert_eq!(wire[10], 0x05, "std-table indicator for m/48'/0'/0'/2'");
77 }
78
79 #[test]
80 fn encodes_card_without_fingerprint_to_80_bytes() {
81 let mut card = fixture_card_1stub_with_fp();
82 card.origin_fingerprint = None;
83 let wire = encode_bytecode(&card).unwrap();
84 assert_eq!(wire.len(), 80);
86 assert_eq!(wire[0], 0x00, "fingerprint flag unset");
87 }
88
89 #[test]
90 fn rejects_zero_stubs() {
91 let mut card = fixture_card_1stub_with_fp();
92 card.policy_id_stubs.clear();
93 assert!(matches!(
94 encode_bytecode(&card),
95 Err(Error::InvalidPolicyIdStubCount),
96 ));
97 }
98
99 #[test]
100 fn deterministic_output() {
101 let card = fixture_card_1stub_with_fp();
102 let a = encode_bytecode(&card).unwrap();
103 let b = encode_bytecode(&card).unwrap();
104 assert_eq!(a, b, "encoder must be byte-deterministic");
105 }
106}