1#[cfg(feature = "alloc")]
13use alloc::string::String;
14use core::cmp::Ordering;
15use core::fmt::{self, Write};
16use core::iter::FusedIterator;
17use core::{slice, str};
18
19const MAX_HRP_LEN: usize = 83;
21
22#[rustfmt::skip]
25macro_rules! define_hrp_const {
26 (
27 #[$doc:meta]
28 pub const $name:ident $size:literal $v:expr;
29 ) => {
30 #[$doc]
31 pub const $name: Hrp = Hrp { buf: [
32 $v[0], $v[1], $v[2], $v[3],
33 0, 0, 0, 0, 0, 0, 0, 0, 0,
34 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
37 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
39 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
40 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41 ], size: $size };
42 };
43}
44define_hrp_const! {
45 pub const BC 2 [98, 99, 0, 0];
47}
48define_hrp_const! {
49 pub const TB 2 [116, 98, 0, 0];
51}
52define_hrp_const! {
53 pub const BCRT 4 [98, 99, 114, 116];
55}
56
57#[derive(Clone, Copy, Debug)]
59pub struct Hrp {
60 buf: [u8; MAX_HRP_LEN],
62 size: usize,
64}
65
66impl Hrp {
67 pub fn parse(hrp: &str) -> Result<Self, Error> {
79 use Error::*;
80
81 let mut new = Hrp { buf: [0_u8; MAX_HRP_LEN], size: 0 };
82
83 if hrp.is_empty() {
84 return Err(Empty);
85 }
86 if hrp.len() > MAX_HRP_LEN {
87 return Err(TooLong(hrp.len()));
88 }
89
90 let mut has_lower: bool = false;
91 let mut has_upper: bool = false;
92 for (i, c) in hrp.chars().enumerate() {
93 if !c.is_ascii() {
94 return Err(NonAsciiChar(c));
95 }
96 let b = c as u8; if !(33..=126).contains(&b) {
100 return Err(InvalidAsciiByte(b));
101 }
102
103 if b.is_ascii_lowercase() {
104 if has_upper {
105 return Err(MixedCase);
106 }
107 has_lower = true;
108 } else if b.is_ascii_uppercase() {
109 if has_lower {
110 return Err(MixedCase);
111 }
112 has_upper = true;
113 };
114
115 new.buf[i] = b;
116 new.size += 1;
117 }
118
119 Ok(new)
120 }
121
122 pub const fn parse_unchecked(hrp: &str) -> Self {
127 let mut new = Hrp { buf: [0_u8; MAX_HRP_LEN], size: 0 };
128 let hrp_bytes = hrp.as_bytes();
129
130 let mut i = 0;
131 while i < hrp.len() {
133 let mut b = hrp_bytes[i];
134 if b < 33 || b > 126 {
136 b = b'X';
137 }
138
139 new.buf[i] = b;
140 new.size += 1;
141 i += 1;
142 }
143 new
144 }
145
146 #[cfg(feature = "alloc")]
148 #[inline]
149 pub fn to_lowercase(&self) -> String { self.lowercase_char_iter().collect() }
150
151 #[inline]
153 pub fn as_bytes(&self) -> &[u8] { &self.buf[..self.size] }
154
155 #[inline]
157 pub fn as_str(&self) -> &str {
158 str::from_utf8(&self.buf[..self.size]).expect("we only store ASCII bytes")
159 }
160
161 #[inline]
166 pub fn byte_iter(&self) -> ByteIter { ByteIter { iter: self.buf[..self.size].iter() } }
167
168 #[inline]
173 pub fn char_iter(&self) -> CharIter { CharIter { iter: self.byte_iter() } }
174
175 #[inline]
177 pub fn lowercase_byte_iter(&self) -> LowercaseByteIter {
178 LowercaseByteIter { iter: self.byte_iter() }
179 }
180
181 #[inline]
183 pub fn lowercase_char_iter(&self) -> LowercaseCharIter {
184 LowercaseCharIter { iter: self.lowercase_byte_iter() }
185 }
186
187 #[inline]
191 #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.size }
193
194 #[inline]
200 pub fn is_valid_segwit(&self) -> bool {
201 self.is_valid_on_mainnet() || self.is_valid_on_testnet()
202 }
203
204 #[inline]
206 pub fn is_valid_on_mainnet(&self) -> bool { *self == self::BC }
207
208 #[inline]
210 pub fn is_valid_on_testnet(&self) -> bool { *self == self::TB }
211
212 #[inline]
214 pub fn is_valid_on_signet(&self) -> bool { *self == self::TB }
215
216 #[inline]
218 pub fn is_valid_on_regtest(&self) -> bool { *self == self::BCRT }
219}
220
221impl fmt::Display for Hrp {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 for c in self.char_iter() {
228 f.write_char(c)?;
229 }
230 Ok(())
231 }
232}
233
234impl Ord for Hrp {
236 #[inline]
237 fn cmp(&self, other: &Self) -> Ordering {
238 self.lowercase_byte_iter().cmp(other.lowercase_byte_iter())
239 }
240}
241
242impl PartialOrd for Hrp {
244 #[inline]
245 fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
246}
247
248impl PartialEq for Hrp {
250 #[inline]
251 fn eq(&self, other: &Self) -> bool {
252 self.lowercase_byte_iter().eq(other.lowercase_byte_iter())
253 }
254}
255
256impl Eq for Hrp {}
257
258impl core::hash::Hash for Hrp {
259 #[inline]
260 fn hash<H: core::hash::Hasher>(&self, h: &mut H) { self.buf.hash(h) }
261}
262
263pub struct ByteIter<'b> {
267 iter: slice::Iter<'b, u8>,
268}
269
270impl<'b> Iterator for ByteIter<'b> {
271 type Item = u8;
272 #[inline]
273 fn next(&mut self) -> Option<u8> { self.iter.next().copied() }
274 #[inline]
275 fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
276}
277
278impl<'b> ExactSizeIterator for ByteIter<'b> {
279 #[inline]
280 fn len(&self) -> usize { self.iter.len() }
281}
282
283impl<'b> DoubleEndedIterator for ByteIter<'b> {
284 #[inline]
285 fn next_back(&mut self) -> Option<Self::Item> { self.iter.next_back().copied() }
286}
287
288impl<'b> FusedIterator for ByteIter<'b> {}
289
290pub struct CharIter<'b> {
294 iter: ByteIter<'b>,
295}
296
297impl<'b> Iterator for CharIter<'b> {
298 type Item = char;
299 #[inline]
300 fn next(&mut self) -> Option<char> { self.iter.next().map(Into::into) }
301 #[inline]
302 fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
303}
304
305impl<'b> ExactSizeIterator for CharIter<'b> {
306 #[inline]
307 fn len(&self) -> usize { self.iter.len() }
308}
309
310impl<'b> DoubleEndedIterator for CharIter<'b> {
311 #[inline]
312 fn next_back(&mut self) -> Option<Self::Item> { self.iter.next_back().map(Into::into) }
313}
314
315impl<'b> FusedIterator for CharIter<'b> {}
316
317pub struct LowercaseByteIter<'b> {
319 iter: ByteIter<'b>,
320}
321
322impl<'b> Iterator for LowercaseByteIter<'b> {
323 type Item = u8;
324 #[inline]
325 fn next(&mut self) -> Option<u8> {
326 self.iter.next().map(|b| if is_ascii_uppercase(b) { b | 32 } else { b })
327 }
328 #[inline]
329 fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
330}
331
332impl<'b> ExactSizeIterator for LowercaseByteIter<'b> {
333 #[inline]
334 fn len(&self) -> usize { self.iter.len() }
335}
336
337impl<'b> DoubleEndedIterator for LowercaseByteIter<'b> {
338 #[inline]
339 fn next_back(&mut self) -> Option<Self::Item> {
340 self.iter.next_back().map(|b| if is_ascii_uppercase(b) { b | 32 } else { b })
341 }
342}
343
344impl<'b> FusedIterator for LowercaseByteIter<'b> {}
345
346pub struct LowercaseCharIter<'b> {
348 iter: LowercaseByteIter<'b>,
349}
350
351impl<'b> Iterator for LowercaseCharIter<'b> {
352 type Item = char;
353 #[inline]
354 fn next(&mut self) -> Option<char> { self.iter.next().map(Into::into) }
355 #[inline]
356 fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
357}
358
359impl<'b> ExactSizeIterator for LowercaseCharIter<'b> {
360 #[inline]
361 fn len(&self) -> usize { self.iter.len() }
362}
363
364impl<'b> DoubleEndedIterator for LowercaseCharIter<'b> {
365 #[inline]
366 fn next_back(&mut self) -> Option<Self::Item> { self.iter.next_back().map(Into::into) }
367}
368
369impl<'b> FusedIterator for LowercaseCharIter<'b> {}
370
371fn is_ascii_uppercase(b: u8) -> bool { (65..=90).contains(&b) }
372
373#[derive(Clone, Debug, PartialEq, Eq)]
377#[non_exhaustive]
378pub enum Error {
379 TooLong(usize),
381 Empty,
383 NonAsciiChar(char),
385 InvalidAsciiByte(u8),
387 MixedCase,
389}
390
391impl fmt::Display for Error {
392 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
393 use Error::*;
394
395 match *self {
396 TooLong(len) => write!(f, "hrp is too long, found {} characters, must be <= 126", len),
397 Empty => write!(f, "hrp is empty, must have at least 1 character"),
398 NonAsciiChar(c) => write!(f, "found non-ASCII character: {}", c),
399 InvalidAsciiByte(b) => write!(f, "byte value is not valid US-ASCII: \'{:x}\'", b),
400 MixedCase => write!(f, "hrp cannot mix upper and lower case"),
401 }
402 }
403}
404
405#[cfg(feature = "std")]
406impl std::error::Error for Error {
407 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
408 use Error::*;
409
410 match *self {
411 TooLong(_) | Empty | NonAsciiChar(_) | InvalidAsciiByte(_) | MixedCase => None,
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 macro_rules! check_parse_ok {
421 ($($test_name:ident, $hrp:literal);* $(;)?) => {
422 $(
423 #[test]
424 fn $test_name() {
425 assert!(Hrp::parse($hrp).is_ok());
426 }
427 )*
428 }
429 }
430 check_parse_ok! {
431 parse_ok_0, "a";
432 parse_ok_1, "A";
433 parse_ok_2, "abcdefg";
434 parse_ok_3, "ABCDEFG";
435 parse_ok_4, "abc123def";
436 parse_ok_5, "ABC123DEF";
437 parse_ok_6, "!\"#$%&'()*+,-./";
438 parse_ok_7, "1234567890";
439 }
440
441 macro_rules! check_parse_err {
442 ($($test_name:ident, $hrp:literal);* $(;)?) => {
443 $(
444 #[test]
445 fn $test_name() {
446 assert!(Hrp::parse($hrp).is_err());
447 }
448 )*
449 }
450 }
451 check_parse_err! {
452 parse_err_0, "has-capitals-aAbB";
453 parse_err_1, "has-value-out-of-range-∈∈∈∈∈∈∈∈";
454 parse_err_2, "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong";
455 parse_err_3, "has spaces in it";
456 }
457
458 macro_rules! check_iter {
459 ($($test_name:ident, $hrp:literal, $len:literal);* $(;)?) => {
460 $(
461 #[test]
462 fn $test_name() {
463 let hrp = Hrp::parse($hrp).expect(&format!("failed to parse hrp {}", $hrp));
464
465 for (got, want) in hrp.byte_iter().zip($hrp.bytes()) {
467 assert_eq!(got, want);
468 }
469
470 for (got, want) in hrp.byte_iter().rev().zip($hrp.bytes().rev()) {
472 assert_eq!(got, want);
473 }
474
475 let mut iter = hrp.byte_iter();
477 for i in 0..$len {
478 assert_eq!(iter.len(), $len - i);
479 let _ = iter.next();
480 }
481 assert!(iter.next().is_none());
482
483 let iter = hrp.char_iter();
485 assert_eq!($hrp.to_string(), iter.collect::<String>());
486
487 for (got, want) in hrp.char_iter().zip($hrp.chars()) {
488 assert_eq!(got, want);
489 }
490
491 for (got, want) in hrp.char_iter().rev().zip($hrp.chars().rev()) {
493 assert_eq!(got, want);
494 }
495
496 for (got, want) in hrp.lowercase_char_iter().zip($hrp.chars().map(|c| c.to_ascii_lowercase())) {
498 assert_eq!(got, want);
499 }
500
501 for (got, want) in hrp.lowercase_char_iter().rev().zip($hrp.chars().rev().map(|c| c.to_ascii_lowercase())) {
503 assert_eq!(got, want);
504 }
505 }
506 )*
507 }
508 }
509 check_iter! {
510 char_0, "abc", 3;
511 char_1, "ABC", 3;
512 char_2, "abc123", 6;
513 char_3, "ABC123", 6;
514 char_4, "abc123def", 9;
515 char_5, "ABC123DEF", 9;
516 }
517
518 #[cfg(feature = "alloc")]
519 #[test]
520 fn hrp_consts() {
521 use crate::primitives::hrp::{BC, BCRT, TB};
522 assert_eq!(BC, Hrp::parse_unchecked("bc"));
523 assert_eq!(TB, Hrp::parse_unchecked("tb"));
524 assert_eq!(BCRT, Hrp::parse_unchecked("bcrt"));
525 }
526
527 #[test]
528 fn as_str() {
529 let s = "arbitraryhrp";
530 let hrp = Hrp::parse_unchecked(s);
531 assert_eq!(hrp.as_str(), s);
532 }
533
534 #[test]
535 fn as_bytes() {
536 let s = "arbitraryhrp";
537 let hrp = Hrp::parse_unchecked(s);
538 assert_eq!(hrp.as_bytes(), s.as_bytes());
539 }
540}