1 module Protocol.Credential where
3 import Control.Monad (Monad(..), replicateM)
5 import Data.Eq (Eq(..))
6 import Data.Either (Either(..))
7 import Data.Function (($), (.))
8 import Data.Functor ((<$>))
10 import Data.Ord (Ord(..))
11 import Data.Text (Text)
12 import Prelude (Integral(..), fromIntegral)
13 import Text.Show (Show)
14 import qualified Control.Monad.Trans.State.Strict as S
15 import qualified Data.Char as Char
16 import qualified Data.List as List
17 import qualified Data.Text as Text
18 import qualified System.Random as Random
22 -- * Type 'Credential'
23 -- | A 'Credential' is a word of 15-characters from the alphabet:
24 -- "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".
25 -- The last character is a checksum.
26 -- The entropy is: @(14 * log (9+26+26) / log 2) ~ 83.03 bits@.
27 newtype Credential p = Credential Text
31 tokenBase = F (9+26+26)
42 random i = S.StateT $ return . Random.randomR (zero, i-one)
44 -- | @'randomCredential'@ generates a random credential.
50 S.StateT r m (Credential p)
52 rs <- replicateM tokenLength (random (fromIntegral (unF tokenBase)))
53 let (tot, cs) = List.foldl' (\(acc,ds) d ->
54 ( acc * tokenBase + F (fromIntegral d)
58 let checksum = 53 - fromIntegral (unF tot `mod` 53)
59 return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)
62 | d < 9 = Char.chr (Char.ord '1'+d)
63 | d < (9+26) = Char.chr (Char.ord 'A'+d-9)
64 | otherwise = Char.chr (Char.ord 'a'+d-9-26)
66 -- | @'readCredential'@ reads and check the well-formedness of a 'Credential'
71 Text -> Either CredentialError (Credential p)
73 | Text.length s /= tokenLength + 1 = Left CredentialError_Length
76 (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)
79 checksum <- digitOfChar (Text.last s)
80 if unF (tot + checksum) `mod` 53 == 0
81 then Right (Credential s)
82 else Left CredentialError_Checksum
86 | c <= '9' = Right (primeField $ Char.ord c - Char.ord '1')
88 | c <= 'Z' = Right (primeField $ Char.ord c - Char.ord 'A' + 9)
90 | c <= 'z' = Right (primeField $ Char.ord c - Char.ord 'a' + 9 + 26)
92 where err = Left $ CredentialError_BadChar c
94 -- ** Type 'CredentialError'
96 = CredentialError_BadChar Char.Char
97 | CredentialError_Checksum
98 | CredentialError_Length