chik_sdk_utils/
address.rs1use bech32::{u5, Variant};
2use hex::FromHexError;
3use thiserror::Error;
4
5#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
7pub enum AddressError {
8 #[error("encoding is not bech32m")]
10 InvalidFormat,
11
12 #[error("wrong length, expected 32 bytes but found {0}")]
14 WrongLength(usize),
15
16 #[error("error when decoding address: {0}")]
18 Decode(#[from] bech32::Error),
19}
20
21#[derive(Error, Debug, Clone, Copy, PartialEq)]
23pub enum PuzzleHashError {
24 #[error("wrong length, expected 32 bytes but found {0}")]
26 WrongLength(usize),
27
28 #[error("error when decoding puzzle hash: {0}")]
30 Decode(#[from] FromHexError),
31}
32
33pub 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
41pub 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
50pub 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
67pub 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
77pub 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}