module Crypto.PubKey.ElGamal
( Params
, PublicNumber
, PrivateNumber
, EphemeralKey(..)
, SharedKey
, Signature
, generatePrivate
, generatePublic
, encryptWith
, encrypt
, decrypt
, signWith
, sign
, verify
) where
import Data.ByteString (ByteString)
import Crypto.Number.ModArithmetic (expSafe, expFast, inverse)
import Crypto.Number.Generate (generateMax)
import Crypto.Number.Serialize (os2ip)
import Crypto.Number.Basic (gcde_binary)
import Crypto.Types.PubKey.DH
import Crypto.Random.API
import Control.Arrow (first)
import Data.Maybe (fromJust)
import Crypto.PubKey.HashDescr (HashFunction)
data Signature = Signature (Integer, Integer)
newtype EphemeralKey = EphemeralKey Integer
generatePrivate :: CPRG g => g -> Integer -> (PrivateNumber, g)
generatePrivate rng q = first PrivateNumber $ generateMax rng q
generateEphemeral :: CPRG g => g -> Integer -> (EphemeralKey, g)
generateEphemeral rng q = first toEphemeral $ generatePrivate rng q
where toEphemeral (PrivateNumber n) = EphemeralKey n
generatePublic :: Params -> PrivateNumber -> PublicNumber
generatePublic (Params p g) (PrivateNumber a) = PublicNumber $ expSafe g a p
encryptWith :: EphemeralKey -> Params -> PublicNumber -> Integer -> (Integer,Integer)
encryptWith (EphemeralKey b) (Params p g) (PublicNumber h) m = (c1,c2)
where s = expSafe h b p
c1 = expSafe g b p
c2 = (s * m) `mod` p
encrypt :: CPRG g => g -> Params -> PublicNumber -> Integer -> ((Integer,Integer), g)
encrypt rng params@(Params p _) public m = first (\b -> encryptWith b params public m) $ generateEphemeral rng q
where q = p-1
decrypt :: Params -> PrivateNumber -> (Integer, Integer) -> Integer
decrypt (Params p _) (PrivateNumber a) (c1,c2) = (c2 * sm1) `mod` p
where s = expSafe c1 a p
sm1 = fromJust $ inverse s p
signWith :: Integer
-> Params
-> PrivateNumber
-> HashFunction
-> ByteString
-> Maybe Signature
signWith k (Params p g) (PrivateNumber x) hashF msg
| k >= p-1 || d > 1 = Nothing
| s == 0 = Nothing
| otherwise = Just $ Signature (r,s)
where r = expSafe g k p
h = os2ip $ hashF msg
s = ((h - x*r) * kInv) `mod` (p-1)
(kInv,_,d) = gcde_binary k (p-1)
sign :: CPRG g
=> g
-> Params
-> PrivateNumber
-> HashFunction
-> ByteString
-> (Signature, g)
sign rng params@(Params p _) priv hashF msg =
let (k, rng') = generateMax rng (p-1)
in case signWith k params priv hashF msg of
Nothing -> sign rng' params priv hashF msg
Just sig -> (sig, rng')
verify :: Params
-> PublicNumber
-> HashFunction
-> ByteString
-> Signature
-> Bool
verify (Params p g) (PublicNumber y) hashF msg (Signature (r,s))
| or [r <= 0,r >= p,s <= 0,s >= (p-1)] = False
| otherwise = lhs == rhs
where h = os2ip $ hashF msg
lhs = expFast g h p
rhs = (expFast y r p * expFast r s p) `mod` p