bech32/primitives/
hrp.rs

1// SPDX-License-Identifier: MIT
2
3//! Provides an [`Hrp`] type that represents the human-readable part of a bech32 encoded string.
4//!
5//! > The human-readable part, which is intended to convey the type of data, or anything else that
6//! > is relevant to the reader. This part MUST contain 1 to 83 US-ASCII characters, with each
7//! > character having a value in the range [33-126]. HRP validity may be further restricted by
8//! > specific applications.
9//!
10//! ref: [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#user-content-Bech32)
11
12#[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
19/// Maximum length of the human-readable part, as defined by BIP-173.
20const MAX_HRP_LEN: usize = 83;
21
22// Defines HRP constants for the different bitcoin networks.
23// You can also access these at `crate::hrp::BC` etc.
24#[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    /// The human-readable part used by the Bitcoin mainnet network.
46    pub const BC 2 [98, 99, 0, 0];
47}
48define_hrp_const! {
49    /// The human-readable part used by the Bitcoin testnet networks (testnet, signet).
50    pub const TB 2 [116, 98, 0, 0];
51}
52define_hrp_const! {
53    /// The human-readable part used when running a Bitcoin regtest network.
54    pub const BCRT 4 [98, 99, 114, 116];
55}
56
57/// The human-readable part (human readable prefix before the '1' separator).
58#[derive(Clone, Copy, Debug)]
59pub struct Hrp {
60    /// ASCII byte values, guaranteed not to be mixed-case.
61    buf: [u8; MAX_HRP_LEN],
62    /// Number of characters currently stored in this HRP.
63    size: usize,
64}
65
66impl Hrp {
67    /// Parses the human-readable part checking it is valid as defined by [BIP-173].
68    ///
69    /// This does _not_ check that the `hrp` is an in-use HRP within Bitcoin (eg, "bc"), rather it
70    /// checks that the HRP string is valid as per the specification in [BIP-173]:
71    ///
72    /// > The human-readable part, which is intended to convey the type of data, or anything else that
73    /// > is relevant to the reader. This part MUST contain 1 to 83 US-ASCII characters, with each
74    /// > character having a value in the range [33-126]. HRP validity may be further restricted by
75    /// > specific applications.
76    ///
77    /// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki>
78    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; // cast OK as we just checked that c is an ASCII value
97
98            // Valid subset of ASCII
99            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    /// Parses the human-readable part (see [`Hrp::parse`] for full docs).
123    ///
124    /// Does not check that `hrp` is valid according to BIP-173 but does check for valid ASCII
125    /// values, replacing any invalid characters with `X`.
126    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        // Funky code so we can be const.
132        while i < hrp.len() {
133            let mut b = hrp_bytes[i];
134            // Valid subset of ASCII
135            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    /// Returns this human-readable part as a lowercase string.
147    #[cfg(feature = "alloc")]
148    #[inline]
149    pub fn to_lowercase(&self) -> String { self.lowercase_char_iter().collect() }
150
151    /// Returns this human-readable part as bytes.
152    #[inline]
153    pub fn as_bytes(&self) -> &[u8] { &self.buf[..self.size] }
154
155    /// Returns this human-readable part as str.
156    #[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    /// Creates a byte iterator over the ASCII byte values (ASCII characters) of this HRP.
162    ///
163    /// If an uppercase HRP was parsed during object construction then this iterator will yield
164    /// uppercase ASCII `char`s. For lowercase bytes see [`Self::lowercase_byte_iter`]
165    #[inline]
166    pub fn byte_iter(&self) -> ByteIter { ByteIter { iter: self.buf[..self.size].iter() } }
167
168    /// Creates a character iterator over the ASCII characters of this HRP.
169    ///
170    /// If an uppercase HRP was parsed during object construction then this iterator will yield
171    /// uppercase ASCII `char`s. For lowercase bytes see [`Self::lowercase_char_iter`].
172    #[inline]
173    pub fn char_iter(&self) -> CharIter { CharIter { iter: self.byte_iter() } }
174
175    /// Creates a lowercase iterator over the byte values (ASCII characters) of this HRP.
176    #[inline]
177    pub fn lowercase_byte_iter(&self) -> LowercaseByteIter {
178        LowercaseByteIter { iter: self.byte_iter() }
179    }
180
181    /// Creates a lowercase character iterator over the ASCII characters of this HRP.
182    #[inline]
183    pub fn lowercase_char_iter(&self) -> LowercaseCharIter {
184        LowercaseCharIter { iter: self.lowercase_byte_iter() }
185    }
186
187    /// Returns the length (number of characters) of the human-readable part.
188    ///
189    /// Guaranteed to be between 1 and 83 inclusive.
190    #[inline]
191    #[allow(clippy::len_without_is_empty)] // HRP is never empty.
192    pub fn len(&self) -> usize { self.size }
193
194    /// Returns `true` if this HRP is valid according to the bips.
195    ///
196    /// [BIP-173] states that the HRP must be either "bc" or "tb".
197    ///
198    /// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#user-content-Segwit_address_format>
199    #[inline]
200    pub fn is_valid_segwit(&self) -> bool {
201        self.is_valid_on_mainnet() || self.is_valid_on_testnet()
202    }
203
204    /// Returns `true` if this HRP is valid on the Bitcoin network i.e., HRP is "bc".
205    #[inline]
206    pub fn is_valid_on_mainnet(&self) -> bool { *self == self::BC }
207
208    /// Returns `true` if this HRP is valid on the Bitcoin testnet network i.e., HRP is "tb".
209    #[inline]
210    pub fn is_valid_on_testnet(&self) -> bool { *self == self::TB }
211
212    /// Returns `true` if this HRP is valid on the Bitcoin signet network i.e., HRP is "tb".
213    #[inline]
214    pub fn is_valid_on_signet(&self) -> bool { *self == self::TB }
215
216    /// Returns `true` if this HRP is valid on the Bitcoin regtest network i.e., HRP is "bcrt".
217    #[inline]
218    pub fn is_valid_on_regtest(&self) -> bool { *self == self::BCRT }
219}
220
221/// Displays the human-readable part.
222///
223/// If an uppercase HRP was parsed during object construction then the returned string will be
224/// in uppercase also. For a lowercase string see `Self::to_lowercase`.
225impl 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
234/// Case insensitive comparison.
235impl 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
242/// Case insensitive comparison.
243impl PartialOrd for Hrp {
244    #[inline]
245    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
246}
247
248/// Case insensitive comparison.
249impl 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
263/// Iterator over bytes (ASCII values) of the human-readable part.
264///
265/// ASCII byte values as they were initially parsed (i.e., in the original case).
266pub 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
290/// Iterator over ASCII characters of the human-readable part.
291///
292/// ASCII `char`s as they were initially parsed (i.e., in the original case).
293pub 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
317/// Iterator over lowercase bytes (ASCII characters) of the human-readable part.
318pub 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
346/// Iterator over lowercase ASCII characters of the human-readable part.
347pub 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/// Errors encountered while checking the human-readable part as defined by [BIP-173].
374///
375/// [BIP-173]: <https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#user-content-Bech32>
376#[derive(Clone, Debug, PartialEq, Eq)]
377#[non_exhaustive]
378pub enum Error {
379    /// The human-readable part is too long.
380    TooLong(usize),
381    /// The human-readable part is empty.
382    Empty,
383    /// Found a non-ASCII character.
384    NonAsciiChar(char),
385    /// Byte value not within acceptable US-ASCII range.
386    InvalidAsciiByte(u8),
387    /// The human-readable part cannot mix upper and lower case.
388    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                    // Test ByteIter forwards.
466                    for (got, want) in hrp.byte_iter().zip($hrp.bytes()) {
467                        assert_eq!(got, want);
468                    }
469
470                    // Test ByteIter backwards.
471                    for (got, want) in hrp.byte_iter().rev().zip($hrp.bytes().rev()) {
472                        assert_eq!(got, want);
473                    }
474
475                    // Test exact sized works.
476                    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                    // Test CharIter forwards.
484                    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                    // Test CharIter backwards.
492                    for (got, want) in hrp.char_iter().rev().zip($hrp.chars().rev()) {
493                        assert_eq!(got, want);
494                    }
495
496                    // Test LowercaseCharIter forwards (implicitly tests LowercaseByteIter)
497                    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                    // Test LowercaseCharIter backwards (implicitly tests LowercaseByteIter)
502                    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}