chia_sdk_utils/
bech32.rs

1use bech32::{Variant, u5};
2use chia_protocol::{Bytes, Bytes32};
3use thiserror::Error;
4
5#[derive(Error, Debug, Clone, PartialEq, Eq)]
6pub enum Bech32Error {
7    #[error("not bech32m encoded")]
8    InvalidFormat,
9
10    #[error("expected 32 bytes, found {0}")]
11    WrongLength(usize),
12
13    #[error("expected prefix {1}, found {0}")]
14    WrongPrefix(String, String),
15
16    #[error("bech32 error: {0}")]
17    Decode(#[from] bech32::Error),
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Bech32 {
22    pub data: Bytes,
23    pub prefix: String,
24}
25
26impl Bech32 {
27    pub fn new(data: Bytes, prefix: String) -> Self {
28        Self { data, prefix }
29    }
30
31    pub fn decode(address: &str) -> Result<Self, Bech32Error> {
32        let (hrp, data, variant) = bech32::decode(address)?;
33
34        if variant != Variant::Bech32m {
35            return Err(Bech32Error::InvalidFormat);
36        }
37
38        let data = bech32::convert_bits(&data, 5, 8, false)?;
39
40        Ok(Self {
41            data: data.into(),
42            prefix: hrp,
43        })
44    }
45
46    pub fn encode(&self) -> Result<String, Bech32Error> {
47        let data = bech32::convert_bits(&self.data, 8, 5, true)
48            .unwrap()
49            .into_iter()
50            .map(u5::try_from_u8)
51            .collect::<Result<Vec<_>, bech32::Error>>()?;
52        Ok(bech32::encode(&self.prefix, data, Variant::Bech32m)?)
53    }
54
55    pub fn expect_prefix(self, prefix: &str) -> Result<Bytes, Bech32Error> {
56        if self.prefix != prefix {
57            return Err(Bech32Error::WrongPrefix(self.prefix, prefix.to_string()));
58        }
59        Ok(self.data)
60    }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Address {
65    pub puzzle_hash: Bytes32,
66    pub prefix: String,
67}
68
69impl Address {
70    pub fn new(puzzle_hash: Bytes32, prefix: String) -> Self {
71        Self {
72            puzzle_hash,
73            prefix,
74        }
75    }
76
77    pub fn decode(address: &str) -> Result<Self, Bech32Error> {
78        Bech32::decode(address).and_then(TryInto::try_into)
79    }
80
81    pub fn encode(&self) -> Result<String, Bech32Error> {
82        Bech32::from(self.clone()).encode()
83    }
84
85    pub fn expect_prefix(self, prefix: &str) -> Result<Bytes32, Bech32Error> {
86        if self.prefix != prefix {
87            return Err(Bech32Error::WrongPrefix(self.prefix, prefix.to_string()));
88        }
89        Ok(self.puzzle_hash)
90    }
91}
92
93impl TryFrom<Bech32> for Address {
94    type Error = Bech32Error;
95
96    fn try_from(bech32: Bech32) -> Result<Self, Self::Error> {
97        let len = bech32.data.len();
98        Ok(Self::new(
99            bech32
100                .data
101                .try_into()
102                .map_err(|_| Bech32Error::WrongLength(len))?,
103            bech32.prefix,
104        ))
105    }
106}
107
108impl From<Address> for Bech32 {
109    fn from(address: Address) -> Self {
110        Bech32::new(address.puzzle_hash.into(), address.prefix)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    fn check_addr(expected: &str) {
119        let info = Address::decode(expected).unwrap();
120        let actual = info.encode().unwrap();
121        assert_eq!(actual, expected);
122    }
123
124    #[test]
125    fn test_addresses() {
126        check_addr("xch1a0t57qn6uhe7tzjlxlhwy2qgmuxvvft8gnfzmg5detg0q9f3yc3s2apz0h");
127        check_addr("xch1ftxk2v033kv94ueucp0a34sgt9398vle7l7g3q9k4leedjmmdysqvv6q96");
128        check_addr("xch1ay273ctc9c6nxmzmzsup28scrce8ney84j4nlewdlaxqs22v53ksxgf38f");
129        check_addr("xch1avnwmy2fuesq7h2jnxehlrs9msrad9uuvrhms35k2pqwmjv56y5qk7zm6v");
130    }
131
132    #[test]
133    fn test_invalid_addresses() {
134        assert_eq!(
135            Address::decode("hello there!"),
136            Err(Bech32Error::Decode(bech32::Error::MissingSeparator))
137        );
138        assert_eq!(
139            Address::decode("bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"),
140            Err(Bech32Error::InvalidFormat)
141        );
142    }
143}