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 if *offset + 2 > data.len() {
49 return Err(SignerError::EncodingError(
50 "compact size: truncated u16".into(),
51 ));
52 }
53 let val = u16::from_le_bytes([data[*offset], data[*offset + 1]]);
54 *offset += 2;
55 Ok(val as u64)
56 }
57 0xFE => {
58 if *offset + 4 > data.len() {
59 return Err(SignerError::EncodingError(
60 "compact size: truncated u32".into(),
61 ));
62 }
63 let mut buf = [0u8; 4];
64 buf.copy_from_slice(&data[*offset..*offset + 4]);
65 *offset += 4;
66 Ok(u32::from_le_bytes(buf) as u64)
67 }
68 0xFF => {
69 if *offset + 8 > data.len() {
70 return Err(SignerError::EncodingError(
71 "compact size: truncated u64".into(),
72 ));
73 }
74 let mut buf = [0u8; 8];
75 buf.copy_from_slice(&data[*offset..*offset + 8]);
76 *offset += 8;
77 Ok(u64::from_le_bytes(buf))
78 }
79 }
80}
81
82pub fn bech32_encode(
88 hrp: &str,
89 witness_version: u8,
90 program: &[u8],
91) -> Result<String, SignerError> {
92 use bech32::Hrp;
93 let hrp =
94 Hrp::parse(hrp).map_err(|e| SignerError::EncodingError(format!("bech32 hrp: {e}")))?;
95 let version = bech32::Fe32::try_from(witness_version)
96 .map_err(|e| SignerError::EncodingError(format!("witness version: {e}")))?;
97 bech32::segwit::encode(hrp, version, program)
98 .map_err(|e| SignerError::EncodingError(format!("bech32 encode: {e}")))
99}
100
101pub fn base58check_encode(version: u8, payload: &[u8]) -> String {
107 let mut data = Vec::with_capacity(1 + payload.len() + 4);
108 data.push(version);
109 data.extend_from_slice(payload);
110 let checksum = crypto::double_sha256(&data);
111 data.extend_from_slice(&checksum[..4]);
112 bs58::encode(&data).into_string()
113}
114
115pub fn base58check_decode(s: &str) -> Result<(u8, Vec<u8>), SignerError> {
119 let decoded = bs58::decode(s)
120 .into_vec()
121 .map_err(|e| SignerError::EncodingError(format!("base58: {e}")))?;
122 if decoded.len() < 5 {
123 return Err(SignerError::EncodingError("base58check too short".into()));
124 }
125 let payload_end = decoded.len() - 4;
126 let checksum = crypto::double_sha256(&decoded[..payload_end]);
127 use subtle::ConstantTimeEq;
128 if checksum[..4].ct_eq(&decoded[payload_end..]).unwrap_u8() != 1 {
129 return Err(SignerError::EncodingError(
130 "base58check: invalid checksum".into(),
131 ));
132 }
133 Ok((decoded[0], decoded[1..payload_end].to_vec()))
134}
135
136#[cfg(test)]
137#[allow(clippy::unwrap_used, clippy::expect_used)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_compact_size_roundtrip() {
143 for val in [
144 0u64,
145 1,
146 252,
147 253,
148 0xFFFF,
149 0x10000,
150 0xFFFF_FFFF,
151 0x1_0000_0000,
152 ] {
153 let mut buf = Vec::new();
154 encode_compact_size(&mut buf, val);
155 let mut offset = 0;
156 let parsed = read_compact_size(&buf, &mut offset).expect("ok");
157 assert_eq!(parsed, val, "failed for {val}");
158 assert_eq!(offset, buf.len());
159 }
160 }
161
162 #[test]
163 fn test_compact_size_single_byte() {
164 let mut buf = Vec::new();
165 encode_compact_size(&mut buf, 42);
166 assert_eq!(buf, vec![42]);
167 }
168
169 #[test]
170 fn test_compact_size_eof() {
171 let mut offset = 0;
172 assert!(read_compact_size(&[], &mut offset).is_err());
173 }
174
175 #[test]
176 fn test_base58check_roundtrip() {
177 let encoded = base58check_encode(0x00, &[0xAA; 20]);
178 let (version, payload) = base58check_decode(&encoded).expect("ok");
179 assert_eq!(version, 0x00);
180 assert_eq!(payload, vec![0xAA; 20]);
181 }
182
183 #[test]
184 fn test_base58check_invalid_checksum() {
185 let mut encoded = base58check_encode(0x00, &[0xBB; 20]);
186 encoded.pop();
188 encoded.push('1');
189 let result = base58check_decode(&encoded);
191 assert!(result.is_err());
192 }
193
194 #[test]
195 fn test_base58check_too_short() {
196 assert!(base58check_decode("1").is_err());
197 }
198
199 #[test]
200 fn test_bech32_encode_v0() {
201 let addr = bech32_encode("bc", 0, &[0xAA; 20]).expect("ok");
202 assert!(addr.starts_with("bc1q"));
203 }
204
205 #[test]
206 fn test_bech32_encode_v1() {
207 let addr = bech32_encode("bc", 1, &[0xBB; 32]).expect("ok");
208 assert!(addr.starts_with("bc1p"));
209 }
210
211 #[test]
212 fn test_bech32_encode_testnet() {
213 let addr = bech32_encode("tb", 0, &[0xCC; 20]).expect("ok");
214 assert!(addr.starts_with("tb1q"));
215 }
216
217 #[test]
220 fn test_bech32_bip173_p2wpkh_vector() {
221 let program = hex::decode("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
223 let addr = bech32_encode("bc", 0, &program).unwrap();
224 assert_eq!(addr, "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4");
225 }
226
227 #[test]
228 fn test_bech32_bip350_p2tr_vector() {
229 let program =
231 hex::decode("a60869f0dbcf1dc659c9cecbee736b12006a35d655ac7e1caeff5ebc1085a044")
232 .unwrap();
233 let addr = bech32_encode("bc", 1, &program).unwrap();
234 assert!(addr.starts_with("bc1p"));
235 assert_eq!(addr.len(), 62); }
237
238 #[test]
239 fn test_bech32_invalid_hrp() {
240 assert!(bech32_encode("", 0, &[0; 20]).is_err());
241 }
242
243 #[test]
246 fn test_base58check_bitcoin_p2pkh_genesis() {
247 let hash160 = hex::decode("751e76e8199196d454941c45d1b3a323f1433bd6").unwrap();
250 let addr = base58check_encode(0x00, &hash160);
251 assert_eq!(addr, "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH");
252 }
253
254 #[test]
255 fn test_base58check_decode_known_address() {
256 let (version, payload) = base58check_decode("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH").unwrap();
257 assert_eq!(version, 0x00);
258 assert_eq!(
259 hex::encode(payload),
260 "751e76e8199196d454941c45d1b3a323f1433bd6"
261 );
262 }
263
264 #[test]
267 fn test_compact_size_boundary_252() {
268 let mut buf = Vec::new();
269 encode_compact_size(&mut buf, 252);
270 assert_eq!(buf.len(), 1); assert_eq!(buf[0], 252);
272 }
273
274 #[test]
275 fn test_compact_size_boundary_253() {
276 let mut buf = Vec::new();
277 encode_compact_size(&mut buf, 253);
278 assert_eq!(buf[0], 0xFD); assert_eq!(buf.len(), 3);
280 let mut offset = 0;
281 assert_eq!(read_compact_size(&buf, &mut offset).unwrap(), 253);
282 }
283
284 #[test]
285 fn test_compact_size_truncated_u16() {
286 let buf = vec![0xFD, 0x01]; let mut offset = 0;
288 assert!(read_compact_size(&buf, &mut offset).is_err());
289 }
290
291 #[test]
292 fn test_compact_size_truncated_u32() {
293 let buf = vec![0xFE, 0x01, 0x00]; let mut offset = 0;
295 assert!(read_compact_size(&buf, &mut offset).is_err());
296 }
297
298 #[test]
299 fn test_compact_size_truncated_u64() {
300 let buf = vec![0xFF, 0x01, 0x00, 0x00, 0x00]; let mut offset = 0;
302 assert!(read_compact_size(&buf, &mut offset).is_err());
303 }
304}