newhope-0.1.0.0: src/Crypto/NewHope/Internal/CPA_KEM.hs
{-# LANGUAGE Safe #-}
{-|
Module : Crypto.NewHope.Internal.CPA_KEM
Description : IND-CPA-secure operations for the NewHope key exchange protocol
Copyright : © Jeremy Bornstein 2019
License : Apache 2.0
Maintainer : jeremy@bornstein.org
Stability : experimental
Portability : portable
IND-CPA-secure operations for the NewHope key exchange protocol. The
algorithm name is either NewHope512-CPAKEM or NewHope1024-CPAKEM,
depending on the value of N.
This module contains the actual implementation. Exposed definitions
are in the 'Crypto.NewHope.CPA_KEM' module.
-}
module Crypto.NewHope.Internal.CPA_KEM where
import Control.DeepSeq
import qualified Data.ByteString as BS
import qualified Crypto.NewHope.CPA_PKE as CPA_PKE
import Crypto.NewHope.FIPS202
import Crypto.NewHope.Internals (N (N1024, N512))
import qualified Crypto.NewHope.Internals as Internals
import Crypto.NewHope.RNG
-- * Counting bytes
-- | Size of a PublicKey
publicKeyBytes :: Internals.N -> Int
publicKeyBytes = CPA_PKE.publicKeyBytes
-- | Size of a SecretKey
secretKeyBytes :: Internals.N -> Int
secretKeyBytes = CPA_PKE.secretKeyBytes
-- | Size of a CipherText
cipherTextBytes :: Internals.N -> Int
cipherTextBytes = CPA_PKE.cipherTextBytes
-- * PublicKey
-- | A public key; also the data sent from the first to the second
-- party in the key exchange.
newtype PublicKey = PublicKey BS.ByteString
-- | We need this instance so that we can deepseq this data for performance tests.
instance NFData PublicKey
where
rnf (PublicKey pkData) = seq pkData ()
-- | Construct a PublicKey from a ByteString of exactly the right
-- size. The result will be a key of N512 or N1024 accordingly.
makePublicKey :: BS.ByteString -> PublicKey
makePublicKey pkData
| not lengthOK = error "Invalid length for PublicKey"
| otherwise = PublicKey pkData
where
len = BS.length pkData
len512 = publicKeyBytes N512
len1024 = publicKeyBytes N1024
lengthOK = len == len512 || len == len1024
-- | The raw data inside the PublicKey
getPKData :: PublicKey -> BS.ByteString
getPKData (PublicKey pkData) = pkData
-- * SecretKey
-- | A secret key; used to derive initiating party's copy of the
-- 'SharedSecret' in combination with a 'CipherText' from the
-- responding party.
newtype SecretKey = SecretKey BS.ByteString
-- | We need this instance so that we can deepseq this data for performance tests.
instance NFData SecretKey
where
rnf (SecretKey skData) = seq skData ()
-- | Construct a SecretKey from a ByteString of exactly the right
-- size. The result will be a key of N512 or N1024 accordingly.
makeSecretKey :: BS.ByteString -> SecretKey
makeSecretKey skData
| not lengthOK = error "Invalid length for SecretKey"
| otherwise = SecretKey skData
where
len = BS.length skData
len512 = secretKeyBytes N512
len1024 = secretKeyBytes N1024
lengthOK = len == len512 || len == len1024
-- | The raw data inside the SecretKey
getSKData :: SecretKey -> BS.ByteString
getSKData (SecretKey skData) = skData
-- * CipherText
-- | Secret data sent from the responding party back to the initiating
-- party, used for initiating party to derive the 'SharedSecret'.
newtype CipherText = CipherText BS.ByteString
-- | We need this instance so that we can deepseq this data for performance tests.
instance NFData CipherText
where
rnf (CipherText ctData) = seq ctData ()
-- | Construct a CipherText from a ByteString of exactly the right
-- size. The result will be of N512 or N1024, accordingly.
makeCipherText :: BS.ByteString -> CipherText
makeCipherText ctData
| not lengthOK = error "Invalid length for CipherText"
| otherwise = CipherText ctData
where
len = BS.length ctData
len512 = cipherTextBytes N512
len1024 = cipherTextBytes N1024
lengthOK = len == len512 || len == len1024
-- | The raw data inside the CipherText
getCTData :: CipherText -> BS.ByteString
getCTData (CipherText ctData) = ctData
-- * SharedSecret
-- | The secret data posessed by both parties, resulting from the key
-- exchange protocol.
newtype SharedSecret = SharedSecret BS.ByteString deriving Eq
-- | We need this instance so that we can deepseq this data for performance tests.
instance NFData SharedSecret
where
rnf (SharedSecret ssData) = seq ssData ()
-- | Construct a SharedSecret from a ByteString of length
-- 'sharedSecretBytes'.
makeSharedSecret :: BS.ByteString -> SharedSecret
makeSharedSecret pkData
| not lengthOK = error "Invalid length for SharedSecret."
| otherwise = SharedSecret pkData
where
goodLength = Internals.sharedSecretBytes
actualLength = BS.length pkData
lengthOK = actualLength == goodLength
-- | The raw data inside the SharedSecret
getSSData :: SharedSecret -> BS.ByteString
getSSData (SharedSecret ssData) = ssData
-- * Top-level operations
-- | The first step of the NewHope key exchange protocol. Called by
-- the initiating party, generates 'PublicKey' and 'SecretKey'. The
-- 'PublicKey' is sent to the receiving party for the next step in the
-- protocol.
keypair :: Context -> Internals.N -> (PublicKey, SecretKey, Context)
keypair ctx n = (pk', sk', ctx')
where
-- Note that this encapsulates keys returned by CPA_PKE.
(pk, sk, ctx') = CPA_PKE.keypair ctx n
pk' = makePublicKey $ CPA_PKE.getPKData pk
sk' = makeSecretKey $ CPA_PKE.getSKPolyData sk
-- | For the provided 'PublicKey', generates a 'CipherText' and
-- 'SharedSecret'. Called by the receiving party, this produces that
-- party's version of the 'SharedSecret' and also the message to
-- transmit to the initiating party ('CipherText').
encrypt :: Context -> PublicKey -> (CipherText, SharedSecret, Context)
encrypt ctx pk = (ct, ss, ctx')
where
symBytes = fromIntegral Internals.symBytes
(buf, ctx') = randomBytes ctx symBytes
(buf0, buf1) = let buf_2sym = shake256 buf (2 * symBytes)
in BS.splitAt symBytes buf_2sym -- we use them separately; coins are buf1 and buf0 used as cleartext
ct = let ct' = let pk' = CPA_PKE.makePublicKeyFromBytes $ getPKData pk
in CPA_PKE.encrypt (CPA_PKE.makePlainText buf0) pk' (Internals.makeSeed buf1)
in makeCipherText $ CPA_PKE.getCTData ct'
ss = let ss' = shake256 buf0 symBytes -- hash pre-k to ss
in makeSharedSecret ss'
-- | Called by the party initiating the protocol, this function
-- generates the 'SharedSecret' for the given 'CipherText' and
-- 'SecretKey'. The result is the initiating party's copy of the
-- 'SecretKey'. (In terms of encryption functions per se, it is also a
-- cleartext value.)
decrypt :: CipherText -> SecretKey -> SharedSecret
decrypt ct sk = SharedSecret ss
where
ss = let ss' = let ct' = CPA_PKE.makeCipherTextFromBytes $ getCTData ct
sk' = CPA_PKE.makeSecretKeyFromBytes $ getSKData sk
in CPA_PKE.decrypt ct' sk'
symBytes = fromIntegral Internals.symBytes
in shake256 (CPA_PKE.getPTData ss') symBytes -- hash pre-k to ss