1use alloc::string::String;
2use alloc::vec::Vec;
3use core::convert::TryInto;
4use core::fmt;
5use core::str::FromStr;
6
7#[cfg(feature = "std")]
8use std::error::Error;
9
10use bech32::{Bech32, Bech32m, Checksum, Hrp, primitives::decode::CheckedHrpstring};
11use zcash_protocol::consensus::NetworkType;
12
13use crate::constants::{self, mainnet, regtest, testnet};
14use crate::kind::unified::Encoding;
15use crate::{AddressKind, ZcashAddress, kind::*};
16
17#[derive(Debug, PartialEq, Eq)]
19pub enum ParseError {
20 InvalidEncoding,
22 NotZcash,
24 Unified(unified::ParseError),
26}
27
28impl From<unified::ParseError> for ParseError {
29 fn from(e: unified::ParseError) -> Self {
30 match e {
31 unified::ParseError::InvalidEncoding(_) => Self::InvalidEncoding,
32 unified::ParseError::UnknownPrefix(_) => Self::NotZcash,
33 _ => Self::Unified(e),
34 }
35 }
36}
37
38impl fmt::Display for ParseError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 ParseError::InvalidEncoding => write!(f, "Invalid encoding"),
42 ParseError::NotZcash => write!(f, "Not a Zcash address"),
43 ParseError::Unified(e) => e.fmt(f),
44 }
45 }
46}
47
48#[cfg(feature = "std")]
49impl Error for ParseError {}
50
51impl FromStr for ZcashAddress {
52 type Err = ParseError;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 let s = s.trim();
58
59 match unified::Address::decode(s) {
61 Ok((net, _revision, data)) => {
62 return Ok(ZcashAddress {
63 net,
64 kind: AddressKind::Unified(data),
65 });
66 }
67 Err(unified::ParseError::NotUnified | unified::ParseError::UnknownPrefix(_)) => {
68 }
70 Err(e) => {
71 return Err(ParseError::from(e));
72 }
73 }
74
75 if let Ok(parsed) = CheckedHrpstring::new::<Bech32>(s) {
77 let net = match parsed.hrp().as_str() {
79 mainnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Main,
80 testnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Test,
81 regtest::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Regtest,
82 _ => {
84 return Err(ParseError::NotZcash);
85 }
86 };
87
88 let data = parsed.byte_iter().collect::<Vec<_>>();
89
90 return data
91 .try_into()
92 .map(AddressKind::Sapling)
93 .map_err(|_| ParseError::InvalidEncoding)
94 .map(|kind| ZcashAddress { net, kind });
95 }
96
97 if let Ok(parsed) = CheckedHrpstring::new::<Bech32m>(s) {
99 let net = match parsed.hrp().as_str() {
101 mainnet::HRP_TEX_ADDRESS => NetworkType::Main,
102 testnet::HRP_TEX_ADDRESS => NetworkType::Test,
103 regtest::HRP_TEX_ADDRESS => NetworkType::Regtest,
104 _ => {
106 return Err(ParseError::NotZcash);
107 }
108 };
109
110 let data = parsed.byte_iter().collect::<Vec<_>>();
111
112 return data
113 .try_into()
114 .map(AddressKind::Tex)
115 .map_err(|_| ParseError::InvalidEncoding)
116 .map(|kind| ZcashAddress { net, kind });
117 }
118
119 if let Ok(decoded) = bs58::decode(s).with_check(None).into_vec()
121 && decoded.len() >= 2
122 {
123 let (prefix, net) = match decoded[..2].try_into().unwrap() {
124 prefix @ (mainnet::B58_PUBKEY_ADDRESS_PREFIX
125 | mainnet::B58_SCRIPT_ADDRESS_PREFIX
126 | mainnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Main),
127 prefix @ (testnet::B58_PUBKEY_ADDRESS_PREFIX
128 | testnet::B58_SCRIPT_ADDRESS_PREFIX
129 | testnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Test),
130 _ => return Err(ParseError::NotZcash),
132 };
133
134 return match prefix {
135 mainnet::B58_SPROUT_ADDRESS_PREFIX | testnet::B58_SPROUT_ADDRESS_PREFIX => {
136 decoded[2..].try_into().map(AddressKind::Sprout)
137 }
138 mainnet::B58_PUBKEY_ADDRESS_PREFIX | testnet::B58_PUBKEY_ADDRESS_PREFIX => {
139 decoded[2..].try_into().map(AddressKind::P2pkh)
140 }
141 mainnet::B58_SCRIPT_ADDRESS_PREFIX | testnet::B58_SCRIPT_ADDRESS_PREFIX => {
142 decoded[2..].try_into().map(AddressKind::P2sh)
143 }
144 _ => unreachable!(),
145 }
146 .map_err(|_| ParseError::InvalidEncoding)
147 .map(|kind| ZcashAddress { kind, net });
148 };
149
150 Err(ParseError::NotZcash)
152 }
153}
154
155fn encode_bech32<Ck: Checksum>(hrp: &str, data: &[u8]) -> String {
156 bech32::encode::<Ck>(Hrp::parse_unchecked(hrp), data).expect("encoding is short enough")
157}
158
159fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String {
160 let mut bytes = Vec::with_capacity(2 + data.len());
161 bytes.extend_from_slice(&prefix);
162 bytes.extend_from_slice(data);
163 bs58::encode(bytes).with_check().into_string()
164}
165
166impl fmt::Display for ZcashAddress {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 let encoded = match &self.kind {
169 AddressKind::Sprout(data) => {
170 encode_b58(constants::b58_sprout_address_prefix(self.net), data)
171 }
172 AddressKind::Sapling(data) => {
173 encode_bech32::<Bech32>(constants::hrp_sapling_payment_address(self.net), data)
174 }
175 AddressKind::Unified(addr) => addr.encode(&self.net),
176 AddressKind::P2pkh(data) => {
177 encode_b58(constants::b58_pubkey_address_prefix(self.net), data)
178 }
179 AddressKind::P2sh(data) => {
180 encode_b58(constants::b58_script_address_prefix(self.net), data)
181 }
182 AddressKind::Tex(data) => {
183 encode_bech32::<Bech32m>(constants::hrp_tex_address(self.net), data)
184 }
185 };
186 write!(f, "{encoded}")
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use alloc::string::ToString;
193
194 use assert_matches::assert_matches;
195
196 use super::*;
197 use crate::kind::unified;
198 use zcash_protocol::consensus::NetworkType;
199
200 fn encoding(encoded: &str, decoded: ZcashAddress) {
201 assert_eq!(decoded.to_string(), encoded);
202 assert_eq!(encoded.parse(), Ok(decoded));
203 }
204
205 #[test]
206 fn sprout() {
207 encoding(
208 "zc8E5gYid86n4bo2Usdq1cpr7PpfoJGzttwBHEEgGhGkLUg7SPPVFNB2AkRFXZ7usfphup5426dt1buMmY3fkYeRrQGLa8y",
209 ZcashAddress {
210 net: NetworkType::Main,
211 kind: AddressKind::Sprout([0; 64]),
212 },
213 );
214 encoding(
215 "ztJ1EWLKcGwF2S4NA17pAJVdco8Sdkz4AQPxt1cLTEfNuyNswJJc2BbBqYrsRZsp31xbVZwhF7c7a2L9jsF3p3ZwRWpqqyS",
216 ZcashAddress {
217 net: NetworkType::Test,
218 kind: AddressKind::Sprout([0; 64]),
219 },
220 );
221 }
222
223 #[test]
224 fn sapling() {
225 encoding(
226 "zs1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpq6d8g",
227 ZcashAddress {
228 net: NetworkType::Main,
229 kind: AddressKind::Sapling([0; 43]),
230 },
231 );
232 encoding(
233 "ztestsapling1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqfhgwqu",
234 ZcashAddress {
235 net: NetworkType::Test,
236 kind: AddressKind::Sapling([0; 43]),
237 },
238 );
239 encoding(
240 "zregtestsapling1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqknpr3m",
241 ZcashAddress {
242 net: NetworkType::Regtest,
243 kind: AddressKind::Sapling([0; 43]),
244 },
245 );
246 }
247
248 #[test]
249 fn unified() {
250 encoding(
251 "u1qpatys4zruk99pg59gcscrt7y6akvl9vrhcfyhm9yxvxz7h87q6n8cgrzzpe9zru68uq39uhmlpp5uefxu0su5uqyqfe5zp3tycn0ecl",
252 ZcashAddress {
253 net: NetworkType::Main,
254 kind: AddressKind::Unified(unified::Address {
255 revision: unified::Revision::R0,
256 items: vec![unified::Uitem::Data(unified::address::Receiver::Sapling(
257 [0; 43],
258 ))],
259 }),
260 },
261 );
262 encoding(
263 "utest10c5kutapazdnf8ztl3pu43nkfsjx89fy3uuff8tsmxm6s86j37pe7uz94z5jhkl49pqe8yz75rlsaygexk6jpaxwx0esjr8wm5ut7d5s",
264 ZcashAddress {
265 net: NetworkType::Test,
266 kind: AddressKind::Unified(unified::Address {
267 revision: unified::Revision::R0,
268 items: vec![unified::Uitem::Data(unified::address::Receiver::Sapling(
269 [0; 43],
270 ))],
271 }),
272 },
273 );
274 encoding(
275 "uregtest15xk7vj4grjkay6mnfl93dhsflc2yeunhxwdh38rul0rq3dfhzzxgm5szjuvtqdha4t4p2q02ks0jgzrhjkrav70z9xlvq0plpcjkd5z3",
276 ZcashAddress {
277 net: NetworkType::Regtest,
278 kind: AddressKind::Unified(unified::Address {
279 revision: unified::Revision::R0,
280 items: vec![unified::Uitem::Data(unified::address::Receiver::Sapling(
281 [0; 43],
282 ))],
283 }),
284 },
285 );
286
287 let badencoded = "uinvalid1ck5navqwcng43gvsxwrxsplc22p7uzlcag6qfa0zh09e87efq6rq8wsnv25umqjjravw70rl994n5ueuhza2fghge5gl7zrl2qp6cwmp";
288 assert_eq!(
289 badencoded.parse::<ZcashAddress>(),
290 Err(ParseError::NotZcash)
291 );
292 }
293
294 #[test]
295 fn transparent() {
296 encoding(
297 "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs",
298 ZcashAddress {
299 net: NetworkType::Main,
300 kind: AddressKind::P2pkh([0; 20]),
301 },
302 );
303 encoding(
304 "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma",
305 ZcashAddress {
306 net: NetworkType::Test,
307 kind: AddressKind::P2pkh([0; 20]),
308 },
309 );
310 encoding(
311 "t3JZcvsuaXE6ygokL4XUiZSTrQBUoPYFnXJ",
312 ZcashAddress {
313 net: NetworkType::Main,
314 kind: AddressKind::P2sh([0; 20]),
315 },
316 );
317 encoding(
318 "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2",
319 ZcashAddress {
320 net: NetworkType::Test,
321 kind: AddressKind::P2sh([0; 20]),
322 },
323 );
324 }
325
326 #[test]
327 fn tex() {
328 let p2pkh_str = "t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC";
329 let tex_str = "tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte";
330
331 let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap();
333 assert_matches!(p2pkh_zaddr.net, NetworkType::Main);
334 if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind {
335 let tex_zaddr = ZcashAddress {
336 net: p2pkh_zaddr.net,
337 kind: AddressKind::Tex(zaddr_data),
338 };
339
340 assert_eq!(tex_zaddr.to_string(), tex_str);
341 } else {
342 panic!("Decoded address should have been a P2PKH address.");
343 }
344
345 let tex_zaddr: ZcashAddress = tex_str.parse().unwrap();
347 assert_matches!(tex_zaddr.net, NetworkType::Main);
348 if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind {
349 let p2pkh_zaddr = ZcashAddress {
350 net: tex_zaddr.net,
351 kind: AddressKind::P2pkh(zaddr_data),
352 };
353
354 assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str);
355 } else {
356 panic!("Decoded address should have been a TEX address.");
357 }
358 }
359
360 #[test]
361 fn tex_testnet() {
362 let p2pkh_str = "tm9ofD7kHR7AF8MsJomEzLqGcrLCBkD9gDj";
363 let tex_str = "textest1qyqszqgpqyqszqgpqyqszqgpqyqszqgpfcjgfy";
364
365 let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap();
367 assert_matches!(p2pkh_zaddr.net, NetworkType::Test);
368 if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind {
369 let tex_zaddr = ZcashAddress {
370 net: p2pkh_zaddr.net,
371 kind: AddressKind::Tex(zaddr_data),
372 };
373
374 assert_eq!(tex_zaddr.to_string(), tex_str);
375 } else {
376 panic!("Decoded address should have been a P2PKH address.");
377 }
378
379 let tex_zaddr: ZcashAddress = tex_str.parse().unwrap();
381 assert_matches!(tex_zaddr.net, NetworkType::Test);
382 if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind {
383 let p2pkh_zaddr = ZcashAddress {
384 net: tex_zaddr.net,
385 kind: AddressKind::P2pkh(zaddr_data),
386 };
387
388 assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str);
389 } else {
390 panic!("Decoded address should have been a TEX address.");
391 }
392 }
393
394 #[test]
395 fn whitespace() {
396 assert_eq!(
397 " t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse(),
398 Ok(ZcashAddress {
399 net: NetworkType::Main,
400 kind: AddressKind::P2pkh([0; 20])
401 }),
402 );
403 assert_eq!(
404 "t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs ".parse(),
405 Ok(ZcashAddress {
406 net: NetworkType::Main,
407 kind: AddressKind::P2pkh([0; 20])
408 }),
409 );
410 assert_eq!(
411 "something t1Hsc1LR8yKnbbe3twRp88p6vFfC5t7DLbs".parse::<ZcashAddress>(),
412 Err(ParseError::NotZcash),
413 );
414 }
415}