fuels_types/
bech32.rs

1use std::{
2    fmt::{Display, Formatter},
3    str::FromStr,
4};
5
6use bech32::{FromBase32, ToBase32, Variant::Bech32m};
7use fuel_tx::{Address, Bytes32, ContractId};
8
9use crate::errors::{Error, Result};
10
11// Fuel Network human-readable part for bech32 encoding
12pub const FUEL_BECH32_HRP: &str = "fuel";
13
14/// Generate type represented in the Bech32 format,
15/// consisting of a human-readable part (hrp) and a hash (e.g. pubkey-, contract hash)
16macro_rules! bech32type {
17    ($i:ident) => {
18        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
19        pub struct $i {
20            pub hrp: String,
21            pub hash: Bytes32,
22        }
23
24        impl $i {
25            pub fn new<T: Into<[u8; 32]>>(hrp: &str, hash: T) -> Self {
26                Self {
27                    hrp: hrp.to_string(),
28                    hash: Bytes32::from(hash.into()),
29                }
30            }
31
32            pub fn hash(&self) -> Bytes32 {
33                self.hash
34            }
35
36            pub fn hrp(&self) -> &str {
37                &self.hrp
38            }
39        }
40
41        impl Default for $i {
42            fn default() -> $i {
43                Self {
44                    hrp: FUEL_BECH32_HRP.to_string(),
45                    hash: Bytes32::new([0u8; 32]),
46                }
47            }
48        }
49
50        impl FromStr for $i {
51            type Err = Error;
52
53            fn from_str(s: &str) -> Result<Self> {
54                let (hrp, pubkey_hash_base32, _) = bech32::decode(s)?;
55
56                let pubkey_hash: [u8; Address::LEN] = Vec::<u8>::from_base32(&pubkey_hash_base32)?
57                    .as_slice()
58                    .try_into()?;
59
60                Ok(Self {
61                    hrp,
62                    hash: Bytes32::new(pubkey_hash),
63                })
64            }
65        }
66
67        impl Display for $i {
68            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69                let data_base32 = self.hash.to_base32();
70                let encoding = bech32::encode(&self.hrp, &data_base32, Bech32m).unwrap();
71
72                write!(f, "{}", encoding)
73            }
74        }
75    };
76}
77
78bech32type!(Bech32Address);
79bech32type!(Bech32ContractId);
80
81// Bech32Address - Address conversion
82impl From<&Bech32Address> for Address {
83    fn from(data: &Bech32Address) -> Address {
84        Address::new(*data.hash)
85    }
86}
87impl From<Bech32Address> for Address {
88    fn from(data: Bech32Address) -> Address {
89        Address::new(*data.hash)
90    }
91}
92impl From<Address> for Bech32Address {
93    fn from(address: Address) -> Self {
94        Self {
95            hrp: FUEL_BECH32_HRP.to_string(),
96            hash: Bytes32::new(*address),
97        }
98    }
99}
100
101// Bech32ContractId - ContractId conversion
102impl From<&Bech32ContractId> for ContractId {
103    fn from(data: &Bech32ContractId) -> ContractId {
104        ContractId::new(*data.hash)
105    }
106}
107impl From<Bech32ContractId> for ContractId {
108    fn from(data: Bech32ContractId) -> ContractId {
109        ContractId::new(*data.hash)
110    }
111}
112impl From<ContractId> for Bech32ContractId {
113    fn from(contract_id: ContractId) -> Self {
114        Self {
115            hrp: FUEL_BECH32_HRP.to_string(),
116            hash: Bytes32::new(*contract_id),
117        }
118    }
119}
120
121#[cfg(test)]
122mod test {
123    use super::*;
124
125    #[test]
126    fn test_new() {
127        let pubkey_hash = [
128            107, 50, 223, 89, 84, 225, 186, 222, 175, 254, 253, 44, 15, 197, 229, 148, 220, 255,
129            55, 19, 170, 227, 221, 24, 183, 217, 102, 98, 75, 1, 0, 39,
130        ];
131
132        {
133            // Create from Bytes32
134            let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, Bytes32::new(pubkey_hash));
135            let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, Bytes32::new(pubkey_hash));
136
137            assert_eq!(*bech32_addr.hash(), pubkey_hash);
138            assert_eq!(*bech32_cid.hash(), pubkey_hash);
139        }
140
141        {
142            // Create from ContractId
143            let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, ContractId::new(pubkey_hash));
144            let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, ContractId::new(pubkey_hash));
145
146            assert_eq!(*bech32_addr.hash(), pubkey_hash);
147            assert_eq!(*bech32_cid.hash(), pubkey_hash);
148        }
149
150        {
151            // Create from Address
152            let bech32_addr = &Bech32Address::new(FUEL_BECH32_HRP, Address::new(pubkey_hash));
153            let bech32_cid = &Bech32ContractId::new(FUEL_BECH32_HRP, Address::new(pubkey_hash));
154
155            assert_eq!(*bech32_addr.hash(), pubkey_hash);
156            assert_eq!(*bech32_cid.hash(), pubkey_hash);
157        }
158    }
159
160    #[test]
161    fn test_from_str() {
162        let pubkey_hashes = [
163            [
164                107, 50, 223, 89, 84, 225, 186, 222, 175, 254, 253, 44, 15, 197, 229, 148, 220,
165                255, 55, 19, 170, 227, 221, 24, 183, 217, 102, 98, 75, 1, 0, 39,
166            ],
167            [
168                49, 83, 18, 64, 150, 242, 119, 146, 83, 184, 84, 96, 160, 212, 110, 69, 81, 34,
169                101, 86, 182, 99, 62, 68, 44, 28, 40, 26, 131, 21, 221, 64,
170            ],
171            [
172                48, 101, 49, 52, 48, 102, 48, 55, 48, 100, 49, 97, 102, 117, 51, 57, 49, 50, 48,
173                54, 48, 98, 48, 100, 48, 56, 49, 53, 48, 52, 49, 52,
174            ],
175        ];
176        let bech32m_encodings = [
177            "fuel1dved7k25uxadatl7l5kql309jnw07dcn4t3a6x9hm9nxyjcpqqns50p7n2",
178            "fuel1x9f3ysyk7fmey5ac23s2p4rwg4gjye2kke3nu3pvrs5p4qc4m4qqwx56k3",
179            "fuel1xpjnzdpsvccrwvryx9skvafn8ycnyvpkxp3rqeps8qcn2vp5xy6qu7yyz7",
180        ];
181
182        for (b32m_e, pbkh) in bech32m_encodings.iter().zip(pubkey_hashes) {
183            let bech32_contract_id = &Bech32ContractId::from_str(b32m_e).unwrap();
184            assert_eq!(*bech32_contract_id.hash(), pbkh);
185        }
186
187        for (b32m_e, pbkh) in bech32m_encodings.iter().zip(pubkey_hashes) {
188            let bech32_contract_id = &Bech32Address::from_str(b32m_e).unwrap();
189            assert_eq!(*bech32_contract_id.hash(), pbkh);
190        }
191    }
192
193    #[test]
194    fn test_from_invalid_bech32_string() {
195        {
196            let expected = [
197                Error::from(bech32::Error::InvalidChecksum),
198                Error::from(bech32::Error::InvalidChar('b')),
199                Error::from(bech32::Error::MissingSeparator),
200            ];
201            let invalid_bech32 = [
202                "fuel1x9f3ysyk7fmey5ac23s2p4rwg4gjye2kke3nu3pvrs5p4qc4m4qqwx32k3",
203                "fuel1xpjnzdpsvccrwvryx9skvafn8ycnyvpkxp3rqeps8qcn2vp5xy6qu7yyb7",
204                "fuelldved7k25uxadatl7l5kql309jnw07dcn4t3a6x9hm9nxyjcpqqns50p7n2",
205            ];
206
207            for (b32m_e, e) in invalid_bech32.iter().zip(expected.iter()) {
208                let result = &Bech32ContractId::from_str(b32m_e).expect_err("should error");
209                assert_eq!(result.to_string(), e.to_string());
210            }
211
212            for (b32m_e, e) in invalid_bech32.iter().zip(expected) {
213                let result = &Bech32Address::from_str(b32m_e).expect_err("should error");
214                assert_eq!(result.to_string(), e.to_string());
215            }
216        }
217    }
218}