1#![cfg_attr(
33 feature = "std",
34 doc = "
35# Examples
36```
37use bech32::{self, FromBase32, ToBase32, Variant};
38let encoded = bech32::encode(\"bech32\", vec![0x00, 0x01, 0x02].to_base32(), Variant::Bech32).unwrap();
39assert_eq!(encoded, \"bech32:qqqsy7wwsnnhh\".to_string());
40let (hrp, data, variant) = bech32::decode(&encoded).unwrap();
41assert_eq!(hrp, \"bech32\");
42assert_eq!(Vec::<u8>::from_base32(&data).unwrap(), vec![0x00, 0x01, 0x02]);
43assert_eq!(variant, Variant::Bech32);
44```
45"
46)]
47#![allow(unknown_lints)]
51#![allow(bare_trait_objects)]
52#![deny(missing_docs)]
53#![deny(non_upper_case_globals)]
54#![deny(non_camel_case_types)]
55#![deny(non_snake_case)]
56#![deny(unused_mut)]
57#![cfg_attr(feature = "strict", deny(warnings))]
58#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]
59
60#[cfg(all(not(feature = "std"), not(test)))]
61extern crate alloc;
62
63#[cfg(any(test, feature = "std"))]
64extern crate core;
65
66#[cfg(all(not(feature = "std"), not(test)))]
67use alloc::{string::String, vec::Vec};
68
69#[cfg(all(not(feature = "std"), not(test)))]
70use alloc::borrow::Cow;
71#[cfg(any(feature = "std", test))]
72use std::borrow::Cow;
73
74use core::fmt;
75
76#[derive(PartialEq, Eq, Debug, Copy, Clone, Default, PartialOrd, Ord, Hash)]
78#[allow(non_camel_case_types)]
79pub struct u5(u8);
80
81impl u5 {
82 pub fn try_from_u8(value: u8) -> Result<u5, Error> {
84 if value > 31 {
85 Err(Error::InvalidData(value))
86 } else {
87 Ok(u5(value))
88 }
89 }
90
91 pub fn to_u8(self) -> u8 {
93 self.0
94 }
95
96 pub fn to_char(self) -> char {
98 CHARSET[self.to_u8() as usize]
99 }
100}
101
102impl From<u5> for u8 {
103 fn from(v: u5) -> u8 {
104 v.0
105 }
106}
107
108impl AsRef<u8> for u5 {
109 fn as_ref(&self) -> &u8 {
110 &self.0
111 }
112}
113
114pub trait WriteBase32 {
116 type Err: fmt::Debug;
118
119 fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> {
121 for b in data {
122 self.write_u5(*b)?;
123 }
124 Ok(())
125 }
126
127 fn write_u5(&mut self, data: u5) -> Result<(), Self::Err>;
129}
130
131pub struct Bech32Writer<'a> {
134 formatter: &'a mut fmt::Write,
135 chk: u64,
136 variant: Variant,
137 finalized: bool,
138}
139
140impl<'a> Bech32Writer<'a> {
141 pub fn new(
146 hrp: &str,
147 variant: Variant,
148 fmt: &'a mut fmt::Write,
149 ) -> Result<Bech32Writer<'a>, fmt::Error> {
150 let mut writer = Bech32Writer {
151 formatter: fmt,
152 chk: 1,
153 variant,
154 finalized: false,
155 };
156
157 writer.formatter.write_str(hrp)?;
158 writer.formatter.write_char(SEP)?;
159
160 for b in hrp.bytes() {
162 writer.polymod_step(u5(b & 0x1f));
163 }
164 writer.polymod_step(u5(0));
165
166 Ok(writer)
167 }
168
169 fn polymod_step(&mut self, v: u5) {
170 let b = (self.chk >> 35) as u8;
171 self.chk = (self.chk & 0x07ffffffff) << 5 ^ (u64::from(*v.as_ref()));
172
173 for (i, item) in GEN.iter() {
174 if (b & i) > 0 {
175 self.chk ^= item;
176 }
177 }
178 }
179
180 pub fn finalize(mut self) -> fmt::Result {
182 self.inner_finalize()?;
183 self.finalized = true;
184 Ok(())
185 }
186
187 fn inner_finalize(&mut self) -> fmt::Result {
188 for _ in 0..8 {
190 self.polymod_step(u5(0))
191 }
192
193 let plm: u64 = self.chk ^ self.variant.constant();
194
195 for p in 0..8 {
196 self.formatter
197 .write_char(u5(((plm >> (5 * (7 - p))) & 0x1f) as u8).to_char())?;
198 }
199
200 Ok(())
201 }
202}
203impl<'a> WriteBase32 for Bech32Writer<'a> {
204 type Err = fmt::Error;
205
206 fn write_u5(&mut self, data: u5) -> fmt::Result {
208 self.polymod_step(data);
209 self.formatter.write_char(data.to_char())
210 }
211}
212
213impl<'a> Drop for Bech32Writer<'a> {
214 fn drop(&mut self) {
215 if !self.finalized {
216 if let Err(e) = self.inner_finalize() {
217 eprintln!("Unhandled error writing the checksum on drop: {}", e);
219 }
220 }
221 }
222}
223
224pub trait FromBase32: Sized {
227 type Err;
229
230 fn from_base32(b32: &[u5]) -> Result<Self, Self::Err>;
232}
233
234impl WriteBase32 for Vec<u5> {
235 type Err = ();
236
237 fn write(&mut self, data: &[u5]) -> Result<(), Self::Err> {
238 self.extend_from_slice(data);
239 Ok(())
240 }
241
242 fn write_u5(&mut self, data: u5) -> Result<(), Self::Err> {
243 self.push(data);
244 Ok(())
245 }
246}
247
248impl FromBase32 for Vec<u8> {
249 type Err = Error;
250
251 fn from_base32(b32: &[u5]) -> Result<Self, Self::Err> {
254 convert_bits(b32, 5, 8, false)
255 }
256}
257
258pub trait ToBase32 {
260 fn to_base32(&self) -> Vec<u5> {
262 let mut vec = Vec::new();
263 self.write_base32(&mut vec).unwrap();
264 vec
265 }
266
267 fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err>;
270}
271
272pub trait Base32Len: ToBase32 {
274 fn base32_len(&self) -> usize;
276}
277
278impl<T: AsRef<[u8]>> ToBase32 for T {
279 fn write_base32<W: WriteBase32>(&self, writer: &mut W) -> Result<(), <W as WriteBase32>::Err> {
280 let mut buffer_bits = 0u32;
282 let mut buffer: u8 = 0;
286
287 for &b in self.as_ref() {
288 if buffer_bits >= 5 {
292 writer.write_u5(u5((buffer & 0b1111_1000) >> 3))?;
293 buffer <<= 5;
294 buffer_bits -= 5;
295 }
296
297 let from_buffer = buffer >> 3;
300 let from_byte = b >> (3 + buffer_bits); writer.write_u5(u5(from_buffer | from_byte))?;
303 buffer = b << (5 - buffer_bits);
304 buffer_bits += 3;
305 }
306
307 if buffer_bits >= 5 {
309 writer.write_u5(u5((buffer & 0b1111_1000) >> 3))?;
310 buffer <<= 5;
311 buffer_bits -= 5;
312 }
313
314 if buffer_bits != 0 {
315 writer.write_u5(u5(buffer >> 3))?;
316 }
317
318 Ok(())
319 }
320}
321
322impl<T: AsRef<[u8]>> Base32Len for T {
323 fn base32_len(&self) -> usize {
324 let bits = self.as_ref().len() * 8;
325 if bits % 5 == 0 {
326 bits / 5
327 } else {
328 bits / 5 + 1
329 }
330 }
331}
332
333pub trait CheckBase32<T: AsRef<[u5]>> {
336 type Err;
338
339 fn check_base32(self) -> Result<T, Self::Err>;
341}
342
343impl<'f, T: AsRef<[u8]>> CheckBase32<Vec<u5>> for T {
344 type Err = Error;
345
346 fn check_base32(self) -> Result<Vec<u5>, Self::Err> {
347 self.as_ref()
348 .iter()
349 .map(|x| u5::try_from_u8(*x))
350 .collect::<Result<Vec<u5>, Error>>()
351 }
352}
353
354#[derive(Clone, Copy, PartialEq, Eq)]
355enum Case {
356 Upper,
357 Lower,
358 None,
359}
360
361fn check_hrp(hrp: &str) -> Result<Case, Error> {
368 if hrp.is_empty() || hrp.len() > 83 {
369 return Err(Error::InvalidLength);
370 }
371
372 let mut has_lower: bool = false;
373 let mut has_upper: bool = false;
374 for b in hrp.bytes() {
375 if b < 33 || b > 126 {
377 return Err(Error::InvalidChar(b as char));
378 }
379
380 if b >= b'a' && b <= b'z' {
381 has_lower = true;
382 } else if b >= b'A' && b <= b'Z' {
383 has_upper = true;
384 };
385
386 if has_lower && has_upper {
387 return Err(Error::MixedCase);
388 }
389 }
390
391 Ok(match (has_upper, has_lower) {
392 (true, false) => Case::Upper,
393 (false, true) => Case::Lower,
394 (false, false) => Case::None,
395 (true, true) => unreachable!(),
396 })
397}
398
399pub fn encode_to_fmt<T: AsRef<[u5]>>(
407 fmt: &mut fmt::Write,
408 hrp: &str,
409 data: T,
410 variant: Variant,
411) -> Result<fmt::Result, Error> {
412 let hrp_lower = match check_hrp(&hrp)? {
413 Case::Upper => Cow::Owned(hrp.to_lowercase()),
414 Case::Lower | Case::None => Cow::Borrowed(hrp),
415 };
416
417 match Bech32Writer::new(&hrp_lower, variant, fmt) {
418 Ok(mut writer) => {
419 Ok(writer.write(data.as_ref()).and_then(|_| {
420 writer.finalize()
422 }))
423 }
424 Err(e) => Ok(Err(e)),
425 }
426}
427
428#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
430pub enum Variant {
431 Bech32,
433 Bech32m,
435}
436
437const BECH32_CONST: u64 = 1;
438const BECH32M_CONST: u64 = 0x2bc830a3;
439
440impl Variant {
441 fn from_remainder(c: u64) -> Option<Self> {
443 match c {
444 BECH32_CONST => Some(Variant::Bech32),
445 BECH32M_CONST => Some(Variant::Bech32m),
446 _ => None,
447 }
448 }
449
450 fn constant(self) -> u64 {
451 match self {
452 Variant::Bech32 => BECH32_CONST,
453 Variant::Bech32m => BECH32M_CONST,
454 }
455 }
456}
457
458pub fn encode<T: AsRef<[u5]>>(hrp: &str, data: T, variant: Variant) -> Result<String, Error> {
465 let mut buf = String::new();
466 encode_to_fmt(&mut buf, hrp, data, variant)?.unwrap();
467 Ok(buf)
468}
469
470pub fn decode(s: &str) -> Result<(String, Vec<u5>, Variant), Error> {
474 if s.len() < 8 {
476 return Err(Error::InvalidLength);
477 }
478
479 let (raw_hrp, raw_data) = match s.rfind(SEP) {
481 None => return Err(Error::MissingSeparator),
482 Some(sep) => {
483 let (hrp, data) = s.split_at(sep);
484 (hrp, &data[1..])
485 }
486 };
487 if raw_data.len() < 6 {
488 return Err(Error::InvalidLength);
489 }
490
491 let mut case = check_hrp(&raw_hrp)?;
492 let hrp_lower = match case {
493 Case::Upper => raw_hrp.to_lowercase(),
494 Case::Lower | Case::None => String::from(raw_hrp),
496 };
497
498 let mut data = raw_data
500 .chars()
501 .map(|c| {
502 if !c.is_ascii() {
506 return Err(Error::InvalidChar(c));
507 }
508
509 if c.is_lowercase() {
510 match case {
511 Case::Upper => return Err(Error::MixedCase),
512 Case::None => case = Case::Lower,
513 Case::Lower => {}
514 }
515 } else if c.is_uppercase() {
516 match case {
517 Case::Lower => return Err(Error::MixedCase),
518 Case::None => case = Case::Upper,
519 Case::Upper => {}
520 }
521 }
522
523 let num_value = CHARSET_REV[c as usize];
525
526 if num_value > 31 || num_value < 0 {
527 return Err(Error::InvalidChar(c));
528 }
529
530 Ok(u5::try_from_u8(num_value as u8).expect("range checked above, num_value <= 31"))
531 })
532 .collect::<Result<Vec<u5>, Error>>()?;
533
534 match verify_checksum(&hrp_lower.as_bytes(), &data) {
536 Some(variant) => {
537 let dbl: usize = data.len();
539 data.truncate(dbl - 8);
540
541 Ok((hrp_lower, data, variant))
542 }
543 None => Err(Error::InvalidChecksum),
544 }
545}
546
547fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option<Variant> {
548 let mut exp = hrp_expand(hrp);
549 exp.extend_from_slice(data);
550 Variant::from_remainder(polymod(&exp))
551}
552
553fn hrp_expand(hrp: &[u8]) -> Vec<u5> {
554 let mut v: Vec<u5> = Vec::new();
555 for b in hrp {
556 v.push(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 7"));
557 }
558 v.push(u5::try_from_u8(0).unwrap());
559 v
560}
561
562fn polymod(values: &[u5]) -> u64 {
563 let mut chk: u64 = 1;
564 let mut b: u8;
565 for v in values {
566 b = (chk >> 35) as u8;
567 chk = (chk & 0x07ffffffff) << 5 ^ (u64::from(*v.as_ref()));
568
569 for (i, item) in GEN.iter() {
570 if (b & i) > 0 {
571 chk ^= item;
572 }
573 }
574 }
575 chk
576}
577
578const SEP: char = ':';
580
581const CHARSET: [char; 32] = [
583 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', 'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', ];
588
589const CHARSET_REV: [i8; 128] = [
591 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
592 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
593 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23,
594 -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29,
595 -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1,
596 -1, -1, -1, -1,
597];
598
599const GEN: [(u8, u64); 5] = [
601 (0x01, 0x98f2bc8e61),
602 (0x02, 0x79b76d99e2),
603 (0x04, 0xf33e5fb3c4),
604 (0x08, 0xae2eabe2a8),
605 (0x10, 0x1e4f43e470),
606];
607
608#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
610pub enum Error {
611 MissingSeparator,
613 InvalidChecksum,
615 InvalidLength,
617 InvalidChar(char),
619 InvalidData(u8),
621 InvalidPadding,
623 MixedCase,
625}
626
627impl fmt::Display for Error {
628 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
629 match *self {
630 Error::MissingSeparator => write!(f, "missing human-readable separator, \"{}\"", SEP),
631 Error::InvalidChecksum => write!(f, "invalid checksum"),
632 Error::InvalidLength => write!(f, "invalid length"),
633 Error::InvalidChar(n) => write!(f, "invalid character (code={})", n),
634 Error::InvalidData(n) => write!(f, "invalid data point ({})", n),
635 Error::InvalidPadding => write!(f, "invalid padding"),
636 Error::MixedCase => write!(f, "mixed-case strings not allowed"),
637 }
638 }
639}
640
641#[cfg(any(feature = "std", test))]
642impl std::error::Error for Error {
643 fn description(&self) -> &str {
644 match *self {
645 Error::MissingSeparator => "missing human-readable separator",
646 Error::InvalidChecksum => "invalid checksum",
647 Error::InvalidLength => "invalid length",
648 Error::InvalidChar(_) => "invalid character",
649 Error::InvalidData(_) => "invalid data point",
650 Error::InvalidPadding => "invalid padding",
651 Error::MixedCase => "mixed-case strings not allowed",
652 }
653 }
654}
655
656pub fn convert_bits<T>(data: &[T], from: u32, to: u32, pad: bool) -> Result<Vec<u8>, Error>
674where
675 T: Into<u8> + Copy,
676{
677 if from > 8 || to > 8 || from == 0 || to == 0 {
678 panic!("convert_bits `from` and `to` parameters 0 or greater than 8");
679 }
680 let mut acc: u32 = 0;
681 let mut bits: u32 = 0;
682 let mut ret: Vec<u8> = Vec::new();
683 let maxv: u32 = (1 << to) - 1;
684 for value in data {
685 let v: u32 = u32::from(Into::<u8>::into(*value));
686 if (v >> from) != 0 {
687 return Err(Error::InvalidData(v as u8));
689 }
690 acc = (acc << from) | v;
691 bits += from;
692 while bits >= to {
693 bits -= to;
694 ret.push(((acc >> bits) & maxv) as u8);
695 }
696 }
697 if pad {
698 if bits > 0 {
699 ret.push(((acc << (to - bits)) & maxv) as u8);
700 }
701 } else if bits >= from || ((acc << (to - bits)) & maxv) != 0 {
702 return Err(Error::InvalidPadding);
703 }
704 Ok(ret)
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710
711 #[test]
712 fn getters() {
713 let decoded = decode("BC:SW50QJK62X6AE").unwrap();
714 let data = [16, 14, 20, 15, 0].check_base32().unwrap();
715 assert_eq!(&decoded.0, "bc");
716 assert_eq!(decoded.1, data.as_slice());
717 }
718
719 #[test]
720 fn valid_checksum() {
721 let strings = [
722 "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2",
723 "bchtest:pr6m7j9njldwwzlg9v7v53unlr4jkmx6eyvwc0uz5t",
724 "pref:pr6m7j9njldwwzlg9v7v53unlr4jkmx6ey65nvtks5",
725 "prefix:0r6m7j9njldwwzlg9v7v53unlr4jkmx6ey3qnjwsrf",
726 "bitcoincash:q9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2ws4mr9g0",
727 "bchtest:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2u94tsynr",
728 "pref:p9adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2khlwwk5v",
729 "prefix:09adhakpwzztepkpwp5z0dq62m6u5v5xtyj7j3h2p29kc2lp",
730 "bitcoincash:qgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcw59jxxuz",
731 "bchtest:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcvs7md7wt",
732 "pref:pgagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkcrsr6gzkn",
733 "prefix:0gagf7w02x4wnz3mkwnchut2vxphjzccwxgjvvjmlsxqwkc5djw8s9g",
734 "bitcoincash:qvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq5nlegake",
735 "bchtest:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq7fqng6m6",
736 "pref:pvch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxq4k9m7qf9",
737 "prefix:0vch8mmxy0rtfrlarg7ucrxxfzds5pamg73h7370aa87d80gyhqxqsh6jgp6w",
738 "bitcoincash:qnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv39gr3uvz",
739 "bchtest:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvmgm6ynej",
740 "pref:pnq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklv0vx5z0w3",
741 "prefix:0nq8zwpj8cq05n7pytfmskuk9r4gzzel8qtsvwz79zdskftrzxtar994cgutavfklvwsvctzqy",
742 "bitcoincash:qh3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqex2w82sl",
743 "bchtest:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqnzf7mt6x",
744 "pref:ph3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqjntdfcwg",
745 "prefix:0h3krj5607v3qlqh5c3wq3lrw3wnuxw0sp8dv0zugrrt5a3kj6ucysfz8kxwv2k53krr7n933jfsunqakcssnmn",
746 "bitcoincash:qmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqscw8jd03f",
747 "bchtest:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqs6kgdsg2g",
748 "pref:pmvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsammyqffl",
749 "prefix:0mvl5lzvdm6km38lgga64ek5jhdl7e3aqd9895wu04fvhlnare5937w4ywkq57juxsrhvw8ym5d8qx7sz7zz0zvcypqsgjrqpnw8",
750 "bitcoincash:qlg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mtky5sv5w",
751 "bchtest:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mc773cwez",
752 "pref:plg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96mg7pj3lh8",
753 "prefix:0lg0x333p4238k0qrc5ej7rzfw5g8e4a4r6vvzyrcy8j3s5k0en7calvclhw46hudk5flttj6ydvjc0pv3nchp52amk97tqa5zygg96ms92w6845",
754 ];
755 for s in strings {
756 match decode(s) {
757 Ok((hrp, payload, variant)) => {
758 let encoded = encode(&hrp, payload, variant).unwrap();
759 assert_eq!(s.to_lowercase(), encoded.to_lowercase());
760 }
761 Err(e) => panic!("Did not decode: {:?} Reason: {:?}", s, e),
762 }
763 }
764 }
765
766 #[test]
767 fn invalid_strings() {
768 let pairs = [
769 (" :nwldj5",
770 Error::InvalidChar(' ')),
771 ("abc:\u{2192}axkwrx",
772 Error::InvalidChar('\u{2192}')),
773 ("an84characterslonghumanreadablepartthatcontainsthenumber:andtheexcludedcharactersbio:569pvx",
774 Error::InvalidLength),
775 ("pzry9x0s0muk",
776 Error::MissingSeparator),
777 (":pzry9x0s0muk",
778 Error::InvalidLength),
779 ("x:b4n0q5v",
780 Error::InvalidChar('b')),
781 ("ABC:DEFGOH",
782 Error::InvalidChar('O')),
783 ("li:dgmt3",
784 Error::InvalidLength),
785 ("de:lg7wt\u{ff}",
786 Error::InvalidChar('\u{ff}')),
787 ("\u{20}:xj0phk",
788 Error::InvalidChar('\u{20}')),
789 ("\u{7F}:g6xzxy",
790 Error::InvalidChar('\u{7F}')),
791 ("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber::d6pts4",
792 Error::InvalidLength),
793 ("qyrz8wqd2c9m",
794 Error::MissingSeparator),
795 (":qyrz8wqd2c9m",
796 Error::InvalidLength),
797 ("y:b0jsk6g",
798 Error::InvalidChar('b')),
799 ("lt:igcx5c0",
800 Error::InvalidChar('i')),
801 ("in:muywd",
802 Error::InvalidLength),
803 ("mm:crxm3i",
804 Error::InvalidChar('i')),
805 ("au:s5cgom",
806 Error::InvalidChar('o')),
807 ("M:VUXWEZ",
808 Error::InvalidChecksum),
809 (":6plkw9",
810 Error::InvalidLength),
811 (":p2gdwpf",
812 Error::InvalidLength),
813 ];
814 for p in pairs {
815 let (s, expected_error) = p;
816 match decode(s) {
817 Ok(_) => panic!("Should be invalid: {:?}", s),
818 Err(e) => assert_eq!(e, expected_error, "testing input '{}'", s),
819 }
820 }
821 }
822
823 #[test]
824 fn valid_conversion() {
825 let tests: Vec<(Vec<u8>, u32, u32, bool, Vec<u8>)> = vec![
827 (vec![0x01], 1, 1, true, vec![0x01]),
828 (vec![0x01, 0x01], 1, 1, true, vec![0x01, 0x01]),
829 (vec![0x01], 8, 8, true, vec![0x01]),
830 (vec![0x01], 8, 4, true, vec![0x00, 0x01]),
831 (vec![0x01], 8, 2, true, vec![0x00, 0x00, 0x00, 0x01]),
832 (
833 vec![0x01],
834 8,
835 1,
836 true,
837 vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
838 ),
839 (vec![0xff], 8, 5, true, vec![0x1f, 0x1c]),
840 (vec![0x1f, 0x1c], 5, 8, false, vec![0xff]),
841 ];
842 for t in tests {
843 let (data, from_bits, to_bits, pad, expected_result) = t;
844 let result = convert_bits(&data, from_bits, to_bits, pad);
845 assert!(result.is_ok());
846 assert_eq!(result.unwrap(), expected_result);
847 }
848 }
849
850 #[test]
851 fn invalid_conversion() {
852 let tests: Vec<(Vec<u8>, u32, u32, bool, Error)> = vec![
854 (vec![0xff], 8, 5, false, Error::InvalidPadding),
855 (vec![0x02], 1, 1, true, Error::InvalidData(0x02)),
856 ];
857 for t in tests {
858 let (data, from_bits, to_bits, pad, expected_error) = t;
859 let result = convert_bits(&data, from_bits, to_bits, pad);
860 assert!(result.is_err());
861 assert_eq!(result.unwrap_err(), expected_error);
862 }
863 }
864
865 #[test]
866 fn convert_bits_invalid_bit_size() {
867 use std::panic::{catch_unwind, set_hook, take_hook};
868
869 let invalid = &[(0, 8), (5, 0), (9, 5), (8, 10), (0, 16)];
870
871 for &(from, to) in invalid {
872 set_hook(Box::new(|_| {}));
873 let result = catch_unwind(|| {
874 let _ = convert_bits(&[0], from, to, true);
875 });
876 let _ = take_hook();
877 assert!(result.is_err());
878 }
879 }
880
881 #[test]
882 fn check_base32() {
883 assert!([0u8, 1, 2, 30, 31].check_base32().is_ok());
884 assert!([0u8, 1, 2, 30, 31, 32].check_base32().is_err());
885 assert!([0u8, 1, 2, 30, 31, 255].check_base32().is_err());
886
887 assert!([1u8, 2, 3, 4].check_base32().is_ok());
888 assert_eq!(
889 [30u8, 31, 35, 20].check_base32(),
890 Err(Error::InvalidData(35))
891 );
892 }
893
894 #[test]
895 fn test_encode() {
896 assert_eq!(
897 encode(
898 "",
899 vec![1u8, 2, 3, 4].check_base32().unwrap(),
900 Variant::Bech32
901 ),
902 Err(Error::InvalidLength)
903 );
904 }
905
906 #[test]
907 fn from_base32() {
908 assert_eq!(
909 Vec::from_base32(&[0x1f, 0x1c].check_base32().unwrap()),
910 Ok(vec![0xff])
911 );
912 assert_eq!(
913 Vec::from_base32(&[0x1f, 0x1f].check_base32().unwrap()),
914 Err(Error::InvalidPadding)
915 );
916 }
917
918 #[test]
919 fn to_base32() {
920 assert_eq!([0xffu8].to_base32(), [0x1f, 0x1c].check_base32().unwrap());
921 }
922
923 #[test]
924 fn reverse_charset() {
925 fn get_char_value(c: char) -> i8 {
926 let charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
927 match charset.find(c.to_ascii_lowercase()) {
928 Some(x) => x as i8,
929 None => -1,
930 }
931 }
932
933 let expected_rev_charset = (0u8..128)
934 .map(|i| get_char_value(i as char))
935 .collect::<Vec<_>>();
936
937 assert_eq!(&(CHARSET_REV[..]), expected_rev_charset.as_slice());
938 }
939
940 #[test]
941 fn writer() {
942 let hrp = "lnbc";
943 let data = "Hello World!".as_bytes().to_base32();
944
945 let mut written_str = String::new();
946 {
947 let mut writer = Bech32Writer::new(hrp, Variant::Bech32, &mut written_str).unwrap();
948 writer.write(&data).unwrap();
949 writer.finalize().unwrap();
950 }
951
952 let encoded_str = encode(hrp, data, Variant::Bech32).unwrap();
953
954 assert_eq!(encoded_str, written_str);
955 }
956
957 #[test]
958 fn write_on_drop() {
959 let hrp = "lntb";
960 let data = "Hello World!".as_bytes().to_base32();
961
962 let mut written_str = String::new();
963 {
964 let mut writer = Bech32Writer::new(hrp, Variant::Bech32, &mut written_str).unwrap();
965 writer.write(&data).unwrap();
966 }
967
968 let encoded_str = encode(hrp, data, Variant::Bech32).unwrap();
969
970 assert_eq!(encoded_str, written_str);
971 }
972
973 #[test]
974 fn test_hrp_case() {
975 let encoded_str = encode("HRP", [0x00, 0x00].to_base32(), Variant::Bech32).unwrap();
977
978 assert_eq!(encoded_str, "hrp:qqqq9rcnqmy0");
979 }
980
981 #[test]
982 fn round_test() {
983 let s = "bitcoincash:qr6m7j9njldwwzlg9v7v53unlr4jkmx6eylep8ekg2";
984 let (hrp, data, variant) = decode(s).unwrap();
985 let encoded_str = encode(&hrp, data, variant).unwrap();
986 assert_eq!(encoded_str, s);
987 }
988}