chik_sdk_utils/
address.rs

1use bech32::{u5, Variant};
2use hex::FromHexError;
3use thiserror::Error;
4
5/// Errors you can get while trying to decode an address.
6#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
7pub enum AddressError {
8    /// The address was encoded as bech32, rather than bech32m.
9    #[error("encoding is not bech32m")]
10    InvalidFormat,
11
12    /// The data was not 32 bytes in length.
13    #[error("wrong length, expected 32 bytes but found {0}")]
14    WrongLength(usize),
15
16    /// An error occured while trying to decode the address.
17    #[error("error when decoding address: {0}")]
18    Decode(#[from] bech32::Error),
19}
20
21/// Errors you can get while trying to decode a puzzle hash.
22#[derive(Error, Debug, Clone, Copy, PartialEq)]
23pub enum PuzzleHashError {
24    /// The buffer was not 32 bytes in length.
25    #[error("wrong length, expected 32 bytes but found {0}")]
26    WrongLength(usize),
27
28    /// An error occured while trying to decode the puzzle hash.
29    #[error("error when decoding puzzle hash: {0}")]
30    Decode(#[from] FromHexError),
31}
32
33/// Decodes a puzzle hash from hex, with or without a prefix.
34pub fn decode_puzzle_hash(puzzle_hash: &str) -> Result<[u8; 32], PuzzleHashError> {
35    let data = hex::decode(strip_prefix(puzzle_hash))?;
36    let length = data.len();
37    data.try_into()
38        .map_err(|_| PuzzleHashError::WrongLength(length))
39}
40
41/// Encodes a puzzle hash into hex, with or without a prefix.
42pub fn encode_puzzle_hash(puzzle_hash: [u8; 32], include_0x: bool) -> String {
43    if include_0x {
44        format!("0x{}", hex::encode(puzzle_hash))
45    } else {
46        hex::encode(puzzle_hash)
47    }
48}
49
50/// Decodes an address into a puzzle hash and HRP prefix.
51pub fn decode_address(address: &str) -> Result<([u8; 32], String), AddressError> {
52    let (hrp, data, variant) = bech32::decode(address)?;
53
54    if variant != Variant::Bech32m {
55        return Err(AddressError::InvalidFormat);
56    }
57
58    let data = bech32::convert_bits(&data, 5, 8, false)?;
59    let length = data.len();
60    let puzzle_hash = data
61        .try_into()
62        .map_err(|_| AddressError::WrongLength(length))?;
63
64    Ok((puzzle_hash, hrp))
65}
66
67/// Encodes an address with a given HRP prefix.
68pub fn encode_address(puzzle_hash: [u8; 32], prefix: &str) -> Result<String, bech32::Error> {
69    let data = bech32::convert_bits(&puzzle_hash, 8, 5, true)
70        .unwrap()
71        .into_iter()
72        .map(u5::try_from_u8)
73        .collect::<Result<Vec<_>, bech32::Error>>()?;
74    bech32::encode(prefix, data, Variant::Bech32m)
75}
76
77/// Removes the `0x` prefix from a puzzle hash in hex format.
78pub fn strip_prefix(puzzle_hash: &str) -> &str {
79    if let Some(puzzle_hash) = puzzle_hash.strip_prefix("0x") {
80        puzzle_hash
81    } else if let Some(puzzle_hash) = puzzle_hash.strip_prefix("0X") {
82        puzzle_hash
83    } else {
84        puzzle_hash
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use hex_literal::hex;
91
92    use super::*;
93
94    fn check_ph(expected: &str) {
95        let expected = strip_prefix(expected);
96        let puzzle_hash = decode_puzzle_hash(expected).unwrap();
97        let actual = encode_puzzle_hash(puzzle_hash, false);
98        assert_eq!(actual, expected);
99    }
100
101    fn check_addr(expected: &str) {
102        let (puzzle_hash, prefix) = decode_address(expected).unwrap();
103        let actual = encode_address(puzzle_hash, &prefix).unwrap();
104        assert_eq!(actual, expected);
105    }
106
107    #[test]
108    fn test_strip_prefix() {
109        check_ph("0x2999682870bd24e7fd0ef6324c69794ff93fc41b016777d2edd5ea8575bdaa31");
110        check_ph("0x99619cc6888f1bd30acd6e8c1f4065dafeba2246bfc3465cddda4e6656083791");
111        check_ph("0X7cc6494dd96d32c97b5f6ba77caae269acd6c86593ada66f343050ce709e904a");
112        check_ph("0X9f057817ad576b24ec60a25ded08f5bde6db0aa0beeb0c099e3ce176866e1c4b");
113    }
114
115    #[test]
116    fn test_puzzle_hashes() {
117        check_ph(&hex::encode([0; 32]));
118        check_ph(&hex::encode([255; 32]));
119        check_ph(&hex::encode([127; 32]));
120        check_ph(&hex::encode([1; 32]));
121        check_ph("f46ec440aeb9b3baa19968810a8537ec4ff406c09c994dd7d3222b87258a52ff");
122        check_ph("2f981b2f9510ef9e62523e6b38fc933e2f060c411cfa64906413ddfd56be8dc1");
123        check_ph("3e09bdd6b19659555a7c8456c5af54d004d774f3d44689360d4778ce685201ad");
124        check_ph("d16c2ad7c5642532659e424dc0d7e4a85779c6dab801b5e6117a8c8587156472");
125    }
126
127    #[test]
128    fn test_invalid_puzzle_hashes() {
129        assert_eq!(
130            decode_puzzle_hash("ac4fd55996a1186fffc30c5b60385a88fd78d538f1c9febbfa9c8a9e9a170ad"),
131            Err(PuzzleHashError::Decode(FromHexError::OddLength))
132        );
133        assert_eq!(
134            decode_puzzle_hash(&hex::encode(hex!(
135                "
136            dfe399911acc4426f44bf31f4d817f6b69f244bbad138a28
137            25c05550f7d2ab70c35408f764281febd624ac8cdfc91817
138            "
139            ))),
140            Err(PuzzleHashError::WrongLength(48))
141        );
142        assert_eq!(
143            decode_puzzle_hash("hello there!"),
144            Err(PuzzleHashError::Decode(FromHexError::InvalidHexCharacter {
145                c: 'h',
146                index: 0
147            }))
148        );
149    }
150
151    #[test]
152    fn test_addresses() {
153        check_addr("xck1a0t57qn6uhe7tzjlxlhwy2qgmuxvvft8gnfzmg5detg0q9f3yc3szqcdpu");
154        check_addr("xck1ftxk2v033kv94ueucp0a34sgt9398vle7l7g3q9k4leedjmmdysqy3r0t3");
155        check_addr("xck1ay273ctc9c6nxmzmzsup28scrce8ney84j4nlewdlaxqs22v53ksw4s7fz");
156        check_addr("xck1avnwmy2fuesq7h2jnxehlrs9msrad9uuvrhms35k2pqwmjv56y5q7rm558");
157    }
158
159    #[test]
160    fn test_invalid_addresses() {
161        assert_eq!(
162            decode_address("hello there!"),
163            Err(AddressError::Decode(bech32::Error::MissingSeparator))
164        );
165        assert_eq!(
166            decode_address("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"),
167            Err(AddressError::InvalidFormat)
168        );
169    }
170}