\section{Key}

\begin{code}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE DeriveDataTypeable  #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving  #-}
{-# LANGUAGE Trustworthy         #-}
module Network.Tox.Crypto.Key where

import           Control.Applicative               ((<$>))
import           Control.Monad                     ((>=>))
import qualified Crypto.Saltine.Class              as Sodium (IsEncoding,
                                                              decode, encode)
import qualified Crypto.Saltine.Core.Box           as Sodium (CombinedKey,
                                                              Nonce, PublicKey,
                                                              SecretKey)
import qualified Crypto.Saltine.Internal.ByteSizes as Sodium (boxBeforeNM,
                                                              boxNonce, boxPK,
                                                              boxSK)
import           Data.Binary                       (Binary)
import qualified Data.Binary                       as Binary (get, put)
import qualified Data.Binary.Get                   as Binary (getByteString,
                                                              runGet)
import qualified Data.Binary.Put                   as Binary (putByteString)
import qualified Data.ByteString                   as ByteString
import qualified Data.ByteString.Base16            as Base16
import qualified Data.ByteString.Lazy              as LazyByteString
import           Data.MessagePack                  (MessagePack (..))
import           Data.Proxy                        (Proxy (..))
import           Data.Typeable                     (Typeable)
import           Test.QuickCheck.Arbitrary         (Arbitrary, arbitrary)
import qualified Test.QuickCheck.Arbitrary         as Arbitrary
import           Text.Read                         (readPrec)


{-------------------------------------------------------------------------------
 -
 - :: Implementation.
 -
 ------------------------------------------------------------------------------}

\end{code}

A Crypto Number is a large fixed size unsigned (non-negative) integer.  Its binary
encoding is as a Big Endian integer in exactly the encoded byte size.  Its
human-readable encoding is as a base-16 number encoded as String.  The NaCl
implementation \href{https://github.com/jedisct1/libsodium}{libsodium} supplies
the functions \texttt{sodium\_bin2hex} and \texttt{sodium\_hex2bin} to aid in
implementing the human-readable encoding.  The in-memory encoding of these
crypto numbers in NaCl already satisfies the binary encoding, so for
applications directly using those APIs, binary encoding and decoding is the
\href{https://en.wikipedia.org/wiki/Identity_function}{identity function}.

\begin{code}

class Sodium.IsEncoding a => CryptoNumber a where
  encodedByteSize :: Proxy a -> Int

\end{code}

Tox uses four kinds of Crypto Numbers:

\begin{tabular}{l|l|l}
  Type         & Bits & Encoded byte size \\
  \hline
  Public Key   & 256  & 32 \\
  Secret Key   & 256  & 32 \\
  Combined Key & 256  & 32 \\
  Nonce        & 192  & 24 \\
\end{tabular}

\begin{code}

instance CryptoNumber Sodium.PublicKey   where { encodedByteSize Proxy = Sodium.boxPK       }
instance CryptoNumber Sodium.SecretKey   where { encodedByteSize Proxy = Sodium.boxSK       }
instance CryptoNumber Sodium.CombinedKey where { encodedByteSize Proxy = Sodium.boxBeforeNM }
instance CryptoNumber Sodium.Nonce       where { encodedByteSize Proxy = Sodium.boxNonce    }

deriving instance Typeable Sodium.PublicKey
deriving instance Typeable Sodium.SecretKey
deriving instance Typeable Sodium.CombinedKey
deriving instance Typeable Sodium.Nonce

newtype Key a = Key { unKey :: a }
  deriving (Eq, Ord, Typeable)

type PublicKey   = Key Sodium.PublicKey
type SecretKey   = Key Sodium.SecretKey
type CombinedKey = Key Sodium.CombinedKey
type Nonce       = Key Sodium.Nonce

instance Sodium.IsEncoding a => Sodium.IsEncoding (Key a) where
  encode = Sodium.encode . unKey
  decode = fmap Key . Sodium.decode


keyToInteger :: Sodium.IsEncoding a => Key a -> Integer
keyToInteger =
  Binary.runGet Binary.get . encode
  where
    prefix = LazyByteString.pack
      [ 0x01 -- Tag: big integer
      , 0x01 -- Sign: positive
      , 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20 -- Length: 32 bytes
      ]
    encode =
      LazyByteString.append prefix
        . LazyByteString.reverse
        . LazyByteString.fromStrict
        . Sodium.encode


decode :: (CryptoNumber a, Monad m) => ByteString.ByteString -> m (Key a)
decode bytes =
  case Sodium.decode bytes of
    Just key -> return $ Key key
    Nothing  -> fail "unable to decode ByteString to Key"


instance CryptoNumber a => Binary (Key a) where
  put (Key key) =
    Binary.putByteString $ Sodium.encode key

  get = do
    bytes <- Binary.getByteString $ encodedByteSize (Proxy :: Proxy a)
    decode bytes


instance CryptoNumber a => Show (Key a) where
  show (Key key) = show $ Base16.encode $ Sodium.encode key

instance CryptoNumber a => Read (Key a) where
  readPrec = fst . Base16.decode <$> readPrec >>= decode

instance CryptoNumber a => MessagePack (Key a) where
  toObject = toObject . Sodium.encode
  fromObject = fromObject >=> decode


{-------------------------------------------------------------------------------
 -
 - :: Tests.
 -
 ------------------------------------------------------------------------------}


instance CryptoNumber a => Arbitrary (Key a) where
  arbitrary = do
    bytes <- fmap ByteString.pack $ Arbitrary.vector $ encodedByteSize (Proxy :: Proxy a)
    decode bytes
\end{code}