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 +9/−0
- LICENSE +1/−1
- bech32.cabal +3/−3
- src/Codec/Binary/Bech32.hs +54/−12
- src/Codec/Binary/Bech32/Internal.hs +138/−35
- test/Codec/Binary/Bech32Spec.hs +160/−14
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 = []