1 module Protocol.Credential where
3 import Control.Monad (Monad(..), replicateM)
5 import Data.Char (Char)
6 import Data.Eq (Eq(..))
7 import Data.Either (Either(..))
8 import Data.Function (($))
9 import Data.Functor ((<$>))
11 import Data.Maybe (maybe)
12 import Data.Text (Text)
13 import Prelude (Integral(..), fromIntegral)
14 import Text.Show (Show)
15 import qualified Control.Monad.Trans.State.Strict as S
16 import qualified Data.Char as Char
17 import qualified Data.List as List
18 import qualified Data.Text as Text
19 import qualified System.Random as Random
21 import Protocol.Arithmetic
23 -- * Type 'Credential'
24 -- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters
25 -- from a base alphabet of (@'tokenBase' '==' 58)@ characters:
26 -- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
27 -- (beware the absence of "0", "O", "I", and "l").
28 -- The last character is a checksum.
29 -- The entropy is: @('tokenLength' * log 'tokenBase' / log 2) '==' 82.01… bits@.
30 newtype Credential = Credential Text
33 credentialAlphabet :: [Char] -- TODO: make this an array
34 credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
36 tokenBase = List.length credentialAlphabet
40 -- | @'randomCredential'@ generates a random 'Credential'.
44 S.StateT r m Credential
46 rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))
47 let (tot, cs) = List.foldl' (\(acc,ds) d ->
49 , charOfDigit d : ds )
51 let checksum = (neg tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?
52 return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)
54 charOfDigit = (credentialAlphabet List.!!)
56 -- | @'readCredential'@ reads and check the well-formedness of a 'Credential'
58 readCredential :: Text -> Either CredentialError Credential
60 | Text.length s /= tokenLength + 1 = Left CredentialError_Length
63 (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)
66 checksum <- digitOfChar (Text.last s)
67 if (tot + checksum) `mod` 53 == 0
68 then Right (Credential s)
69 else Left CredentialError_Checksum
72 maybe (Left $ CredentialError_BadChar c) Right $
73 List.elemIndex c credentialAlphabet
75 -- ** Type 'CredentialError'
77 = CredentialError_BadChar Char.Char
78 | CredentialError_Checksum
79 | CredentialError_Length