packages feed

bech32 1.0.2 → 1.1.0

raw patch · 6 files changed

+393/−18 lines, 6 filesdep +base58-bytestringdep +memorydep +optparse-applicativedep ~basenew-component:exe:bech32PVP ok

version bump matches the API change (PVP)

Dependencies added: base58-bytestring, memory, optparse-applicative, process

Dependency ranges changed: base

API changes (from Hackage documentation)

Files

ChangeLog.md view
@@ -1,18 +1,65 @@-# ChangeLog for `bech32`+# Changelog -## 1.0.2 -- 2020-02-19+<!-- This ChangeLog follows a format specified by: https://keepachangelog.com/en/1.0.0/ --> -+ Added support for the `bech32-th` extension library.+## [1.1.0] - 2020-07-08 -## 1.0.1 -- 2020-02-13+### Added  -+ Improved module documentation, adding basic examples to help beginner users-  quickly get up to speed.-+ Exposed functions `dataPartFromWords` and `dataPartToWords` within public+- Added `bech32` command-line for easy conversions in the console.+  +  ```console+  Usage: bech32 [PREFIX]+    Convert to and from bech32 strings. Data are read from standard input.++  Available options:+    -h,--help                Show this help text+    PREFIX                   An optional human-readable prefix (e.g. 'addr').+                               - When provided, the input text is decoded from various encoding formats and re-encoded to bech32 using the given prefix.+                               - When omitted, the input text is decoded from bech32 to base16.++  Supported encoding formats: Base16, Bech32 & Base58.++  Examples:+    To Bech32:+      $ bech32 base16_ <<< 706174617465+      base16_1wpshgct5v5r5mxh0++      $ bech32 base58_ <<< Ae2tdPwUPEYy+      base58_1p58rejhd9592uusa8pzj2++      $ bech32 new_prefix <<< old_prefix1wpshgcg2s33x3+      new_prefix1wpshgcgeak9mv++    From Bech32:+      $ bech32 <<< base16_1wpshgct5v5r5mxh0+      706174617465+  ```++## [1.0.2] - 2020-02-19++### Added++- Added support for the `bech32-th` extension library.++## [1.0.1] - 2020-02-13++### Added++- Exposed functions `dataPartFromWords` and `dataPartToWords` within public   interface.-+ Exposed the `Word5` type within the public interface.-+ Exposed the `CharPosition` type within the public interface. -## 1.0.0 -- 2019-09-27+- Exposed the `Word5` type within the public interface. -+ Initial release pulled from https://github.com/input-output-hk/cardano-wallet+- Exposed the `CharPosition` type within the public interface.++### Changed++- Improved module documentation, adding basic examples to help beginner users+  quickly get up to speed.++## [1.0.0] - 2019-09-27++### Added ++- Initial release pulled from https://github.com/input-output-hk/cardano-wallet
+ app/Main.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE NamedFieldPuns #-}++import Prelude++import Codec.Binary.Bech32+    ( HumanReadablePart+    , dataPartFromBytes+    , dataPartToBytes+    , humanReadablePartFromText+    )+import Control.Arrow+    ( left, right )+import Control.Monad+    ( guard )+import Data.ByteArray.Encoding+    ( convertFromBase, convertToBase )+import Data.ByteString.Base58+    ( bitcoinAlphabet, decodeBase58, unAlphabet )+import Data.Char+    ( isHexDigit, isLetter, isLower, isUpper, toLower )+import Data.Either.Extra+    ( maybeToEither )+import Data.Maybe+    ( fromJust )+import Options.Applicative+    ( Parser+    , ParserInfo+    , argument+    , customExecParser+    , eitherReader+    , footerDoc+    , helpDoc+    , helper+    , info+    , metavar+    , optional+    , prefs+    , progDesc+    , showHelpOnEmpty+    , (<|>)+    )+import Options.Applicative.Help.Pretty+    ( bold, hsep, indent, text, underline, vsep )+import System.IO+    ( BufferMode (..), Handle, hSetBuffering, stderr, stdin, stdout )++import qualified Codec.Binary.Bech32 as Bech32+import qualified Codec.Binary.Bech32.Internal as Bech32+import qualified Data.ByteArray.Encoding as BA+import qualified Data.ByteString.Char8 as B8+import qualified Data.Text as T+import qualified Data.Text.Encoding as T++main :: IO ()+main = setup >> parse >>= run++newtype Cmd = Cmd+  { prefix :: Maybe HumanReadablePart+  } deriving (Show)++-- | Enable ANSI colors on Windows and correct output buffering+setup :: IO ()+setup =+    mapM_ hSetup [stderr, stdout]+  where+    hSetup :: Handle -> IO ()+    hSetup h = hSetBuffering h NoBuffering++-- | Parse command line options and arguments+parse :: IO Cmd+parse = customExecParser (prefs showHelpOnEmpty) parser+  where+    parser :: ParserInfo Cmd+    parser = info (helper <*> cmd) $ mconcat+        [ progDesc $ unwords+            [ "Convert to and from bech32 strings."+            , "Data are read from standard input."+            ]+        , footerDoc $ Just $ vsep+            [ hsep+                [ text "Supported encoding formats:"+                , indent 0 $ text  "Base16, Bech32 & Base58."+                ]+            , text ""+            , text "Examples:"+            , indent 2 $ hsep [underline $ text "To", text "Bech32:"]+            , indent 4 $ bold $ text "$ bech32 base16_ <<< 706174617465"+            , indent 4 $ text "base16_1wpshgct5v5r5mxh0"+            , text ""+            , indent 4 $ bold $ text "$ bech32 base58_ <<< Ae2tdPwUPEYy"+            , indent 4 $ text "base58_1p58rejhd9592uusa8pzj2"+            , text ""+            , indent 4 $ bold $ text "$ bech32 new_prefix <<< old_prefix1wpshgcg2s33x3"+            , indent 4 $ text "new_prefix1wpshgcgeak9mv"+            , text ""+            , indent 2 $ hsep [underline $ text "From", text "Bech32:"]+            , indent 4 $ bold $ text "$ bech32 <<< base16_1wpshgct5v5r5mxh0"+            , indent 4 $ text "706174617465"+            ]+        ]++    cmd :: Parser Cmd+    cmd = Cmd <$> optional hrpArgument++-- | Parse a 'HumanReadablePart' as an argument.+hrpArgument :: Parser HumanReadablePart+hrpArgument = argument (eitherReader reader) $ mconcat+    [ metavar "PREFIX"+    , helpDoc $ Just $ vsep+        [ text "An optional human-readable prefix (e.g. 'addr')."+        , indent 2 $ text+            "- When provided, the input text is decoded from various encoding \+            \formats and re-encoded to bech32 using the given prefix."+        , indent 2 $ text+            "- When omitted, the input text is decoded from bech32 to base16."+        ]+    ]+  where+    reader :: String -> Either String HumanReadablePart+    reader = left show . humanReadablePartFromText . T.pack++-- | Run a Command in IO+run :: Cmd -> IO ()+run Cmd{prefix} = do+    source <- T.decodeUtf8 . B8.filter (/= '\n') <$> B8.hGetContents stdin+    case prefix of+        Nothing  -> runDecode source+        Just hrp -> runEncode hrp source+  where+    runDecode source =+        case Bech32.decodeLenient source of+            Left err ->+                fail (show err)+            Right (_, dataPart) -> do+                let base16 = convertToBase BA.Base16+                B8.putStrLn $ base16 $ fromJust $ dataPartToBytes dataPart++    runEncode hrp source = do+        datapart <- either fail pure $+            case detectEncoding (T.unpack source) of+                Just Base16 -> do+                    let fromBase16 = convertFromBase BA.Base16 . T.encodeUtf8+                    dataPartFromBytes <$> fromBase16 source+                Just Bech32 ->+                    right snd $ left show $ Bech32.decodeLenient source+                Just Base58 -> do+                    let err = "Invalid Base58-encoded string."+                    let fromBase58 = decodeBase58 bitcoinAlphabet . T.encodeUtf8+                    dataPartFromBytes <$> maybeToEither err (fromBase58 source)+                Nothing ->+                    Left "Unable to detect input encoding. Neither Base16, \+                         \Bech32 nor Base58."+        B8.putStrLn $ T.encodeUtf8 $ Bech32.encodeLenient hrp datapart++data Encoding = Base16 | Bech32 | Base58 deriving (Show, Eq)++-- | Try detecting the encoding of a given 'String'+detectEncoding :: String -> Maybe Encoding+detectEncoding str+    | length str < minimalSizeForDetection = Nothing+    | otherwise = resembleBase16 <|> resembleBech32 <|> resembleBase58+  where+    resembleBase16 = do+        guard (all isHexDigit (toLower <$> str))+        guard (even (length str))+        pure Base16++    resembleBech32 = do+        guard (not (null humanpart))+        guard (all Bech32.humanReadableCharIsValid humanpart)+        guard (length datapart >= Bech32.checksumLength)+        guard (all (`elem` Bech32.dataCharList) datapart)+        guard (all isUpper alpha || all isLower alpha)+        guard (Bech32.separatorChar `elem` str)+        pure Bech32+      where+        datapart  = reverse . takeWhile (/= Bech32.separatorChar) . reverse $ str+        humanpart = takeWhile (/= Bech32.separatorChar) str+        alpha = filter isLetter str++    resembleBase58 = do+        guard (all isBase58Digit str)+        pure Base58+      where+        isBase58Digit :: Char -> Bool+        isBase58Digit =+            (`elem` T.unpack (T.decodeUtf8 $ unAlphabet bitcoinAlphabet))++-- NOTE For small string, it can be tricky to tell whether a string is hex+-- or bech32 encoded. Both could potentially be valid. As the length+-- increases, the probability for a string to satisfy all three encoding+-- rules gets smaller and smaller.+--+-- For example, let's consider the probability for the alphabet to match+-- between base16 and base58 (which will be bigger than the actual probability+-- of both encoding to be valid, since there are additional rules on top of+-- the alphabet):+--+--     P_1 = 16/58+--+-- Now, the probability that a base58 string of 8 characters will contain+-- only hexadecimal characters is+--+--     P_8 = P_1 ^ 8 ~ 0.00003+--+-- Which can be considered small enough to not happened too frequently. The+-- probability gets worse with Bech32 which has quite a lot of rules.+minimalSizeForDetection :: Int+minimalSizeForDetection = 8
bech32.cabal view
@@ -1,5 +1,5 @@ name:          bech32-version:       1.0.2+version:       1.1.0 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.@@ -24,6 +24,11 @@     default: False     manual: True +flag release+  description: Compile executables for a release.+  manual: True+  default: False+ library   default-language:       Haskell2010@@ -39,7 +44,7 @@       -Werror   build-depends:       array-    , base                                  < 4.14+    , base >= 4.11.1.0 && < 4.15     , bytestring     , containers     , extra@@ -50,6 +55,28 @@       Codec.Binary.Bech32       Codec.Binary.Bech32.Internal +executable bech32+  main-is: Main.hs+  other-modules:+      Paths_bech32+  hs-source-dirs:+      app+  ghc-options: -Wall -Wcompat -fwarn-redundant-constraints -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >=4.7 && <5+    , base58-bytestring+    , bech32+    , bytestring+    , extra+    , memory+    , optparse-applicative+    , text+  if flag(release)+    ghc-options: -Werror -static -O2+    cc-options: -static+    ld-options: -static -pthread+  default-language: Haskell2010+ test-suite bech32-test   default-language:       Haskell2010@@ -65,18 +92,23 @@       -Werror   build-depends:       base+    , base58-bytestring     , bech32     , bytestring     , containers     , deepseq     , extra     , hspec-    , QuickCheck+    , memory+    , process+    , QuickCheck >= 2.12     , text     , vector   build-tools:       hspec-discover+    , bech32   main-is:       Main.hs   other-modules:+      AppSpec       Codec.Binary.Bech32Spec
src/Codec/Binary/Bech32/Internal.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-}  -- |
+ test/AppSpec.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module AppSpec+    ( spec+    ) where++import Prelude++import Codec.Binary.Bech32+    ( dataPartFromBytes, humanReadablePartFromText )+import Data.ByteArray.Encoding+    ( Base (..), convertToBase )+import Data.ByteString+    ( ByteString )+import Data.ByteString.Base58+    ( bitcoinAlphabet, encodeBase58 )+import Data.Text+    ( Text )+import System.Process+    ( readProcess )+import Test.Hspec+    ( Spec, describe )+import Test.Hspec.QuickCheck+    ( prop )+import Test.QuickCheck+    ( Arbitrary (..), choose, vector, withMaxSuccess )+import Test.QuickCheck.Monadic+    ( assert, monadicIO, run )++import qualified Codec.Binary.Bech32 as Bech32+import qualified Data.Text as T+import qualified Data.Text.Encoding as T++spec :: Spec+spec =+    describe "bech32 command-line" $ do+        specDecode+        specEncode base16+        specEncode (bech32 "bech32")+        specEncode base58++-- | Check that, for a given encoder, any encoded string can be decoded and+-- re-encoded to bech32 using the given prefix.+specEncode+    :: (String -> String)+    -> Spec+specEncode encode = prop ("can re-encode encoded strings " <> encode "...") $+    \(MinString str) ->+        withMaxSuccess 1000 $ monadicIO $ do+            out <- run $ readProcess "bech32" ["prefix"] (encode str)+            assert (init out == bech32 "prefix" str)++-- | Check that any bech32-encoded string can be decoded successfully.+specDecode :: Spec+specDecode = prop "any bech32 string can be decoded to hex" $+    \(MinString str) ->+        withMaxSuccess 1000 $ monadicIO $ do+            out <- run $ readProcess "bech32" [] (bech32 "bech32" str)+            assert (init out == base16 str)++base16 :: String -> String+base16 = fromUtf8 . convertToBase Base16 . utf8++bech32 :: Text -> String -> String+bech32 txt = T.unpack . Bech32.encodeLenient hrp . dataPartFromBytes . utf8+  where+    Right hrp = humanReadablePartFromText txt+++base58 :: String -> String+base58 = fromUtf8 . encodeBase58 bitcoinAlphabet . utf8++utf8 :: String -> ByteString+utf8 = T.encodeUtf8 . T.pack++fromUtf8 :: ByteString -> String+fromUtf8 = T.unpack . T.decodeUtf8++-- Generate strings of a minimal length.+newtype MinString = MinString String deriving (Eq, Show)++instance Arbitrary MinString where+    arbitrary =+        MinString <$> (choose (8, 100) >>= vector)+    shrink (MinString str) =+        MinString <$> filter ((> 8) . length) (shrink str)
test/Codec/Binary/Bech32Spec.hs view
@@ -146,14 +146,14 @@                         "no invalid characters"                     $ cover 10 (not isValid)                         "one or more invalid characters"-                    $ if-                    | isValid ->+                    $ if isValid then                         fmap humanReadablePartToText                             (humanReadablePartFromText hrp)                             `shouldBe` Right (T.toLower hrp)-                    | otherwise ->+                       else                         humanReadablePartFromText hrp                             `shouldBe` Left invalidError+          it "Lengths are checked correctly." $             property $ \(HumanReadablePartWithSuspiciousLength hrp) ->