Skip to main content

mini_bitcoin_script/
hex.rs

1use crate::error::ScriptError;
2
3/// Decode a hexadecimal string into a byte vector.
4///
5/// Accepts both uppercase and lowercase hex digits. Does not accept
6/// a `0x` prefix — callers must strip it if present.
7///
8/// # Errors
9///
10/// Returns [`ScriptError::InvalidHex`] if the string has an odd number
11/// of characters or contains non-hex characters.
12pub fn decode_hex(hex: &str) -> Result<Vec<u8>, ScriptError> {
13    if hex.len() % 2 != 0 {
14        return Err(ScriptError::InvalidHex);
15    }
16
17    let mut bytes = Vec::with_capacity(hex.len() / 2);
18
19    for i in (0..hex.len()).step_by(2) {
20        let pair = &hex[i..i + 2];
21        let byte = u8::from_str_radix(pair, 16).map_err(|_| ScriptError::InvalidHex)?;
22        bytes.push(byte);
23    }
24
25    Ok(bytes)
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn empty_string() {
34        assert_eq!(decode_hex("").unwrap(), vec![]);
35    }
36
37    #[test]
38    fn single_byte() {
39        assert_eq!(decode_hex("00").unwrap(), vec![0x00]);
40        assert_eq!(decode_hex("ff").unwrap(), vec![0xff]);
41    }
42
43    #[test]
44    fn mixed_case() {
45        assert_eq!(decode_hex("FF").unwrap(), vec![0xff]);
46        assert_eq!(decode_hex("aAbB").unwrap(), vec![0xaa, 0xbb]);
47    }
48
49    #[test]
50    fn multi_byte() {
51        assert_eq!(
52            decode_hex("deadbeef").unwrap(),
53            vec![0xde, 0xad, 0xbe, 0xef]
54        );
55    }
56
57    #[test]
58    fn odd_length() {
59        assert_eq!(decode_hex("0"), Err(ScriptError::InvalidHex));
60        assert_eq!(decode_hex("abc"), Err(ScriptError::InvalidHex));
61    }
62
63    #[test]
64    fn invalid_characters() {
65        assert_eq!(decode_hex("gg"), Err(ScriptError::InvalidHex));
66        assert_eq!(decode_hex("0x00"), Err(ScriptError::InvalidHex));
67    }
68}