newhope-0.1.0.0: src/Crypto/NewHope/CPA_PKE.hs
{-# LANGUAGE Safe #-}
{-|
Module : Crypto.NewHope.CPA_PKE
Description : IND-CPA-secure key encapsulation 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 key encapsulation for the NewHope key exchange protocol
-}
module Crypto.NewHope.CPA_PKE ( keypair
, encrypt
, decrypt
, publicKeyBytes
, secretKeyBytes
, cipherTextBytes
, PublicKey
, makePublicKey
, makePublicKeyFromBytes
, getPKData
, getPKPolyData
, getPKPoly
, getPKSeed
, SecretKey
, makeSecretKey
, makeSecretKeyFromBytes
, getSKPoly
, getSKPolyData
, CipherText
, makeCipherTextFromBytes -- need to do this when decrypting
, getCTData
, getCTb
, getCTbData
, getCTv
, makePlainText -- we use Plain instead of Clear so we have distinct abbreviations! i.e. pt != ct
, getPTData
) where
import Control.Applicative
import qualified Data.ByteString as BS
import Crypto.NewHope.FIPS202
import Crypto.NewHope.Internals (N (N1024, N512), symBytes)
import qualified Crypto.NewHope.Internals as Internals
import Crypto.NewHope.Poly (Poly)
import qualified Crypto.NewHope.Poly as Poly
import Crypto.NewHope.RNG
import StringUtils
-- * Counting bytes
-- | Size in bytes of a PublicKey
publicKeyBytes :: Internals.N -> Int
publicKeyBytes n = Poly.polyBytes n + symBytes
-- | Size in bytes of a SecretKey
secretKeyBytes :: Internals.N -> Int
secretKeyBytes = Poly.polyBytes
-- | Size in bytes of a CipherText
cipherTextBytes :: Internals.N -> Int
cipherTextBytes = liftA2 (+) Poly.polyBytes Poly.polyCompressedBytes
-- * PlainText
-- | This is a "msg" encoded from a Poly. Since these are of constant
-- length even for different N, when restoring the Poly we need to
-- know N from elsewhere.
newtype PlainText = PlainText BS.ByteString deriving Eq
-- | Construct PlainText from ByteString
makePlainText :: BS.ByteString -> PlainText
makePlainText bs
| not lengthOK = error "Invalid length for PlainText"
| otherwise = PlainText bs
where
len = BS.length bs
lengthOK = len == Poly.polyMsgBytes
-- | The raw data inside a PlainText
getPTData :: PlainText -> BS.ByteString
getPTData (PlainText ptData) = ptData
-- | The encoded Poly inside a PlainText
getPTv :: PlainText -> Internals.N -> Poly
getPTv (PlainText ptData) n = Poly.fromMsg n ptData
-- * PublicKey
newtype PublicKey = PublicKey BS.ByteString deriving Eq
-- | The N associated with this key
getPKn :: PublicKey -> Internals.N
getPKn (PublicKey pkData)
| len == publicKeyBytes N512 = N512
| len == publicKeyBytes N1024 = N1024
| otherwise = error "Invalid N for PublicKey"
where
len = BS.length pkData
-- | In terms of the NewHope protocol, our PublicKey data is a
-- concatenation of the serialization of the Poly pk and the
-- public seed which generated the Poly a.
makePublicKey :: Poly -> Internals.Seed -> PublicKey
makePublicKey poly seed
| not lengthOK = error "Invalid imputed length for PublicKey"
| otherwise = PublicKey bs
where
bs = BS.append poly' seed'
poly' = Poly.toByteString poly
seed' = Internals.getSeedData seed
n = Poly.getN poly
lengthOK = BS.length bs == publicKeyBytes n
-- | Construct PublicKey from ByteString
makePublicKeyFromBytes :: BS.ByteString -> PublicKey
makePublicKeyFromBytes pkData
| not lengthOK = error "Invalid length for PublicKey"
| otherwise = PublicKey pkData
where
len = BS.length pkData
lengthOK = len == publicKeyBytes N512 || len == publicKeyBytes N1024
-- | The raw data inside a PublicKey
getPKData :: PublicKey -> BS.ByteString
getPKData (PublicKey pkData) = pkData
-- | The length of the encoded Poly pk in our encoded data.
getPKPolyBytes :: PublicKey -> Int
getPKPolyBytes pk = Poly.polyBytes $ getPKn pk
-- | The raw data encoding the Poly pk
getPKPolyData :: PublicKey -> BS.ByteString
getPKPolyData pk@(PublicKey pkData) = encoded
where
polyBytes = getPKPolyBytes pk
encoded = BS.take polyBytes pkData
-- | The decoded Poly pk
getPKPoly :: PublicKey -> Poly
getPKPoly pk@(PublicKey pkData) = Poly.fromByteString encoded
where
polyBytes = getPKPolyBytes pk
encoded = BS.take polyBytes pkData
-- | The Seed
getPKSeed :: PublicKey -> Internals.Seed
getPKSeed (PublicKey pkData) = Internals.makeSeed seedData
where
offset = BS.length pkData - symBytes
seedData = BS.drop offset pkData
-- * SecretKey
newtype SecretKey = SecretKey BS.ByteString deriving Eq
-- | Construct from a Poly
makeSecretKey :: Poly -> SecretKey
makeSecretKey poly
| not lengthOK = error "Invalid imputed N for SecretKey"
| otherwise = SecretKey bs
where
n = Poly.getN poly
bs = Poly.toByteString poly
lengthOK = BS.length bs == secretKeyBytes n
-- | Construct from a ByteString, which must contain a serialized Poly.
makeSecretKeyFromBytes :: BS.ByteString -> SecretKey
makeSecretKeyFromBytes bs
| not lengthOK = error "Invalid length for SecretKey"
| otherwise = SecretKey bs
where
len512 = secretKeyBytes N512
len1024 = secretKeyBytes N1024
len = BS.length bs
lengthOK = len == len512 || len == len1024
-- | The N associated with this key. Prefixed with _ because nothing
-- uses this now, but it's good to have it here to document the
-- structure/contents and for possible future testing.
_getSKn :: SecretKey -> Internals.N
_getSKn (SecretKey skData)
| len == len512 = N512
| len == len1024 = N1024
| otherwise = error "Invalid N for SecretKey"
where
len = BS.length skData
len512 = secretKeyBytes N512
len1024 = secretKeyBytes N1024
-- | The encoded Poly data, which also happens to be all of our data
getSKPolyData :: SecretKey -> BS.ByteString
getSKPolyData (SecretKey sk) = sk
-- | The encapsulated Poly
getSKPoly :: SecretKey -> Poly
getSKPoly (SecretKey sk) = Poly.fromByteString sk
-- * CipherText
newtype CipherText = CipherText BS.ByteString deriving Eq
-- TDOD: Useful for debugging. Should we retain it in the distribution?
instance Show CipherText where
show (CipherText bs) = "CipherText: " ++ byteStringToHexString bs
-- | Construct a CipherText from two Polys.
makeCipherText :: Poly -> Poly -> CipherText
makeCipherText b v
| not lengthOK = error "Invalid imputed length for CipherText"
| otherwise = CipherText bs
where
b' = Poly.toByteString b
v' = Poly.compress v
bs = BS.append b' v'
lengthOK = let bn = Poly.getN b
vn = Poly.getN v
in (bn == vn) && (BS.length bs == cipherTextBytes bn)
-- | Construct a CipherText from the data in a ByteString
makeCipherTextFromBytes :: BS.ByteString -> CipherText
makeCipherTextFromBytes bs
| not lengthOK = error "Invalid length for CipherText"
| otherwise = CipherText bs
where
len = BS.length bs
len512 = cipherTextBytes N512
len1024 = cipherTextBytes N1024
lengthOK = len == len512 || len == len1024
-- | The N associated with this CipherText
getCTn :: CipherText -> Internals.N
getCTn (CipherText ctData)
| len == len512 = N512
| len == len1024 = N1024
| otherwise = error "Invalid N for CipherText"
where
len = BS.length ctData
len512 = cipherTextBytes N512
len1024 = cipherTextBytes N1024
-- | The data encoding the encapsulated Poly b
getCTbData :: CipherText -> BS.ByteString
getCTbData ct@(CipherText ctData) = polyData
where
polyData = BS.take polyBytes ctData
n = getCTn ct
polyBytes = Poly.polyBytes n
-- | The encapsulated Poly b
getCTb :: CipherText -> Poly
getCTb = Poly.fromByteString . getCTbData
-- | The data encoding the encapsulated Poly v
getCTData :: CipherText -> BS.ByteString
getCTData (CipherText ctData) = ctData
-- | The encapsulated Poly v
getCTv :: CipherText -> Poly
getCTv ct@(CipherText ctData) = Poly.decompress polyData
where
polyData = BS.drop polyBytes ctData
n = getCTn ct
polyBytes = Poly.polyBytes n
-- | Deterministically generate public Poly a from Seed. This is just
-- `Poly.uniform` but remains here for some degree of isomorphism with
-- the reference library.
genA :: Internals.N -> Internals.Seed -> Poly
genA = Poly.uniform
-- * Higher-level functions
-- | Generates a new keypair, along with the post-utilization Context.
keypair :: Context -> Internals.N -> (PublicKey, SecretKey, Context)
keypair ctx n = (pk, sk, ctx')
where
(z, ctx') = randomBytes ctx symBytes
(publicSeed, noiseSeed) = BS.splitAt symBytes $ shake256 z (2 * symBytes)
shat = Poly.ntt $ Poly.sample n (Internals.makeSeed noiseSeed) 0
ehat = Poly.ntt $ Poly.sample n (Internals.makeSeed noiseSeed) 1
sk = makeSecretKey shat
pk = let ahatShat = let ahat = genA n $ Internals.makeSeed publicSeed
in Poly.mulPointwise shat ahat
bhat = Poly.add ehat ahatShat
in makePublicKey bhat $ Internals.makeSeed publicSeed
-- | Encrypt PlainText to CipherText
encrypt :: PlainText -> PublicKey -> Internals.Seed -> CipherText
encrypt pt pk coin = makeCipherText uhat vprime
where
n = getPKn pk
v = getPTv pt n
bhat = getPKPoly pk
publicSeed = getPKSeed pk
sprime = Poly.ntt $ Poly.sample n coin 0
eprime = Poly.ntt $ Poly.sample n coin 1
uhat = Poly.add eprime $ Poly.mulPointwise sprime (genA n publicSeed)
vprime = Poly.add v $ Poly.add (Poly.sample n coin 2)
(Poly.invntt $ Poly.mulPointwise bhat sprime)
-- | Decrypt CipherText to PlainText.
decrypt :: CipherText -> SecretKey -> PlainText
decrypt c sk = PlainText msg
where
shat = getSKPoly sk
uhat = getCTb c
vprime = getCTv c
msg = Poly.toMsg $ Poly.sub (Poly.invntt $ Poly.mulPointwise shat uhat) vprime