{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE UndecidableInstances #-} -- for Reifies instances
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Finite Field Cryptography (FFC)
-- is a method of implementing discrete logarithm cryptography
-- using finite field mathematics.
module Voting.Protocol.FFC where

import Control.Arrow (first)
import Control.DeepSeq (NFData)
import Control.Monad (Monad(..), unless)
import Data.Aeson (ToJSON(..), FromJSON(..), (.:), (.:?), (.=))
import Data.Bool
import Data.Either (Either(..))
import Data.Eq (Eq(..))
import Data.Function (($), (.))
import Data.Functor ((<$>))
import Data.Maybe (Maybe(..), fromMaybe, fromJust)
import Data.Monoid (Monoid(..))
import Data.Ord (Ord(..))
import Data.Proxy (Proxy(..))
import Data.Reflection (Reifies(..), reify)
import Data.Semigroup (Semigroup(..))
import Data.Text (Text)
import GHC.Generics (Generic)
import GHC.Natural (minusNaturalMaybe)
import Numeric.Natural (Natural)
import Prelude (Integral(..), fromIntegral)
import Text.Read (readMaybe, readEither)
import Text.Show (Show(..))
import qualified Crypto.KDF.PBKDF2 as Crypto
import qualified Data.Aeson as JSON
import qualified Data.Aeson.Types as JSON
import qualified Data.Char as Char
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
import Voting.Protocol.Credential

-- * Type 'FFC'
-- | Mutiplicative subgroup of a Finite Prime Field.
--
-- NOTE: an 'FFC' term-value is brought into the context of many functions
-- through a type-variable @c@ whose 'Reifies' constraint enables to 'reflect'
-- that 'FFC' at the term-level (a surprising technique but a very useful one).
-- Doing like this is simpler than working in a 'Monad' (like a 'Reader'),
-- and enables that 'FFC' term to be used simply in instances' methods
-- not supporting an inner 'Monad', like 'parseJSON', 'randomR', 'fromEnum' or 'arbitrary'.
-- Aside from that, the sharing of 'FFC' amongst several types
-- is encoded at the type-level by including @c@
-- as a phantom type of 'F', 'G' and 'E'.
data FFC = FFC
 {   ffc_name :: !Text
 ,   ffc_fieldCharac :: !Natural
     -- ^ The prime number characteristic of a Finite Prime Field.
     --
     -- ElGamal's hardness to decrypt requires a large prime number
     -- to form the multiplicative subgroup.
 ,   ffc_groupGen :: !Natural
     -- ^ A generator of the multiplicative subgroup of the Finite Prime Field.
     --
     -- NOTE: since 'ffc_fieldCharac' is prime,
     -- the multiplicative subgroup is cyclic,
     -- and there are phi('fieldCharac'-1) many choices for the generator of the group,
     -- where phi is the Euler totient function.
 ,   ffc_groupOrder :: !Natural
     -- ^ The order of the subgroup.
     --
     -- WARNING: 'ffc_groupOrder' MUST be a prime number dividing @('ffc_fieldCharac'-1)@
     -- to ensure that ElGamal is secure in terms of the DDH assumption.
 } deriving (Eq,Show,Generic,NFData)
instance ToJSON FFC where
        toJSON FFC{..} =
                JSON.object $
                 (if Text.null ffc_name then [] else ["name" .= ffc_name] ) <>
                 [ "p" .= show ffc_fieldCharac
                 , "g" .= show ffc_groupGen
                 , "q" .= show ffc_groupOrder
                 ]
        toEncoding FFC{..} =
                JSON.pairs $
                        (if Text.null ffc_name then mempty else "name" .= ffc_name) <>
                        "p" .= show ffc_fieldCharac <>
                        "g" .= show ffc_groupGen <>
                        "q" .= show ffc_groupOrder
instance FromJSON FFC where
        parseJSON = JSON.withObject "FFC" $ \o -> do
                ffc_name <- fromMaybe "" <$> (o .:? "name")
                p <- o .: "p"
                g <- o .: "g"
                q <- o .: "q"
                -- TODO: check p is probable prime
                -- TODO: check q is probable prime
                ffc_fieldCharac <- case readEither (Text.unpack p) of
                 Left err -> JSON.typeMismatch ("FFC: fieldCharac: "<>err) (JSON.String p)
                 Right a -> return a
                ffc_groupGen <- case readEither (Text.unpack g) of
                 Left err -> JSON.typeMismatch ("FFC: groupGen: "<>err) (JSON.String g)
                 Right a -> return a
                ffc_groupOrder <- case readEither (Text.unpack q) of
                 Left err -> JSON.typeMismatch ("FFC: groupOrder: "<>err) (JSON.String q)
                 Right a -> return a
                unless (nat ffc_groupGen < ffc_fieldCharac) $
                        JSON.typeMismatch "FFC: groupGen is not lower than fieldCharac" (JSON.Object o)
                unless (ffc_groupOrder < ffc_fieldCharac) $
                        JSON.typeMismatch "FFC: groupOrder is not lower than fieldCharac" (JSON.Object o)
                unless (nat ffc_groupGen > 1) $
                        JSON.typeMismatch "FFC: groupGen is not greater than 1" (JSON.Object o)
                unless (fromJust (ffc_fieldCharac`minusNaturalMaybe`one) `rem` ffc_groupOrder == 0) $
                        JSON.typeMismatch "FFC: groupOrder does not divide fieldCharac-1" (JSON.Object o)
                return FFC{..}
instance Reifies c FFC => CryptoParams FFC c where
        groupGen = G $ ffc_groupGen $ reflect (Proxy::Proxy c)
        groupOrder c = ffc_groupOrder $ reflect c
instance ReifyCrypto FFC where
        reifyCrypto = reify
instance Key FFC where
        cryptoType _ = "FFC"
        cryptoName = ffc_name
        randomSecretKey = random
        credentialSecretKey (UUID uuid) (Credential cred) =
                fromNatural $ decodeBigEndian $
                Crypto.fastPBKDF2_SHA256
                 Crypto.Parameters
                 { Crypto.iterCounts   = 1000
                 , Crypto.outputLength = 32 -- bytes, ie. 256 bits
                 }
                 (Text.encodeUtf8 cred)
                 (Text.encodeUtf8 uuid)
        publicKey = (groupGen @FFC ^)

fieldCharac :: forall c. Reifies c FFC => Natural
fieldCharac = ffc_fieldCharac $ reflect (Proxy::Proxy c)

-- ** Examples
-- | Weak parameters for debugging purposes only.
weakFFC :: FFC
weakFFC = FFC
 { ffc_name        = "weakFFC"
 , ffc_fieldCharac = 263
 , ffc_groupGen    = 2
 , ffc_groupOrder  = 131
 }

-- | Parameters used in Belenios.
-- A 2048-bit 'fieldCharac' of a Finite Prime Field,
-- with a 256-bit 'groupOrder' for a multiplicative subgroup
-- generated by 'groupGen'.
beleniosFFC :: FFC
beleniosFFC = FFC
 { ffc_name        = "beleniosFFC"
 , ffc_fieldCharac = 20694785691422546401013643657505008064922989295751104097100884787057374219242717401922237254497684338129066633138078958404960054389636289796393038773905722803605973749427671376777618898589872735865049081167099310535867780980030790491654063777173764198678527273474476341835600035698305193144284561701911000786737307333564123971732897913240474578834468260652327974647951137672658693582180046317922073668860052627186363386088796882120769432366149491002923444346373222145884100586421050242120365433561201320481118852408731077014151666200162313177169372189248078507711827842317498073276598828825169183103125680162072880719
 , ffc_groupGen    =  2402352677501852209227687703532399932712287657378364916510075318787663274146353219320285676155269678799694668298749389095083896573425601900601068477164491735474137283104610458681314511781646755400527402889846139864532661215055797097162016168270312886432456663834863635782106154918419982534315189740658186868651151358576410138882215396016043228843603930989333662772848406593138406010231675095763777982665103606822406635076697764025346253773085133173495194248967754052573659049492477631475991575198775177711481490920456600205478127054728238140972518639858334115700568353695553423781475582491896050296680037745308460627
 , ffc_groupOrder  = 78571733251071885079927659812671450121821421258408794611510081919805623223441
 }

-- | The type of the elements of a Finite Prime Field.
--
-- A field must satisfy the following properties:
--
-- * @(f, ('+'), 'zero')@ forms an abelian group,
--   called the additive group of 'f'.
--
-- * @('NonNull' f, ('*'), 'one')@ forms an abelian group,
--   called the multiplicative group of 'f'.
--
-- * ('*') is associative:
--   @(a'*'b)'*'c == a'*'(b'*'c)@ and
--   @a'*'(b'*'c) == (a'*'b)'*'c@.
--
-- * ('*') and ('+') are both commutative:
--   @a'*'b == b'*'a@ and
--   @a'+'b == b'+'a@
--
-- * ('*') and ('+') are both left and right distributive:
--   @a'*'(b'+'c) == (a'*'b) '+' (a'*'c)@ and
--   @(a'+'b)'*'c == (a'*'c) '+' (b'*'c)@
--
-- The 'Natural' is always within @[0..'fieldCharac'-1]@.
type instance FieldElement FFC = Natural
deriving newtype instance Eq     (G FFC c)
deriving newtype instance Ord    (G FFC c)
deriving newtype instance NFData (G FFC c)
deriving newtype instance Show   (G FFC c)
instance Reifies c FFC => FromJSON (G FFC c) where
        parseJSON (JSON.String s)
         | Just (c0,_) <- Text.uncons s
         , c0 /= '0'
         , Text.all Char.isDigit s
         , Just x <- readMaybe (Text.unpack s)
         , x < fieldCharac @c
         , r <- G x
         , r ^ E (groupOrder @FFC (Proxy @c)) == one
         = return r
        parseJSON json = JSON.typeMismatch "GroupElement" json
instance ToJSON (G FFC c) where
        toJSON (G x) = JSON.toJSON (show x)
instance Reifies c FFC => FromNatural (G FFC c) where
        fromNatural i = G $ abs $ i `mod` fieldCharac @c
                where
                abs x | x < 0 = x + fieldCharac @c
                      | otherwise = x
instance ToNatural (G FFC c) where
        nat = unG
instance Reifies c FFC => Additive (G FFC c) where
        zero = G 0
        G x + G y = G $ (x + y) `mod` fieldCharac @c
instance Reifies c FFC => Semiring (G FFC c) where
        one = G 1
        G x * G y = G $ (x * y) `mod` fieldCharac @c
instance Reifies c FFC => Ring (G FFC c) where
        negate (G x)
         | x == 0 = zero
         | otherwise = G $ fromJust $ nat (fieldCharac @c)`minusNaturalMaybe`x
instance Reifies c FFC => EuclideanRing (G FFC c) where
        -- | NOTE: add 'groupOrder' so the exponent given to (^) is positive.
        inverse = (^ E (fromJust $ groupOrder @FFC (Proxy @c)`minusNaturalMaybe`1))
instance Reifies c FFC => Random.Random (G FFC c) where
        randomR (G lo, G hi) =
                first (G . fromIntegral) .
                Random.randomR
                 ( 0`max`toInteger lo
                 , toInteger hi`min`(toInteger (fieldCharac @c) - 1) )
        random =
                first (G . fromIntegral) .
                Random.randomR (0, toInteger (fieldCharac @c) - 1)