packages feed

bech32 1.0.0 → 1.0.1

raw patch · 6 files changed

+365/−65 lines, 6 filesnew-uploaderPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Codec.Binary.Bech32: CharPosition :: Int -> CharPosition
+ Codec.Binary.Bech32: data Word5
+ Codec.Binary.Bech32: dataCharList :: String
+ Codec.Binary.Bech32: dataPartFromWords :: [Word5] -> DataPart
+ Codec.Binary.Bech32: dataPartToWords :: DataPart -> [Word5]
+ Codec.Binary.Bech32: encodedStringMaxLength :: Int
+ Codec.Binary.Bech32: encodedStringMinLength :: Int
+ Codec.Binary.Bech32: humanReadableCharMaxBound :: Char
+ Codec.Binary.Bech32: humanReadableCharMinBound :: Char
+ Codec.Binary.Bech32: humanReadablePartMaxLength :: Int
+ Codec.Binary.Bech32: humanReadablePartMinLength :: Int
+ Codec.Binary.Bech32: newtype CharPosition
+ Codec.Binary.Bech32: separatorChar :: Char
+ Codec.Binary.Bech32.Internal: humanReadableCharIsValid :: Char -> Bool

Files

ChangeLog.md view
@@ -3,3 +3,12 @@ ## 1.0.0 -- 2019-09-27  + Initial release pulled from https://github.com/input-output-hk/cardano-wallet++## 1.0.1 -- 2020-02-13+++ Improved module documentation, adding basic examples to help beginner users+  quickly get up to speed.++ Exposed functions `dataPartFromWords` and `dataPartToWords` within public+  interface.++ Exposed the `Word5` type within the public interface.++ Exposed the `CharPosition` type within the public interface.
LICENSE view
@@ -186,7 +186,7 @@       same "printed page" as the copyright notice for easier       identification within third-party archives. -   Copyright © 2018-2019 IOHK+   Copyright © 2019-2020 IOHK     Licensed under the Apache License, Version 2.0 (the "License");    you may not use this file except in compliance with the License.
bech32.cabal view
@@ -1,11 +1,11 @@ name:          bech32-version:       1.0.0+version:       1.0.1 synopsis:      Implementation of the Bech32 cryptocurrency address format (BIP 0173). description:   Implementation of the Bech32 cryptocurrency address format documented in the                BIP (Bitcoin Improvement Proposal) 0173. author:        IOHK Engineering Team-maintainer:    operations@iohk.io, erikd@mega-nerd.com-copyright:     2017 Marko Bencun, 2019 IOHK+maintainer:    operations@iohk.io, erikd@mega-nerd.com, jonathan.knowles@iohk.io+copyright:     2017 Marko Bencun, 2019-2020 IOHK license:       Apache-2.0 license-file:  LICENSE homepage:      https://github.com/input-output-hk/bech32
src/Codec/Binary/Bech32.hs view
@@ -1,37 +1,79 @@ -- |--- Copyright: © 2017 Marko Bencun, 2018-2019 IOHK+-- Copyright: © 2017 Marko Bencun, 2019-2020 IOHK -- License: Apache-2.0 -- -- Implementation of the [Bech32] -- (https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) -- address format. ----- From an original implementation by Marko Bencun:------ [sipa/bech32](https://github.com/sipa/bech32/tree/bdc264f84014c234e908d72026b7b780122be11f/ref/haskell)+-- Based on an [original implementation](https://github.com/sipa/bech32/tree/bdc264f84014c234e908d72026b7b780122be11f/ref/haskell)+-- by [Marko Bencun](https://github.com/sipa).  module Codec.Binary.Bech32     (-      -- * Encoding & Decoding+    -- * Basic Usage++    -- ** Encoding       encode-    , encodeLenient-    , EncodingError (..)+    -- ** Decoding     , decode-    , decodeLenient++    -- * Error Handling++    -- ** Encoding+    , EncodingError (..)+    -- ** Decoding     , DecodingError (..) -      -- * Data Part+    -- * Core Types++    -- ** Data Payloads     , DataPart+    -- *** Conversion to and from Words+    , dataPartFromWords+    , dataPartToWords+    -- *** Conversion to and from Bytes     , dataPartFromBytes-    , dataPartFromText     , dataPartToBytes+    -- *** Conversion to and from Text+    , dataPartFromText     , dataPartToText -      -- * Human-Readable Part+    -- ** Human-Readable Prefixes     , HumanReadablePart-    , HumanReadablePartError (..)+    -- *** Conversion to and from Text     , humanReadablePartFromText     , humanReadablePartToText+    -- *** Error Handling+    , HumanReadablePartError (..)++    -- * Additional Types++    -- ** Character Positions+    , CharPosition (..)+    -- ** Data Words+    , Word5++    -- * Advanced Usage++    -- ** Encoding and Decoding with Greater Leniency+    , encodeLenient+    , decodeLenient++    -- * Fundamental Constants++    -- ** Data Payloads+    , dataCharList+    -- ** Human-Readable Prefixes+    , humanReadablePartMinLength+    , humanReadablePartMaxLength+    , humanReadableCharMinBound+    , humanReadableCharMaxBound+    -- ** Encoded Strings+    , encodedStringMaxLength+    , encodedStringMinLength+    , separatorChar+     ) where  import Codec.Binary.Bech32.Internal
src/Codec/Binary/Bech32/Internal.hs view
@@ -7,7 +7,7 @@ {-# LANGUAGE UndecidableInstances #-}  -- |--- Copyright: © 2017 Marko Bencun, 2018-2019 IOHK+-- Copyright: © 2017 Marko Bencun, 2019-2020 IOHK -- License: Apache-2.0 -- -- Implementation of the [Bech32]@@ -54,6 +54,7 @@     , humanReadablePartToWords     , humanReadablePartMinLength     , humanReadablePartMaxLength+    , humanReadableCharIsValid     , humanReadableCharMinBound     , humanReadableCharMaxBound @@ -146,10 +147,11 @@ dataPartToBytes dp = BS.pack <$>     (toBase256 =<< traverse dataCharToWord (T.unpack $ dataPartToText dp)) --- | Constructs a 'DataPart' from textual input. All characters in the input---   must be a member of 'dataCharList', the set of characters permitted to---   appear within the data part of a Bech32 string.+-- | Constructs a 'DataPart' from textual input. --+-- All characters in the input must be a member of 'dataCharList', the set of+-- characters permitted to appear within the data part of a Bech32 string.+-- -- Returns 'Nothing' if any character in the input is not a member of -- 'dataCharList'. --@@ -174,7 +176,7 @@ dataPartToText :: DataPart -> Text dataPartToText (DataPart t) = t --- | Construct a 'DataPart' directly from words.+-- | Construct a 'DataPart' directly from a list of words. -- -- This function guarantees to satisfy the following properties: --@@ -184,19 +186,29 @@ dataPartFromWords :: [Word5] -> DataPart dataPartFromWords = DataPart . T.pack . fmap dataCharFromWord --- | Convert a 'DataPart' into words.+-- | Unpack a 'DataPart' into a list of its constituent words. --+-- This function guarantees to satisfy the following properties:+--+-- > dataPartFromWords (dataPartToWords d) == d+-- > dataPartToWords (dataPartFromWords w) == w+--- dataPartToWords :: DataPart -> [Word5] dataPartToWords = mapMaybe dataCharToWord . T.unpack . dataPartToText  -- | Returns true iff. the specified character is permitted to appear within --   the data part of a Bech32 string.---   See here for more details: https://git.io/fj8FS+--+-- See here for more details: https://git.io/fj8FS+-- dataCharIsValid :: Char -> Bool dataCharIsValid = (`Map.member` dataCharToWordMap)  -- | A list of all characters that are permitted to appear within the data part---   of a Bech32 string. See here for more details: https://git.io/fj8FS+--   of a Bech32 string.+--+-- See here for more details: https://git.io/fj8FS+-- dataCharList :: String dataCharList = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" @@ -249,8 +261,15 @@ --   human-readable part of a Bech32 string. data HumanReadablePartError     = HumanReadablePartTooShort+      -- ^ The human-readable part is /shorter than/+      -- 'humanReadablePartMinLength'.     | HumanReadablePartTooLong+      -- ^ The human-readable part is /longer than/+      -- 'humanReadablePartMaxLength'.     | HumanReadablePartContainsInvalidChars [CharPosition]+      -- ^ The human-readable part contains one or more characters whose values+      -- are /less than/ 'humanReadableCharMinBound' or /greater than/+      -- 'humanReadableCharMaxBound'.     deriving (Eq, Show)  -- | Get the raw text of the human-readable part of a Bech32 string.@@ -295,15 +314,16 @@                             Encoding & Decoding -------------------------------------------------------------------------------} --- | Like 'encode' but allows output to be longer than 90 characters. This isn't--- ideal as the error detection becomes worse as string get longer but it's--- still acceptable.+-- | Like 'encode' but allows output to be longer than 90 characters. ----- From BIP-0173:+-- This isn't ideal, as Bech32 error detection becomes worse as strings get+-- longer, but it may be useful in certain circumstances. -----     Even though the chosen code performs reasonably well up to 1023+-- From [BIP-0173](https://en.bitcoin.it/wiki/BIP_0173):+--+--     "Even though the chosen code performs reasonably well up to 1023 --     characters, other designs are preferable for lengths above 89---     characters (excluding the separator).+--     characters (excluding the separator)." -- encodeLenient :: HumanReadablePart -> DataPart -> Text encodeLenient hrp dp = humanReadablePartToText hrp@@ -312,7 +332,28 @@   where     dcp = dataCharFromWord <$> dataPartToWords dp <> createChecksum hrp dp --- | Encode a human-readable string and data payload into a Bech32 string.+-- | Encode a Bech32 string from a human-readable prefix and data payload.+--+-- == Example+--+-- >>> import Prelude+-- >>> import Codec.Binary.Bech32+-- >>> import Data.Text.Encoding+--+-- First, prepare a human-readable prefix:+--+-- >>> Right prefix = humanReadablePartFromText "example"+--+-- Next, prepare a data payload:+--+-- >>> messageToEncode = "Lorem ipsum dolor sit amet!"+-- >>> dataPart = dataPartFromBytes $ encodeUtf8 messageToEncode+--+-- Finally, produce a Bech32 string:+--+-- >>> encode prefix dataPart+-- Right "example1f3hhyetdyp5hqum4d5sxgmmvdaezqumfwssxzmt9wsss9un3cx"+-- encode :: HumanReadablePart -> DataPart -> Either EncodingError Text encode hrp dp     | T.length result > encodedStringMaxLength = Left EncodedStringTooLong@@ -322,11 +363,15 @@  -- | Represents the set of error conditions that may occur while encoding a --   Bech32 string.-data EncodingError = EncodedStringTooLong+data EncodingError =+    EncodedStringTooLong+    -- ^ The resultant encoded string would be /longer than/+    -- 'encodedStringMaxLength'.     deriving (Eq, Show) --- | Like 'decode' but does not enforce a maximum length. See also--- 'encodeLenient' for details.+-- | Like 'decode' but does not enforce a maximum length.+--+-- See also 'encodeLenient' for details. decodeLenient :: Text -> Either DecodingError (HumanReadablePart, DataPart) decodeLenient bech32 = do     guardE (T.length bech32 >= encodedStringMinLength) StringToDecodeTooShort@@ -362,7 +407,29 @@             (T.length bech32 - separatorLength - 1 - ) <$>                 locateErrors (fromIntegral residue) (T.length bech32 - 1) --- | Decode a Bech32 string into a human-readable part and data part.+-- | Decode a Bech32 string into a human-readable prefix and data payload.+--+-- == Example+--+-- >>> import Prelude+-- >>> import Codec.Binary.Bech32+-- >>> import Data.Text.Encoding+--+-- First, decode the input:+--+-- >>> input = "example1f3hhyetdyp5hqum4d5sxgmmvdaezqumfwssxzmt9wsss9un3cx"+-- >>> Right (prefix, dataPart) = decode input+--+-- Next, examine the decoded human-readable prefix:+--+-- >>> humanReadablePartToText prefix+-- "example"+--+-- Finally, examine the decoded data payload:+--+-- >>> decodeUtf8 <$> dataPartToBytes dataPart+-- Just "Lorem ipsum dolor sit amet!"+-- decode :: Text -> Either DecodingError (HumanReadablePart, DataPart) decode bech32 = do     guardE (T.length bech32 <= encodedStringMaxLength) StringToDecodeTooLong@@ -398,19 +465,31 @@ -- string with the 'decode' function. data DecodingError     = StringToDecodeTooLong+      -- ^ The string to decode is /longer than/ 'encodedStringMaxLength'.     | StringToDecodeTooShort+      -- ^ The string to decode is /shorter than/ 'encodedStringMinLength'.     | StringToDecodeHasMixedCase+      -- ^ The string to decode contains /both/ upper case /and/ lower case+      -- characters.     | StringToDecodeMissingSeparatorChar+      -- ^ The string to decode is missing the /separator character/, specified+      -- by 'separatorChar'.     | StringToDecodeContainsInvalidChars [CharPosition]-      -- ^ In cases where it is possible to determine the exact locations of+      -- ^ The string to decode contains one or more /invalid characters/.+      --+      -- In cases where it /is/ possible to determine the exact locations of       -- erroneous characters, this list will encode those locations. Clients-      -- can use this information to provide user feedback. In cases where it-      -- isn't possible to reliably determine the locations of erroneous-      -- characters, this list will be empty.+      -- can use this information to provide user feedback.+      --+      -- In cases where it /isn't/ possible to reliably determine the locations+      -- of erroneous characters, this list will be empty.     deriving (Eq, Show) --- | The separator character. This character appears immediately after the--- human-readable part and before the data part.+-- | The separator character.+--+-- This character appears immediately after the human-readable part and before+-- the data part in an encoded string.+-- separatorChar :: Char separatorChar = '1' @@ -422,15 +501,19 @@ separatorLength :: Int separatorLength = 1 --- | The maximum length of an encoded string, in bytes. This length includes the---   human-readable part, the separator character, the encoded data portion,---   and the checksum.+-- | The maximum length of an encoded string, in bytes.+--+-- This length includes the human-readable part, the separator character, the+-- encoded data portion, and the checksum.+-- encodedStringMaxLength :: Int encodedStringMaxLength = 90 --- | The minimum length of an encoded string, in bytes. This length includes the---   human-readable part, the separator character, the encoded data portion,---   and the checksum.+-- | The minimum length of an encoded string, in bytes.+--+-- This length includes the human-readable part, the separator character, the+-- encoded data portion, and the checksum.+-- encodedStringMinLength :: Int encodedStringMinLength =     humanReadablePartMinLength + separatorLength + checksumLength@@ -440,6 +523,11 @@ -------------------------------------------------------------------------------}  -- | The zero-based position of a character in a string, counting from the left.+--+-- Values of this type are typically used to reflect the positions of errors.+--+-- See 'DecodingError'.+-- newtype CharPosition = CharPosition Int     deriving (Eq, Ord, Show) @@ -451,6 +539,21 @@ (.>>.) = unsafeShiftR (.<<.) = unsafeShiftL +-- | Represents a __data word__ of __5 bits__ in width.+--+-- Each character in the data portion of a Bech32 string encodes exactly 5 bits+-- of data.+--+-- === Construction and Deconstruction+--+-- Use the 'toEnum' and 'fromEnum' functions to construct and deconstruct+-- 'Word5' values.+--+-- === Packing Words into Data Payloads+--+-- Use the 'dataPartFromWords' and 'dataPartToWords' functions to pack and+-- unpack 'Word5' values into and out of data payloads.+-- newtype Word5 = Word5 { getWord5 :: Word8 }     deriving (Eq, Ord, Show) @@ -468,7 +571,7 @@     inRange (m,n) i = m <= i && i <= n  word5 :: Integral a => a -> Word5-word5 x = Word5 ((fromIntegral x) .&. 31)+word5 x = Word5 (fromIntegral x .&. 31) {-# INLINE word5 #-} {-# SPECIALIZE INLINE word5 :: Word8 -> Word5 #-} @@ -483,7 +586,7 @@     go chk value =         foldl' xor chk' [g | (g, i) <- zip generator [25 ..], testBit chk i]       where-        chk' = chk .<<. 5 `xor` (fromWord5 value)+        chk' = chk .<<. 5 `xor` fromWord5 value         generator =             [ 0x3b6a57b2             , 0x26508e6d@@ -751,7 +854,7 @@       l_s1 /= -1 &&       l_s2 /= -1 && (2 * l_s1 - l_s2 - l_s0 + 2046) `mod` 1023 == 0 =           let p1 = (l_s1 - l_s0 + 1023) `mod` 1023 in-          if (p1 >= len) then [] else+          if p1 >= len then [] else           let l_e1 = l_s0 + (1023 - 997) * p1 in           [p1 | l_e1 `mod` 33 <= 0]     | otherwise =@@ -776,7 +879,7 @@         | s1_s0p2 == 0      = []         | l_e2 `mod` 33 > 0 = []         | l_e1 `mod` 33 > 0 = []-        | (p1 < p2)         = [p1, p2]+        | p1 < p2           = [p1, p2]         | otherwise         = [p2, p1]       where         inv_p1_p2 = 1023 -
test/Codec/Binary/Bech32Spec.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-}@@ -17,14 +18,18 @@     , DataPart     , DecodingError (..)     , HumanReadablePart+    , HumanReadablePartError (..)     , dataPartFromBytes     , dataPartFromText     , dataPartFromWords     , dataPartIsValid     , dataPartToWords+    , humanReadableCharIsValid     , humanReadableCharMaxBound     , humanReadableCharMinBound     , humanReadablePartFromText+    , humanReadablePartMaxLength+    , humanReadablePartMinLength     , humanReadablePartToText     , separatorChar     )@@ -62,10 +67,13 @@     ( Arbitrary (..)     , Positive (..)     , arbitraryBoundedEnum+    , checkCoverage     , choose     , counterexample     , cover     , elements+    , frequency+    , oneof     , property     , withMaxSuccess     , (.&&.)@@ -97,9 +105,9 @@                         T.breakOnEnd (T.singleton separatorChar) checksum                 let Just (first, rest') = T.uncons rest                 let checksumCorrupted =-                        (hrp `T.snoc` (chr (ord first `xor` 1)))+                        (hrp `T.snoc` chr (ord first `xor` 1))                         `T.append` rest'-                (Bech32.decode checksumCorrupted) `shouldSatisfy` isLeft'+                Bech32.decode checksumCorrupted `shouldSatisfy` isLeft'                 -- test that re-encoding the decoded checksum results in                 -- the same checksum.                 let checksumEncoded = Bech32.encode resultHRP resultData@@ -109,8 +117,73 @@     describe "Invalid Checksums" $ forM_ invalidChecksums $         \(checksum, expect) ->             it (T.unpack checksum) $-                Bech32.decode checksum `shouldBe` (Left expect)+                Bech32.decode checksum `shouldBe` Left expect +    describe "Parsing human-readable parts from text" $ do++        describe "Known-good human readable parts parse correctly." $+            forM_ validHumanReadableParts $ \hrp ->+                it (T.unpack hrp) $+                    (humanReadablePartToText <$> humanReadablePartFromText hrp)+                    `shouldBe` Right hrp++        describe "Known-bad human readable parts fail to parse." $+            forM_ invalidHumanReadableParts $ \hrp ->+                it (T.unpack hrp) $+                    humanReadablePartFromText hrp `shouldSatisfy` isLeft'++        it "Characters are checked correctly for validity." $+            property $ \(HumanReadablePartWithSuspiciousChars hrp) ->+                let isValid = T.all humanReadableCharIsValid hrp+                    invalidError = HumanReadablePartContainsInvalidChars $+                        CharPosition . fst <$>+                        filter+                            ((not . humanReadableCharIsValid) . snd)+                            ([0 .. ] `zip` T.unpack hrp)+                in+                checkCoverage+                    $ cover 10 isValid+                        "no invalid characters"+                    $ cover 10 (not isValid)+                        "one or more invalid characters"+                    $ if+                    | isValid ->+                        fmap humanReadablePartToText+                            (humanReadablePartFromText hrp)+                            `shouldBe` Right (T.toLower hrp)+                    | otherwise ->+                        humanReadablePartFromText hrp+                            `shouldBe` Left invalidError++        it "Lengths are checked correctly." $+            property $ \(HumanReadablePartWithSuspiciousLength hrp) ->+                let lo  = humanReadablePartMinLength+                    hi  = humanReadablePartMaxLength+                    len = T.length hrp+                in+                checkCoverage+                    $ cover 10 (len < lo)+                        "too short"+                    $ cover 10 (len == lo)+                        "minimum length"+                    $ cover 10 (len > lo && len < hi)+                        "comfortably within bounds"+                    $ cover 10 (len == hi)+                        "maximum length"+                    $ cover 10 (len > hi)+                        "too long"+                    $ if+                    | len < lo ->+                        humanReadablePartFromText hrp+                            `shouldBe` Left HumanReadablePartTooShort+                    | len > hi ->+                        humanReadablePartFromText hrp+                            `shouldBe` Left HumanReadablePartTooLong+                    | otherwise ->+                        fmap humanReadablePartToText+                            (humanReadablePartFromText hrp)+                            `shouldBe` Right (T.toLower hrp)+     describe "More Encoding/Decoding Cases" $ do         it "length > maximum" $ do             let hrpUnpacked = "ca"@@ -301,24 +374,24 @@                         (Bech32.decode corruptedString `shouldBe` Left                             StringToDecodeHasMixedCase) -    describe "Roundtrip (encode . decode)" $ do+    describe "Roundtrip (encode . decode)" $         it "Can perform roundtrip for valid data" $ property $ \(hrp, dp) ->             (eitherToMaybe (Bech32.encode hrp dp)                 >>= eitherToMaybe . Bech32.decode) === Just (hrp, dp) -    describe "Roundtrip (dataPartToBytes . dataPartFromBytes)" $ do+    describe "Roundtrip (dataPartToBytes . dataPartFromBytes)" $         it "Can perform roundtrip base conversion" $ property $ \bs ->             (Bech32.dataPartToBytes . Bech32.dataPartFromBytes) bs === Just bs -    describe "Roundtrip (dataPartFromText . dataPartToText)" $ do+    describe "Roundtrip (dataPartFromText . dataPartToText)" $         it "Can perform roundtrip conversion" $ property $ \dp ->             (Bech32.dataPartFromText . Bech32.dataPartToText) dp === Just dp -    describe "Roundtrip (dataPartFromWords . dataPartToWords)" $ do+    describe "Roundtrip (dataPartFromWords . dataPartToWords)" $         it "Can perform roundtrip conversion" $ property $ \dp ->             (Bech32.dataPartFromWords . Bech32.dataPartToWords) dp === dp -    describe "Roundtrip (dataPartToWords . dataPartFromWords)" $ do+    describe "Roundtrip (dataPartToWords . dataPartFromWords)" $         it "Can perform roundtrip conversion" $ property $ \ws ->             (Bech32.dataPartToWords . Bech32.dataPartFromWords) ws === ws @@ -327,16 +400,16 @@             (Bech32.humanReadablePartFromText . Bech32.humanReadablePartToText)                 hrp === Right hrp -    describe "Roundtrip (toBase256 . toBase32)" $ do+    describe "Roundtrip (toBase256 . toBase32)" $         it "Can perform roundtrip base conversion" $ property $ \ws ->             (Bech32.toBase256 . Bech32.toBase32) ws === Just ws -    describe "Roundtrip (toBase32 . toBase256)" $ do+    describe "Roundtrip (toBase32 . toBase256)" $         it "Can perform roundtrip base conversion" $ property $ \ws ->             isJust (Bech32.toBase256 ws) ==>                 (Bech32.toBase32 <$> Bech32.toBase256 ws) === Just ws -    describe "Roundtrip (dataCharToWord . dataCharFromWord)" $ do+    describe "Roundtrip (dataCharToWord . dataCharFromWord)" $         it "can perform roundtrip character set conversion" $             property $ \w ->                 Bech32.dataCharToWord (toLower (Bech32.dataCharFromWord w))@@ -400,10 +473,26 @@                     .&&.                     (outputWordsSuffix `shouldSatisfy` all (== 0)) -    describe "Pointless test to trigger coverage on derived instances" $ do+    describe "Pointless test to trigger coverage on derived instances" $         it (show $ humanReadablePartFromText $ T.pack "ca") True  -- Taken from the BIP 0173 specification: https://git.io/fjBIN+validHumanReadableParts :: [Text]+validHumanReadableParts =+    [ "addr"+    , "ca"+    , "bc"+    , "tb"+    , "xprv"+    ]++invalidHumanReadableParts :: [Text]+invalidHumanReadableParts =+    [ "鑫"+    , "臥虎藏龍"+    ]++-- Taken from the BIP 0173 specification: https://git.io/fjBIN validBech32Strings :: [Text] validBech32Strings =     [ "A12UEL5L"@@ -514,8 +603,8 @@ bech32CharSet =     Set.filter (not . isUpper) $         Set.fromList [humanReadableCharMinBound .. humanReadableCharMaxBound]-            `Set.union` (Set.singleton separatorChar)-            `Set.union` (Set.fromList Bech32.dataCharList)+            `Set.union` Set.singleton separatorChar+            `Set.union` Set.fromList Bech32.dataCharList  -- | Find the index of an element in a sorted vector using simple binary search. sortedVectorElemIndex :: Ord a => a -> Vector a -> Maybe Int@@ -606,6 +695,63 @@             ]       where         chars = humanReadablePartToText hrp++-- | A human-readable part that may (or may not) contain one or more invalid+--   characters.+newtype HumanReadablePartWithSuspiciousChars =+    HumanReadablePartWithSuspiciousChars Text+    deriving (Eq, Show)++instance Arbitrary HumanReadablePartWithSuspiciousChars where+    arbitrary = do+        len <- genLength+        chars <- replicateM len genChar+        return $ HumanReadablePartWithSuspiciousChars $ T.pack chars+      where+        genChar = frequency+            [ (98, genCharValid)+            , ( 1, genCharBelowMinBound)+            , ( 1, genCharAboveMaxBound)+            ]+        genCharValid = choose+            ( humanReadableCharMinBound+            , humanReadableCharMaxBound+            )+        genCharBelowMinBound = choose+            ( toEnum 0+            , toEnum (fromEnum humanReadableCharMinBound - 1)+            )+        genCharAboveMaxBound = choose+            ( toEnum (fromEnum humanReadableCharMaxBound + 1)+            , toEnum (fromEnum humanReadableCharMaxBound * 2)+            )+        genLength = choose+            ( humanReadablePartMinLength+            , humanReadablePartMaxLength+            )++-- | A human-readable part that may (or may not) be too long or too short.+newtype HumanReadablePartWithSuspiciousLength =+    HumanReadablePartWithSuspiciousLength Text+    deriving (Eq, Show)++instance Arbitrary HumanReadablePartWithSuspiciousLength where+    arbitrary = do+        len <- oneof+            [ choose (0, lo - 1)+            , pure lo+            , choose (lo + 1, hi - 1)+            , pure hi+            , choose (hi + 1, hi * 2)+            ]+        chars <- replicateM len arbitrary+        return+            $ HumanReadablePartWithSuspiciousLength+            $ T.pack+            $ fmap getHumanReadableChar chars+      where+        lo = humanReadablePartMinLength+        hi = humanReadablePartMaxLength  instance Arbitrary ByteString where     shrink bytes | BS.null bytes = []