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.Ord (Ord(..))
13 import Data.Text (Text)
14 import Prelude (Integral(..), fromIntegral)
15 import Text.Show (Show)
16 import qualified Control.Monad.Trans.State.Strict as S
17 import qualified Data.Char as Char
18 import qualified Data.List as List
19 import qualified Data.Text as Text
20 import qualified System.Random as Random
22 import Protocol.Arithmetic
24 -- * Type 'Credential'
25 -- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters
26 -- from a base alphabet of (@'tokenBase' '==' 58)@ characters:
27 -- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
28 -- (beware the absence of "0", "O", "I", and "l").
29 -- The last character is a checksum.
30 -- The entropy is: @('tokenLength' * log 'tokenBase' / log 2) '==' 82.01… bits@.
31 newtype Credential = Credential Text
34 credentialAlphabet :: [Char] -- TODO: make this an array
35 credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
37 tokenBase = List.length credentialAlphabet
41 -- | @'randomCredential'@ generates a random 'Credential'.
45 S.StateT r m Credential
47 rs <- replicateM tokenLength (randomR (fromIntegral tokenBase))
48 let (tot, cs) = List.foldl' (\(acc,ds) d ->
50 , charOfDigit d : ds )
52 let checksum = (neg tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?
53 return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)
55 charOfDigit = (credentialAlphabet List.!!)
57 -- | @'readCredential'@ reads and check the well-formedness of a 'Credential'
59 readCredential :: Text -> Either CredentialError Credential
61 | Text.length s /= tokenLength + 1 = Left CredentialError_Length
64 (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)
67 checksum <- digitOfChar (Text.last s)
68 if (tot + checksum) `mod` 53 == 0
69 then Right (Credential s)
70 else Left CredentialError_Checksum
73 maybe (Left $ CredentialError_BadChar c) Right $
74 List.elemIndex c credentialAlphabet
76 -- ** Type 'CredentialError'
78 = CredentialError_BadChar Char.Char
79 | CredentialError_Checksum
80 | CredentialError_Length