1use crate::crypto;
7use crate::error::SignerError;
8
9pub fn encode_compact_size(buf: &mut Vec<u8>, value: u64) {
20 if value < 0xFD {
21 buf.push(value as u8);
22 } else if value <= 0xFFFF {
23 buf.push(0xFD);
24 buf.extend_from_slice(&(value as u16).to_le_bytes());
25 } else if value <= 0xFFFF_FFFF {
26 buf.push(0xFE);
27 buf.extend_from_slice(&(value as u32).to_le_bytes());
28 } else {
29 buf.push(0xFF);
30 buf.extend_from_slice(&value.to_le_bytes());
31 }
32}
33
34pub fn read_compact_size(data: &[u8], offset: &mut usize) -> Result<u64, SignerError> {
38 if *offset >= data.len() {
39 return Err(SignerError::EncodingError(
40 "compact size: unexpected EOF".into(),
41 ));
42 }
43 let first = data[*offset];
44 *offset += 1;
45 match first {
46 0x00..=0xFC => Ok(first as u64),
47 0xFD => {
48 let end = offset.checked_add(2).ok_or_else(|| {
49 SignerError::EncodingError("compact size: u16 offset overflow".into())
50 })?;
51 if end > data.len() {
52 return Err(SignerError::EncodingError(
53 "compact size: truncated u16".into(),
54 ));
55 }
56 let val = u16::from_le_bytes([data[*offset], data[*offset + 1]]);
57 *offset = end;
58 if val < 0xFD {
60 return Err(SignerError::EncodingError(
61 "compact size: non-canonical 0xFD encoding for value < 253".into(),
62 ));
63 }
64 Ok(val as u64)
65 }
66 0xFE => {
67 let end = offset.checked_add(4).ok_or_else(|| {
68 SignerError::EncodingError("compact size: u32 offset overflow".into())
69 })?;
70 if end > data.len() {
71 return Err(SignerError::EncodingError(
72 "compact size: truncated u32".into(),
73 ));
74 }
75 let mut buf = [0u8; 4];
76 buf.copy_from_slice(&data[*offset..end]);
77 *offset = end;
78 let val = u32::from_le_bytes(buf);
79 if val <= 0xFFFF {
81 return Err(SignerError::EncodingError(
82 "compact size: non-canonical 0xFE encoding for value <= 0xFFFF".into(),
83 ));
84 }
85 Ok(val as u64)
86 }
87 0xFF => {
88 let end = offset.checked_add(8).ok_or_else(|| {
89 SignerError::EncodingError("compact size: u64 offset overflow".into())
90 })?;
91 if end > data.len() {
92 return Err(SignerError::EncodingError(
93 "compact size: truncated u64".into(),
94 ));
95 }
96 let mut buf = [0u8; 8];
97 buf.copy_from_slice(&data[*offset..end]);
98 *offset = end;
99 let val = u64::from_le_bytes(buf);
100 if val <= 0xFFFF_FFFF {
102 return Err(SignerError::EncodingError(
103 "compact size: non-canonical 0xFF encoding for value <= 0xFFFFFFFF".into(),
104 ));
105 }
106 Ok(val)
107 }
108 }
109}
110
111pub fn bech32_encode(
117 hrp: &str,
118 witness_version: u8,
119 program: &[u8],
120) -> Result<String, SignerError> {
121 use bech32::Hrp;
122 let hrp =
123 Hrp::parse(hrp).map_err(|e| SignerError::EncodingError(format!("bech32 hrp: {e}")))?;
124 let version = bech32::Fe32::try_from(witness_version)
125 .map_err(|e| SignerError::EncodingError(format!("witness version: {e}")))?;
126 bech32::segwit::encode(hrp, version, program)
127 .map_err(|e| SignerError::EncodingError(format!("bech32 encode: {e}")))
128}
129
130pub fn base58check_encode(version: u8, payload: &[u8]) -> String {
136 let mut data = Vec::with_capacity(1 + payload.len() + 4);
137 data.push(version);
138 data.extend_from_slice(payload);
139 let checksum = crypto::double_sha256(&data);
140 data.extend_from_slice(&checksum[..4]);
141 bs58::encode(&data).into_string()
142}
143
144pub fn base58check_decode(s: &str) -> Result<(u8, Vec<u8>), SignerError> {
148 let decoded = bs58::decode(s)
149 .into_vec()
150 .map_err(|e| SignerError::EncodingError(format!("base58: {e}")))?;
151 if decoded.len() < 5 {
152 return Err(SignerError::EncodingError("base58check too short".into()));
153 }
154 let payload_end = decoded.len() - 4;
155 let checksum = crypto::double_sha256(&decoded[..payload_end]);
156 use subtle::ConstantTimeEq;
157 if checksum[..4].ct_eq(&decoded[payload_end..]).unwrap_u8() != 1 {
158 return Err(SignerError::EncodingError(
159 "base58check: invalid checksum".into(),
160 ));
161 }
162 Ok((decoded[0], decoded[1..payload_end].to_vec()))
163}
164
165#[cfg(test)]
166#[allow(clippy::unwrap_used, clippy::expect_used)]
167mod tests {
168 use super::*;
169
170 #[test]
171 fn test_compact_size_roundtrip() {
172 for val in [
173 0u64,
174 1,
175 252,
176 253,
177 0xFFFF,
178 0x10000,
179 0xFFFF_FFFF,
180 0x1_0000_0000,
181 ] {
182 let mut buf = Vec::new();
183 encode_compact_size(&mut buf, val);
184 let mut offset = 0;
185 let parsed = read_compact_size(&buf, &mut offset).expect("ok");
186 assert_eq!(parsed, val, "failed for {val}");
187 assert_eq!(offset, buf.len());
188 }
189 }
190
191 #[test]
192 fn test_compact_size_single_byte() {
193 let mut buf = Vec::new();
194 encode_compact_size(&mut buf, 42);
195 assert_eq!(buf, vec![42]);
196 }
197
198 #[test]
199 fn test_compact_size_eof() {
200 let mut offset = 0;
201 assert!(read_compact_size(&[], &mut offset).is_err());
202 }
203
204 #[test]
205 fn test_base58check_roundtrip() {
206 let encoded = base58check_encode(0x00, &[0xAA; 20]);
207 let (version, payload) = base58check_decode(&encoded).expect("ok");
208 assert_eq!(version, 0x00);
209 assert_eq!(payload, vec![0xAA; 20]);
210 }
211
212 #[test]
213 fn test_base58check_invalid_checksum() {
214 let mut encoded = base58check_encode(0x00, &[0xBB; 20]);
215 encoded.pop();
217 encoded.push('1');
218 let result = base58check_decode(&encoded);
220 assert!(result.is_err());
221 }
222
223 #[test]
224 fn test_base58check_too_short() {
225 assert!(base58check_decode("1").is_err());
226 }
227
228 #[test]
229 fn test_bech32_encode_v0() {
230 let addr = bech32_encode("bc", 0, &[0xAA; 20]).expect("ok");
231 assert!(addr.starts_with("bc1q"));
232 }
233
234 #[test]
235 fn test_bech32_encode_v1() {
236 let addr = bech32_encode("bc", 1, &[0xBB; 32]).expect("ok");
237 assert!(addr.starts_with("bc1p"));
238 }
239
240 #[test]
241 fn test_bech32_encode_testnet() {
242 let addr = bech32_encode("tb", 0, &[0xCC; 20]).expect("ok");
243 assert!(addr.starts_with("tb1q"));
244 }
245
246 #[test]
249 fn test_bech32_bip173_p2wpkh_vector() {
250 let program = hex::decode("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
252 let addr = bech32_encode("bc", 0, &program).unwrap();
253 assert_eq!(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
254 }
255
256 #[test]
257 fn test_bech32_bip350_p2tr_vector() {
258 let program =
260 hex::decode("a60869f0dbcf1dc659c9cecbee736b12006a35d655ac7e1caeff5ebc1085a044")
261 .unwrap();
262 let addr = bech32_encode("bc", 1, &program).unwrap();
263 assert!(addr.starts_with("bc1p"));
264 assert_eq!(addr.len(), 62); }
266
267 #[test]
268 fn test_bech32_invalid_hrp() {
269 assert!(bech32_encode("", 0, &[0; 20]).is_err());
270 }
271
272 #[test]
275 fn test_base58check_bitcoin_p2pkh_genesis() {
276 let hash160 = hex::decode("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
279 let addr = base58check_encode(0x00, &hash160);
280 assert_eq!(addr, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
281 }
282
283 #[test]
284 fn test_base58check_decode_known_address() {
285 let (version, payload) = base58check_decode("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH").unwrap();
286 assert_eq!(version, 0x00);
287 assert_eq!(
288 hex::encode(payload),
289 "751e76e8199196d454941c45d1b3a323f1433bd6"
290 );
291 }
292
293 #[test]
296 fn test_compact_size_boundary_252() {
297 let mut buf = Vec::new();
298 encode_compact_size(&mut buf, 252);
299 assert_eq!(buf.len(), 1); assert_eq!(buf[0], 252);
301 }
302
303 #[test]
304 fn test_compact_size_boundary_253() {
305 let mut buf = Vec::new();
306 encode_compact_size(&mut buf, 253);
307 assert_eq!(buf[0], 0xFD); assert_eq!(buf.len(), 3);
309 let mut offset = 0;
310 assert_eq!(read_compact_size(&buf, &mut offset).unwrap(), 253);
311 }
312
313 #[test]
314 fn test_compact_size_truncated_u16() {
315 let buf = vec![0xFD, 0x01]; let mut offset = 0;
317 assert!(read_compact_size(&buf, &mut offset).is_err());
318 }
319
320 #[test]
321 fn test_compact_size_truncated_u32() {
322 let buf = vec![0xFE, 0x01, 0x00]; let mut offset = 0;
324 assert!(read_compact_size(&buf, &mut offset).is_err());
325 }
326
327 #[test]
328 fn test_compact_size_truncated_u64() {
329 let buf = vec![0xFF, 0x01, 0x00, 0x00, 0x00]; let mut offset = 0;
331 assert!(read_compact_size(&buf, &mut offset).is_err());
332 }
333}