lib: doc: add TODOs
[majurity.git] / hjugement-protocol / src / Voting / Protocol / Credential.hs
index 39ac87b20c526fc27207c527ae16db9f5dc73ef4..14582d1f5e83a3410c6c99833e645e4b93c7cea9 100644 (file)
@@ -1,35 +1,59 @@
 {-# LANGUAGE DeriveAnyClass #-}
 {-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE DerivingStrategies #-}
 module Voting.Protocol.Credential where
 
 import Control.DeepSeq (NFData)
-import Control.Monad (Monad(..), replicateM)
-import Data.Bits
+import Control.Monad (Monad(..), forM_, replicateM)
 import Data.Bool
 import Data.Char (Char)
-import Data.Either (Either(..))
+import Data.Either (Either(..), either)
 import Data.Eq (Eq(..))
 import Data.Function (($))
 import Data.Functor ((<$>))
 import Data.Int (Int)
 import Data.Maybe (maybe)
 import Data.Ord (Ord(..))
+import Data.Reflection (Reifies(..))
+import Data.Semigroup (Semigroup(..))
 import Data.Text (Text)
 import GHC.Generics (Generic)
-import Numeric.Natural (Natural)
-import Prelude (Integral(..), fromIntegral, div)
-import Text.Show (Show)
+import Prelude (Integral(..), fromIntegral)
+import Text.Show (Show(..))
 import qualified Control.Monad.Trans.State.Strict as S
-import qualified Crypto.KDF.PBKDF2 as Crypto
-import qualified Data.ByteArray as ByteArray
-import qualified Data.ByteString as BS
+import qualified Data.Aeson as JSON
+import qualified Data.Aeson.Types as JSON
 import qualified Data.Char as Char
 import qualified Data.List as List
 import qualified Data.Text as Text
-import qualified Data.Text.Encoding as Text
 import qualified System.Random as Random
 
 import Voting.Protocol.Arithmetic
+import Voting.Protocol.Cryptography
+
+-- * Class 'Key'
+class Key crypto where
+       -- | Type of cryptography, eg. "FFC".
+       cryptoType :: crypto -> Text
+       -- | Name of the cryptographic paramaters, eg. "Belenios".
+       cryptoName :: crypto -> Text
+       -- | Generate a random 'SecretKey'.
+       randomSecretKey ::
+        Reifies c crypto =>
+        Monad m => Random.RandomGen r =>
+        S.StateT r m (SecretKey crypto c)
+       -- | @('credentialSecretKey' uuid cred)@ returns the 'SecretKey'
+       -- derived from given 'uuid' and 'cred'
+       -- using 'Crypto.fastPBKDF2_SHA256'.
+       credentialSecretKey ::
+        Reifies c crypto =>
+        UUID -> Credential -> SecretKey crypto c
+       -- | @('publicKey' secKey)@ returns the 'PublicKey'
+       -- derived from given 'SecretKey' @secKey@.
+       publicKey ::
+        Reifies c crypto =>
+        SecretKey crypto c ->
+        PublicKey crypto c
 
 -- * Type 'Credential'
 -- | A 'Credential' is a word of @('tokenLength'+1 '==' 15)@-characters
@@ -39,7 +63,14 @@ import Voting.Protocol.Arithmetic
 -- The last character is a checksum.
 -- The entropy is: @('tokenLength' * log 'tokenBase' / log 2) '==' 82.01… bits@.
 newtype Credential = Credential Text
- deriving (Eq,Show,Generic,NFData)
+ deriving (Eq,Show,Generic)
+ deriving newtype NFData
+ deriving newtype JSON.ToJSON
+instance JSON.FromJSON Credential where
+       parseJSON json@(JSON.String s) =
+               either (\err -> JSON.typeMismatch ("Credential: "<>show err) json) return $
+               readCredential s
+       parseJSON json = JSON.typeMismatch "Credential" json
 
 credentialAlphabet :: [Char] -- TODO: make this an array
 credentialAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@@ -56,16 +87,16 @@ randomCredential = do
                        ( acc * tokenBase + d
                        , charOfDigit d : ds )
                 ) (zero::Int, []) rs
-       let checksum = (neg tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?
+       let checksum = (negate tot + 53) `mod` 53 -- NOTE: why 53 and not 'tokenBase' ?
        return $ Credential $ Text.reverse $ Text.pack (charOfDigit checksum:cs)
        where
        charOfDigit = (credentialAlphabet List.!!)
 
 -- | @'readCredential'@ reads and check the well-formedness of a 'Credential'
 -- from raw 'Text'.
-readCredential :: Text -> Either CredentialError Credential
+readCredential :: Text -> Either ErrorToken Credential
 readCredential s
- | Text.length s /= tokenLength + 1 = Left CredentialError_Length
+ | Text.length s /= tokenLength + 1 = Left ErrorToken_Length
  | otherwise = do
        tot <- Text.foldl'
         (\acc c -> acc >>= \a -> ((a * tokenBase) +) <$> digitOfChar c)
@@ -74,22 +105,29 @@ readCredential s
        checksum <- digitOfChar (Text.last s)
        if (tot + checksum) `mod` 53 == 0
        then Right (Credential s)
-       else Left CredentialError_Checksum
+       else Left ErrorToken_Checksum
        where
        digitOfChar c =
-               maybe (Left $ CredentialError_BadChar c) Right $
+               maybe (Left $ ErrorToken_BadChar c) Right $
                List.elemIndex c credentialAlphabet
 
--- ** Type 'CredentialError'
-data CredentialError
- =   CredentialError_BadChar Char.Char
- |   CredentialError_Checksum
- |   CredentialError_Length
+-- ** Type 'ErrorToken'
+data ErrorToken
+ =   ErrorToken_BadChar Char.Char
+ |   ErrorToken_Checksum
+ |   ErrorToken_Length
  deriving (Eq,Show,Generic,NFData)
 
 -- ** Type 'UUID'
 newtype UUID = UUID Text
- deriving (Eq,Ord,Show,Generic,NFData)
+ deriving (Eq,Ord,Show,Generic)
+ deriving anyclass (JSON.ToJSON)
+ deriving newtype NFData
+instance JSON.FromJSON UUID where
+       parseJSON json@(JSON.String s) =
+               either (\err -> JSON.typeMismatch ("UUID: "<>show err) json) return $
+               readUUID s
+       parseJSON json = JSON.typeMismatch "UUID" json
 
 -- | @'randomUUID'@ generates a random 'UUID'.
 randomUUID ::
@@ -102,36 +140,15 @@ randomUUID = do
        where
        charOfDigit = (credentialAlphabet List.!!)
 
--- ** Type 'SecretKey'
-type SecretKey = E
-
-randomSecretKey :: Monad m => RandomGen r => SubGroup q => S.StateT r m (SecretKey q)
-randomSecretKey = random
-
--- | @('credentialSecretKey' uuid cred)@ returns the 'SecretKey'
--- derived from given 'uuid' and 'cred'
--- using 'Crypto.fastPBKDF2_SHA256'.
-credentialSecretKey :: SubGroup q => UUID -> Credential -> SecretKey q
-credentialSecretKey (UUID uuid) (Credential cred) =
-       fromNatural $ BS.foldl'
-        (\acc b -> acc`shiftL`3 + fromIntegral b)
-        (0::Natural)
-        (ByteArray.convert deriv)
+-- | @'readCredential'@ reads and check the well-formedness of a 'Credential'
+-- from raw 'Text'.
+readUUID :: Text -> Either ErrorToken UUID
+readUUID s
+ | Text.length s /= tokenLength = Left ErrorToken_Length
+ | otherwise = do
+       forM_ (Text.unpack s) digitOfChar
+       return (UUID s)
        where
-       deriv :: BS.ByteString
-       deriv =
-               Crypto.fastPBKDF2_SHA256
-                Crypto.Parameters
-                { Crypto.iterCounts   = 1000
-                , Crypto.outputLength = 256 `div` 8
-                }
-                (Text.encodeUtf8 cred)
-                (Text.encodeUtf8 uuid)
-
--- ** Type 'PublicKey'
-type PublicKey = G
-
--- | @('publicKey' secKey)@ returns the 'PublicKey'
--- derived from given 'SecretKey'.
-publicKey :: SubGroup q => SecretKey q -> PublicKey q
-publicKey = (groupGen ^)
+       digitOfChar c =
+               maybe (Left $ ErrorToken_BadChar c) Right $
+               List.elemIndex c credentialAlphabet