module Codec.Crypto.DSA.Exceptions(
         -- * Basic DSA Concepts
         ParameterSizes(..)
       , Params(..)
       , PublicKey(..)
       , PrivateKey(..)
       , Signature(..)
       , DSAError(..)
       , getN, getL
         -- * DSA Key generation
       , generateKeyPair
       , generateKeyPairWithParams
         -- * DSA Message Signing
         -- ** Basic, Suggested Mechanisms
       , signMessage
       , verifyMessage
         -- ** Advanced Methods
       , HashFunction(..)
       , signMessage'
       , verifyMessage'
         -- ** /k/ Generation Mechanisms
       , KGenerator
       , KSequence(..)
       , kViaExtraRandomBits
       , kViaTestingCandidates
       , kViaRFC6979
         -- * Generation of /p/ and /q/
         -- ** Generation via the probable primes method
       , ProbablePrimesEvidence(..)
       , generateProbablePrimes
       , validateProbablePrimes
         -- ** Generation via the provable primes method
       , ProvablePrimesEvidence(..)
       , generateProvablePrimes
       , validateProvablePrimes
         -- * Generation of the generator /g/
       , generateUnverifiableGenerator
       , generatorIsValid
       , generateVerifiableGenerator
       , validateVerifiableGenerator
       )
 where

import Codec.Crypto.DSA.Pure(ParameterSizes(..), HashFunction(..),
                             DSAError(..), ProbablePrimesEvidence(..),
                             ProvablePrimesEvidence(..), GenerationEvidence,
                             KGenerator, KSequence(..), getN, getL)
import qualified Codec.Crypto.DSA.Pure as Pure
import Control.Exception
import Crypto.Random
import Crypto.Types.PubKey.DSA
import Data.ByteString.Lazy(ByteString)
import Data.Word

-- |Generate a DSA key pair. This will also generate the /p/, /q/, and /g/
-- parameters using provable and verifiable algorithms, with SHA-256 as the
-- hash function. If you want to use your own /p/, /q/, and /g/ values or 
-- specify your own generation or hash function,, use the
-- 'generateKeyPairWithParams' function, below.
generateKeyPair :: CryptoRandomGen g =>
                   g -> ParameterSizes ->
                   (PublicKey, PrivateKey, ProvablePrimesEvidence, g)
generateKeyPair g s = throwLeft (Pure.generateKeyPair g s)

-- |Generate a key pair given a set of DSA parameters. You really should have
-- validated this set (/p/, /q/, and /g/) using the relevant functions below
-- before you do this. Doing so even if you generated them is probably not a bad
-- practice.
--
-- This uses the method using extra random bits from FIPS 186-4. You better be
-- using a good enough random number generator.
generateKeyPairWithParams :: CryptoRandomGen g =>
                             Params -> g ->
                             (PublicKey, PrivateKey, g)
generateKeyPairWithParams p g =
  throwLeft (Pure.generateKeyPairWithParams p g)

-- |Sign a message using DSA. This method utilizes very good defaults for
-- message signing that should be acceptable for most use cases: it uses SHA-256
-- for the hash function, and generates /k/ using the methods described in RFC
-- 6979. If you wish to change these defaults, please see `signMessaage'`.
signMessage :: PrivateKey -> ByteString -> Signature
signMessage p m = throwLeft (Pure.signMessage p m)

-- |Verify a DSA message signature. This uses the same default mechanisms as
-- `signMessage`.
verifyMessage :: PublicKey -> ByteString -> Signature -> Bool
verifyMessage = Pure.verifyMessage' SHA256

-- |Sign a message given the hash function an /k/ generation routine. Returns
-- either an error the signature generated. You can define your own /k/
-- generation routine ... but we don't recommend it. Actually, while we're
-- recommending, we recommend you use `kViaRFC6979`, if you're not sure
-- which to use.
signMessage' :: CryptoRandomGen g =>
                HashFunction -> KGenerator g -> g ->
                PrivateKey -> ByteString ->
                (Signature, g)
signMessage' h m g p b = throwLeft (Pure.signMessage' h m g p b)

-- |Verify a signed message. You need to know what hash algorithm they used
-- to generate the signature, and pass it in. Returns True if the signature
-- was valid.
verifyMessage' :: HashFunction -> PublicKey -> ByteString -> Signature -> Bool
verifyMessage' = Pure.verifyMessage'

kViaExtraRandomBits :: CryptoRandomGen g => KGenerator g
kViaExtraRandomBits = Pure.kViaExtraRandomBits

kViaTestingCandidates :: CryptoRandomGen g => KGenerator g
kViaTestingCandidates = Pure.kViaTestingCandidates

kViaRFC6979 :: CryptoRandomGen g => KGenerator g
kViaRFC6979 = Pure.kViaRFC6979

-- | Using an approved hash function -- at the point of writing, a SHA-2
-- variant -- generate values of p and q for use in DSA, for which p and
-- q have a very high probability of being prime. In addition to p and q,
-- this routine returns the "domain parameter seed" and "counter" used to
-- generate the primes. These can be supplied to later validation functions;
-- their secrecy is not required for the algorithm to work.
--
-- The inputs to the function are the DSA parameters we are generating a
-- key for, a source of entropy, the hash function to use, and (optionally)
-- the length of the domain parameter seed to use. The last item must be
-- greater to or later to the value of n, if supplied, and will be set to
-- (n + 8) if not.
--
-- The security of this method depends on the strength of the hash being
-- used. To that end, FIPS 140-2 requires a SHA-2 variant.
generateProbablePrimes :: CryptoRandomGen g =>
                          ParameterSizes ->
                          g ->
                          (ByteString -> ByteString) ->
                          Maybe Integer ->
                          (Integer,Integer, ProbablePrimesEvidence, g)
generateProbablePrimes p g h i = 
  throwLeft (Pure.generateProbablePrimes p g h i)

-- |Validate that the probable primes that either you generated or that someone
-- provided to you are legitimate.
validateProbablePrimes :: CryptoRandomGen g =>
                          g {- A random number source -} ->
                          Integer {- ^p -} ->
                          Integer {- ^q -} ->
                          ProbablePrimesEvidence {- ^The evidence -} ->
                          (Bool, g)
validateProbablePrimes = Pure.validateProbablePrimes

-- |Using an approved hash function -- at the point of writing, a SHA-2
-- variant -- generate values of p and q for use in DSA, for which p and
-- q are provably prime. In addition to p and q, this routine generates
-- a series of additional values that can be used to validate that this
-- algorithm performed correctly.
--
-- The inputs to the function are the DSA parameters we are generating 
-- key for, a source of entropy, the hash function to use, and (optionally)
-- an initial seed length in bits. The last item, if provided, must be
-- greater than or equal to the N value being tested against, and must
-- be a multiple of 8.
generateProvablePrimes :: CryptoRandomGen g =>
                          ParameterSizes {- ^The DSA parameters to use -} ->
                          g {- ^source of randomness -} ->
                          (ByteString -> ByteString) {- ^Hash function -} ->
                          Maybe Integer {- ^Optional seed length, in bits. Must
                                            be greater than or equal to N, and
                                            divisible by 8. -} ->
                          (Integer, Integer, ProvablePrimesEvidence, g)
generateProvablePrimes a b c d = throwLeft (Pure.generateProvablePrimes a b c d)

-- |Validate that the provable primes that either you generated or that
-- someone provided to you are legitimate.
validateProvablePrimes :: Integer -> Integer ->
                          ProvablePrimesEvidence ->
                          Bool
validateProvablePrimes = Pure.validateProvablePrimes

-- |Generate the generator /g/ using a method that is not verifiable to a third
-- party. Quoth FIPS: "[This] method ... may be used when complete validation of
-- the generator /g/ is not required; it is recommended that this method be used
-- only when the party generating /g/ is trusted to not deliberately generate a
-- /g/ that has a potentially exploitable relationship to another generator
-- /g'/.
--
-- The input to this function are a valid /p/ and /q/, generated using an
-- approved method.
--
-- It may be possible (?) that this routine could fail to find a possible
-- generator. In that case, Nothing is returned.
generateUnverifiableGenerator :: Integer -> Integer -> Integer
generateUnverifiableGenerator p q =
  throwNothing (Pure.generateUnverifiableGenerator p q)

-- |Validate that the given generator /g/ works for the values /p/ and /q/
-- provided.
generatorIsValid :: Integer {- ^p -} -> Integer {- ^q -} ->
                    Integer {- ^g -} ->
                    Bool
generatorIsValid = Pure.generatorIsValid

-- |Generate a generator /g/, given the values of /p/, /q/, the evidence created
-- generating those values, and an index. Quoth FIPS: "This generation method
-- supports the generation of multiple values of /g/ for specific values of /p/
-- and /q/. The use of different values of /g/ for the same /p/ and /q/ may be
-- used to support key separation; for example, using the /g/ that is generated
-- with @index = 1@ for digital signatures and with @index = 2@ for key
-- establishment."
--
-- This method is replicatable, so that given the same inputs it will generate
-- the same outputs. Thus, you can validate that the /g/ generated using this
-- method was generated correctly using 'validateVerifiableGenerator', which
-- will be nice if you don't trust the person you're talking to.
generateVerifiableGenerator :: GenerationEvidence ev =>
                               Integer {- ^p -} -> Integer {- ^q -} ->
                               ev {- ^The evidence created generating /p/
                                     and /q/ -} ->
                               Word8 {- ^an index (This allows multiple /g/s
                                        from one pair) -} ->
                               Integer
generateVerifiableGenerator a b c d =
  throwNothing (Pure.generateVerifiableGenerator a b c d)

-- |Validate that the value /g/ was generated by 'generateVerifiableGenerator'
-- or someone using the same algorithm. This is probably a good idea if you
-- don't trust your compatriot. 
validateVerifiableGenerator :: GenerationEvidence ev =>
                               Integer {- ^p -} -> Integer {- ^q -} ->
                               ev {- ^The evidence created generating /p/
                                     and /q/ -} ->
                               Word8 {- ^an index (This allows multiple /g/s
                                        from one pair) -} ->
                               Integer {- ^g -} ->
                               Bool
validateVerifiableGenerator = Pure.validateVerifiableGenerator

--

throwNothing :: Maybe a -> a
throwNothing Nothing  = throw DSAInvalidInput
throwNothing (Just x) = x