content: {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, DeriveGeneric, ForeignFunctionInterface #-}
  -- |
  -- Module      : Crypto.Saltine.Internal.Box
  -- Copyright   : (c) Max Amanshauser 2021
  -- License     : MIT
  --
  -- Maintainer  : max@lambdalifting.org
  -- Stability   : experimental
  -- Portability : non-portable
  --
  module Crypto.Saltine.Internal.Box (
      box_publickeybytes
    , box_secretkeybytes
    , box_noncebytes
    , box_zerobytes
    , box_boxzerobytes
    , box_macbytes
    , box_beforenmbytes
    , box_sealbytes
    , c_box_keypair
    , c_box_easy
    , c_box_open_easy
    , c_box_beforenm
    , c_box_easy_afternm
    , c_box_open_easy_afternm
    , c_box_seal
    , c_box_seal_open
    , SecretKey(..)
    , PublicKey(..)
    , Keypair(..)
    , CombinedKey(..)
    , Nonce(..)
  ) where
  
  import Control.DeepSeq              (NFData)
  import Crypto.Saltine.Class
  import Crypto.Saltine.Core.Hash     (shorthash)
  import Crypto.Saltine.Internal.Hash (nullShKey)
  import Crypto.Saltine.Internal.Util as U
  import Data.ByteString              (ByteString)
  import Data.Data                    (Data, Typeable)
  import Data.Hashable                (Hashable)
  import Data.Monoid
  import Foreign.C
  import Foreign.Ptr
  import GHC.Generics                 (Generic)
  
  import qualified Data.ByteString as S
  
  -- | An opaque 'box' cryptographic secret key.
  newtype SecretKey = SK { unSK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData)
  instance Eq SecretKey where
      SK a == SK b = U.compare a b
  instance Show SecretKey where
      show k = "Box.SecretKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}"
  
  instance IsEncoding SecretKey where
    decode v = if S.length v == box_secretkeybytes
             then Just (SK v)
             else Nothing
    {-# INLINE decode #-}
    encode (SK v) = v
    {-# INLINE encode #-}
  
  -- | An opaque 'box' cryptographic public key.
  newtype PublicKey = PK { unPK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData)
  instance Eq PublicKey where
      PK a == PK b = U.compare a b
  instance Show PublicKey where
      show k = "Box.PublicKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}"
  
  instance IsEncoding PublicKey where
    decode v = if S.length v == box_publickeybytes
             then Just (PK v)
             else Nothing
    {-# INLINE decode #-}
    encode (PK v) = v
    {-# INLINE encode #-}
  
  -- | A convenience type for keypairs
  data Keypair = Keypair {
      secretKey :: SecretKey
    , publicKey :: PublicKey
  } deriving (Ord, Data, Typeable, Generic)
  
  instance Eq Keypair where
      kp1 == kp2 = U.compare (encode $ secretKey kp1) (encode $ secretKey kp2)
              !&&! U.compare (encode $ publicKey kp1) (encode $ publicKey kp2)
  
  instance Show Keypair where
      show k = "Box.Keypair {secretKey = " <> show (secretKey k) <> ", publicKey = " <> show (publicKey k) <> "}"
  
  instance Hashable Keypair
  instance NFData   Keypair
  
  -- | An opaque 'boxAfterNM' cryptographic combined key.
  newtype CombinedKey = CK { unCK :: ByteString } deriving (Ord, Hashable, Data, Typeable, Generic, NFData)
  instance Eq CombinedKey where
      CK a == CK b = U.compare a b
  instance Show CombinedKey where
      show k = "Box.CombinedKey {hashesTo = \"" <> (bin2hex . shorthash nullShKey $ encode k) <> "\"}"
  
  instance IsEncoding CombinedKey where
    decode v = if S.length v == box_beforenmbytes
             then Just (CK v)
             else Nothing
    {-# INLINE decode #-}
    encode (CK v) = v
    {-# INLINE encode #-}
  
  -- | An opaque 'box' nonce.
  newtype Nonce = Nonce { unNonce :: ByteString } deriving (Eq, Ord, Hashable, Data, Typeable, Generic, NFData)
  instance Show Nonce where
      show k = "Box.Nonce " <> bin2hex (encode k)
  
  instance IsEncoding Nonce where
    decode v = if S.length v == box_noncebytes
             then Just (Nonce v)
             else Nothing
    {-# INLINE decode #-}
    encode (Nonce v) = v
    {-# INLINE encode #-}
  
  instance IsNonce Nonce where
    zero            = Nonce (S.replicate box_noncebytes 0)
    nudge (Nonce n) = Nonce (nudgeBS n)
  
  
  box_publickeybytes, box_secretkeybytes, box_noncebytes, box_zerobytes, box_boxzerobytes :: Int
  box_macbytes, box_beforenmbytes, box_sealbytes :: Int
  
  -- Box
  -- | Size of a @crypto_box@ public key
  box_publickeybytes  = fromIntegral c_crypto_box_publickeybytes
  -- | Size of a @crypto_box@ secret key
  box_secretkeybytes  = fromIntegral c_crypto_box_secretkeybytes
  -- | Size of a @crypto_box@ nonce
  box_noncebytes      = fromIntegral c_crypto_box_noncebytes
  -- | Size of 0-padding prepended to messages before using @crypto_box@
  -- or after using @crypto_box_open@
  box_zerobytes       = fromIntegral c_crypto_box_zerobytes
  -- | Size of 0-padding prepended to ciphertext before using
  -- @crypto_box_open@ or after using @crypto_box@.
  box_boxzerobytes    = fromIntegral c_crypto_box_boxzerobytes
  box_macbytes        = fromIntegral c_crypto_box_macbytes
  -- | Size of a @crypto_box_beforenm@-generated combined key
  box_beforenmbytes   = fromIntegral c_crypto_box_beforenmbytes
  
  -- SealedBox
  -- | Amount by which ciphertext is longer than plaintext
  -- in sealed boxes
  box_sealbytes       = fromIntegral c_crypto_box_sealbytes
  
  -- src/libsodium/crypto_box/crypto_box.c
  foreign import ccall "crypto_box_publickeybytes"
    c_crypto_box_publickeybytes :: CSize
  foreign import ccall "crypto_box_secretkeybytes"
    c_crypto_box_secretkeybytes :: CSize
  foreign import ccall "crypto_box_beforenmbytes"
    c_crypto_box_beforenmbytes :: CSize
  foreign import ccall "crypto_box_noncebytes"
    c_crypto_box_noncebytes :: CSize
  foreign import ccall "crypto_box_zerobytes"
    c_crypto_box_zerobytes :: CSize
  foreign import ccall "crypto_box_boxzerobytes"
    c_crypto_box_boxzerobytes :: CSize
  foreign import ccall "crypto_box_macbytes"
    c_crypto_box_macbytes :: CSize
  
  -- src/libsodium/crypto_box_seal.c
  foreign import ccall "crypto_box_sealbytes"
    c_crypto_box_sealbytes :: CSize
  
  -- | Should always return a 0.
  foreign import ccall "crypto_box_keypair"
    c_box_keypair :: Ptr CChar
                  -- ^ Public key
                  -> Ptr CChar
                  -- ^ Secret key
                  -> IO CInt
                  -- ^ Always 0
  
  -- | The secretbox C API uses C strings.
  foreign import ccall "crypto_box_easy"
    c_box_easy :: Ptr CChar
               -- ^ Cipher output buffer
               -> Ptr CChar
               -- ^ Constant message input buffer
               -> CULLong
               -- ^ Length of message input buffer
               -> Ptr CChar
               -- ^ Constant nonce buffer
               -> Ptr CChar
               -- ^ Constant public key buffer
               -> Ptr CChar
               -- ^ Constant secret key buffer
               -> IO CInt
               -- ^ Always 0
  
  -- | The secretbox C API uses C strings.
  foreign import ccall "crypto_box_open_easy"
    c_box_open_easy :: Ptr CChar
                    -- ^ Message output buffer
                    -> Ptr CChar
                    -- ^ Constant ciphertext input buffer
                    -> CULLong
                    -- ^ Length of message input buffer
                    -> Ptr CChar
                    -- ^ Constant nonce buffer
                    -> Ptr CChar
                    -- ^ Constant public key buffer
                    -> Ptr CChar
                    -- ^ Constant secret key buffer
                    -> IO CInt
                    -- ^ 0 for success, -1 for failure to verify
  
  -- | Single target key precompilation.
  foreign import ccall "crypto_box_beforenm"
    c_box_beforenm :: Ptr CChar
                   -- ^ Combined key output buffer
                   -> Ptr CChar
                   -- ^ Constant public key buffer
                   -> Ptr CChar
                   -- ^ Constant secret key buffer
                   -> IO CInt
                   -- ^ Always 0
  
  -- | Precompiled key crypto box. Uses C strings.
  foreign import ccall "crypto_box_easy_afternm"
    c_box_easy_afternm :: Ptr CChar
                       -- ^ Cipher output buffer
                       -> Ptr CChar
                       -- ^ Constant message input buffer
                       -> CULLong
                       -- ^ Length of message input buffer (incl. 0s)
                       -> Ptr CChar
                       -- ^ Constant nonce buffer
                       -> Ptr CChar
                       -- ^ Constant combined key buffer
                       -> IO CInt
                       -- ^ Always 0
  
  -- | The secretbox C API uses C strings.
  foreign import ccall "crypto_box_open_easy_afternm"
    c_box_open_easy_afternm :: Ptr CChar
                            -- ^ Message output buffer
                            -> Ptr CChar
                            -- ^ Constant ciphertext input buffer
                            -> CULLong
                            -- ^ Length of message input buffer (incl. 0s)
                            -> Ptr CChar
                            -- ^ Constant nonce buffer
                            -> Ptr CChar
                            -- ^ Constant combined key buffer
                            -> IO CInt
                            -- ^ 0 for success, -1 for failure to verify
  
  
  -- | The sealedbox C API uses C strings.
  foreign import ccall "crypto_box_seal"
    c_box_seal :: Ptr CChar
               -- ^ Cipher output buffer
               -> Ptr CChar
               -- ^ Constant message input buffer
               -> CULLong
               -- ^ Length of message input buffer
               -> Ptr CChar
               -- ^ Constant public key buffer
               -> IO CInt
               -- ^ Always 0
  
  -- | The sealedbox C API uses C strings.
  foreign import ccall "crypto_box_seal_open"
    c_box_seal_open :: Ptr CChar
                    -- ^ Message output buffer
                    -> Ptr CChar
                    -- ^ Constant ciphertext input buffer
                    -> CULLong
                    -- ^ Length of message input buffer
                    -> Ptr CChar
                    -- ^ Constant public key buffer
                    -> Ptr CChar
                    -- ^ Constant secret key buffer
                    -> IO CInt
                    -- ^ 0 for success, -1 for failure to decrypt
  