-- |
-- Module      : Crypto.PubKey.ECC.DH
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Elliptic curve Diffie Hellman
module Crypto.PubKey.ECC.DH (
    Curve,
    PublicPoint,
    PrivateNumber,
    SharedKey (..),
    generatePrivate,
    calculatePublic,
    getShared,
    tryGetShared,
) where

import Crypto.Error (
    CryptoError (..),
    CryptoFailable (..),
    throwCryptoError,
 )
import Crypto.Number.Generate (generateMax)
import Crypto.Number.Serialize (i2ospOf_)
import Crypto.PubKey.DH (SharedKey (..))
import Crypto.PubKey.ECC.Prim (isPointInSubgroup, isPointValid, pointMul)
import Crypto.PubKey.ECC.Types (
    Curve,
    Point (..),
    PrivateNumber,
    PublicPoint,
    common_curve,
    curveSizeBits,
    ecc_g,
    ecc_n,
 )
import Crypto.Random.Types

-- | Generating a private number d.
generatePrivate :: MonadRandom m => Curve -> m PrivateNumber
generatePrivate :: forall (m :: * -> *). MonadRandom m => Curve -> m PrivateNumber
generatePrivate Curve
curve = PrivateNumber -> m PrivateNumber
forall (m :: * -> *).
MonadRandom m =>
PrivateNumber -> m PrivateNumber
generateMax PrivateNumber
n
  where
    n :: PrivateNumber
n = CurveCommon -> PrivateNumber
ecc_n (CurveCommon -> PrivateNumber) -> CurveCommon -> PrivateNumber
forall a b. (a -> b) -> a -> b
$ Curve -> CurveCommon
common_curve Curve
curve

-- | Generating a public point Q.
calculatePublic :: Curve -> PrivateNumber -> PublicPoint
calculatePublic :: Curve -> PrivateNumber -> PublicPoint
calculatePublic Curve
curve PrivateNumber
d = PublicPoint
q
  where
    g :: PublicPoint
g = CurveCommon -> PublicPoint
ecc_g (CurveCommon -> PublicPoint) -> CurveCommon -> PublicPoint
forall a b. (a -> b) -> a -> b
$ Curve -> CurveCommon
common_curve Curve
curve
    q :: PublicPoint
q = Curve -> PrivateNumber -> PublicPoint -> PublicPoint
pointMul Curve
curve PrivateNumber
d PublicPoint
g

-- | Generating a shared key using our private number and
--   the other party public point.
--
-- This raises the 'Crypto.Error.CryptoError' that 'tryGetShared' reports.  Use
-- 'tryGetShared' where the failure has to be handled.
getShared :: Curve -> PrivateNumber -> PublicPoint -> SharedKey
getShared :: Curve -> PrivateNumber -> PublicPoint -> SharedKey
getShared Curve
curve PrivateNumber
db PublicPoint
qa = CryptoFailable SharedKey -> SharedKey
forall a. CryptoFailable a -> a
throwCryptoError (CryptoFailable SharedKey -> SharedKey)
-> CryptoFailable SharedKey -> SharedKey
forall a b. (a -> b) -> a -> b
$ Curve -> PrivateNumber -> PublicPoint -> CryptoFailable SharedKey
tryGetShared Curve
curve PrivateNumber
db PublicPoint
qa

-- | Generating a shared key using our private number and the other party
--   public point, reporting a rejected point instead of raising.
--
-- The public point comes from the other party, so it is checked before it is
-- multiplied.  A point that does not satisfy the curve equation is reported as
-- 'CryptoError_PointCoordinatesInvalid'.
--
-- Satisfying the equation is not by itself membership of the subgroup the base
-- point generates; the two coincide only when the cofactor is 1.  On a curve
-- whose cofactor is above 1 the other party can offer a point of small order,
-- and the value that comes back then depends on our private number only
-- through its residue modulo that order, which hands them those bits.  So the
-- point is also required to be in the subgroup, by 'isPointInSubgroup', and is
-- reported as 'CryptoError_PointSubgroupInvalid' when it is not.  That check
-- costs one further scalar multiplication, and is skipped where the cofactor
-- is 1 and it cannot fail.
--
-- An exchange that yields the point at infinity, and so has no x coordinate to
-- derive the key from, is reported as 'CryptoError_ScalarMultiplicationInvalid'.
tryGetShared :: Curve -> PrivateNumber -> PublicPoint -> CryptoFailable SharedKey
tryGetShared :: Curve -> PrivateNumber -> PublicPoint -> CryptoFailable SharedKey
tryGetShared Curve
curve PrivateNumber
db PublicPoint
qa
    | Bool -> Bool
not (Curve -> PublicPoint -> Bool
isPointValid Curve
curve PublicPoint
qa) = CryptoError -> CryptoFailable SharedKey
forall a. CryptoError -> CryptoFailable a
CryptoFailed CryptoError
CryptoError_PointCoordinatesInvalid
    | Bool -> Bool
not (Curve -> PublicPoint -> Bool
isPointInSubgroup Curve
curve PublicPoint
qa) = CryptoError -> CryptoFailable SharedKey
forall a. CryptoError -> CryptoFailable a
CryptoFailed CryptoError
CryptoError_PointSubgroupInvalid
    | Bool
otherwise = case Curve -> PrivateNumber -> PublicPoint -> PublicPoint
pointMul Curve
curve PrivateNumber
db PublicPoint
qa of
        Point PrivateNumber
x PrivateNumber
_ -> SharedKey -> CryptoFailable SharedKey
forall a. a -> CryptoFailable a
CryptoPassed (SharedKey -> CryptoFailable SharedKey)
-> SharedKey -> CryptoFailable SharedKey
forall a b. (a -> b) -> a -> b
$ ScrubbedBytes -> SharedKey
SharedKey (ScrubbedBytes -> SharedKey) -> ScrubbedBytes -> SharedKey
forall a b. (a -> b) -> a -> b
$ Int -> PrivateNumber -> ScrubbedBytes
forall ba. ByteArray ba => Int -> PrivateNumber -> ba
i2ospOf_ ((Int
nbBits Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
7) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
8) PrivateNumber
x
        PublicPoint
PointO -> CryptoError -> CryptoFailable SharedKey
forall a. CryptoError -> CryptoFailable a
CryptoFailed CryptoError
CryptoError_ScalarMultiplicationInvalid
  where
    nbBits :: Int
nbBits = Curve -> Int
curveSizeBits Curve
curve