never executed always true always false
1 {-# LANGUAGE DeriveFunctor #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE LambdaCase #-}
4 {-# LANGUAGE TupleSections #-}
5
6 {-# OPTIONS_HADDOCK hide #-}
7
8 module Codec.Binary.Encoding
9 ( -- * Types
10 AbstractEncoding (..)
11 , Encoding
12
13 -- * Encode
14 , encode
15
16 -- * Decode
17 , detectEncoding
18 , fromBase16
19 , fromBase64
20 , fromBase58
21 , fromBech32
22 ) where
23
24 import Prelude
25
26 import Codec.Binary.Bech32
27 ( HumanReadablePart )
28 import Control.Applicative
29 ( (<|>) )
30 import Control.Arrow
31 ( left )
32 import Control.Monad
33 ( guard )
34 import Data.ByteArray.Encoding
35 ( Base (..), convertFromBase, convertToBase )
36 import Data.ByteString
37 ( ByteString )
38 import Data.ByteString.Base58
39 ( bitcoinAlphabet, decodeBase58, encodeBase58, unAlphabet )
40 import Data.Char
41 ( isLetter, isLower, isUpper, ord, toLower )
42
43 import qualified Codec.Binary.Bech32 as Bech32
44 import qualified Data.Text as T
45 import qualified Data.Text.Encoding as T
46
47
48 --
49 -- Encoding
50 --
51
52 -- | A concrete 'Encoding' algebraic data-type.
53 type Encoding = AbstractEncoding HumanReadablePart
54
55 -- | An abstract 'Encoding' to make it easy to map over the bech32 component.
56 -- Typically used as 'AbstractEncoding HumanReadablePart'.
57 --
58 -- > λ> let xpubHRP = [humanReadablePart|xpub|]
59 -- > λ> let xprvHRP = [humanReadablePart|xprv|]
60 -- >
61 -- > λ> fmap (const xpubHRP) (EBech32 xprvHRP)
62 -- > EBech32 (HumanReadablePart "xpub")
63 --
64 data AbstractEncoding a
65 = EBase16
66 | EBase58
67 | EBech32 a
68 deriving (Eq, Show, Functor)
69
70 --
71 -- Encode
72 --
73
74
75 -- | Encode a 'ByteString' with the given encoding.
76 --
77 -- @since 2.0.0
78 encode :: Encoding -> ByteString -> ByteString
79 encode encoding bytes = case encoding of
80 EBase16 ->
81 convertToBase Base16 bytes
82 EBase58 ->
83 encodeBase58 bitcoinAlphabet bytes
84 EBech32 hrp ->
85 T.encodeUtf8 $ Bech32.encodeLenient hrp $ Bech32.dataPartFromBytes bytes
86
87 --
88 -- Decode
89 --
90
91 -- | Try detecting the encoding of a given 'String'
92 --
93 -- @since 2.0.0
94 detectEncoding :: String -> Maybe (AbstractEncoding ())
95 detectEncoding str = isBase16 <|> isBech32 <|> isBase58
96 where
97 isBase16 = do
98 guard (all ((`elem` "0123456789abcdef") . toLower) str)
99 guard (even (length str))
100 pure EBase16
101
102 isBech32 = do
103 guard (not (null humanpart))
104 guard (all (\c -> ord c >= 33 && ord c <= 126) humanpart)
105 guard (length datapart >= 6)
106 guard (all (`elem` Bech32.dataCharList) datapart)
107 guard (all isUpper alpha || all isLower alpha)
108 pure (EBech32 ())
109 where
110 datapart = reverse . takeWhile (/= '1') . reverse $ str
111 humanpart = takeWhile (/= '1') str
112 alpha = filter isLetter str
113
114 isBase58 = do
115 guard (all (`elem` T.unpack (T.decodeUtf8 $ unAlphabet bitcoinAlphabet)) str)
116 pure EBase58
117
118 -- | Try decoding a base16-encoded 'ByteString'
119 --
120 -- @since 2.0.0
121 fromBase16 :: ByteString -> Either String ByteString
122 fromBase16 = convertFromBase Base16
123
124 -- | Try decoding a base64-encoded 'ByteString'
125 --
126 -- @since 3.13.0
127 fromBase64 :: ByteString -> Either String ByteString
128 fromBase64 = convertFromBase Base64
129
130 -- | Try decoding a bech32-encoded 'ByteString'
131 --
132 -- @since 2.0.0
133 fromBech32
134 :: ([Int] -> String -> String)
135 -> ByteString
136 -> Either String (HumanReadablePart, ByteString)
137 fromBech32 markCharsRedAtIndices raw = left errToString $ do
138 (hrp, dp) <- left Just $ Bech32.decodeLenient $ T.decodeUtf8 raw
139 maybe (Left Nothing) (Right . (hrp,)) $ Bech32.dataPartToBytes dp
140 where
141 unCharPos (Bech32.CharPosition x) = x
142 invalidCharsMsg = "Invalid character(s) in string"
143 errToString = ("Bech32 error: " <>) . \case
144 Just Bech32.StringToDecodeTooLong ->
145 "string is too long"
146 Just Bech32.StringToDecodeTooShort ->
147 "string is too short"
148 Just Bech32.StringToDecodeHasMixedCase ->
149 "string has mixed case"
150 Just Bech32.StringToDecodeMissingSeparatorChar ->
151 "string has no separator char"
152 Just (Bech32.StringToDecodeContainsInvalidChars []) ->
153 invalidCharsMsg
154 Just (Bech32.StringToDecodeContainsInvalidChars ixs) ->
155 invalidCharsMsg <> ":\n" <> markCharsRedAtIndices
156 (map unCharPos ixs)
157 (T.unpack . T.decodeUtf8 $ raw)
158 Nothing ->
159 "invalid data-part; these bytes ain't uint8."
160
161 fromBase58 :: ByteString -> Either String ByteString
162 fromBase58 raw = maybe (Left "Invalid Base58-encoded string.") Right $ do
163 decodeBase58 bitcoinAlphabet raw