packages feed

sel-0.0.1.0: src/Sel/PublicKey/Signature.hs

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- |
--
-- Module: Sel.PublicKey.Signature
-- Description: Public-key signatures with the Ed25519 algorithm
-- Copyright: (C) Hécate Moonlight 2022
-- License: BSD-3-Clause
-- Maintainer: The Haskell Cryptography Group
-- Portability: GHC only
module Sel.PublicKey.Signature
  ( -- ** Introduction
    -- $introduction
    PublicKey
  , SecretKey
  , SignedMessage

    -- ** Key Pair generation
  , generateKeyPair

    -- ** Message Signing
  , signMessage
  , openMessage

    -- ** Constructing and Deconstructing
  , getSignature
  , unsafeGetMessage
  , mkSignature
  )
where

import Control.Monad (void)
import Data.ByteString (StrictByteString)
import Data.ByteString.Unsafe (unsafePackMallocCStringLen)
import qualified Data.ByteString.Unsafe as ByteString
import Foreign
  ( ForeignPtr
  , Ptr
  , castPtr
  , mallocBytes
  , mallocForeignPtrBytes
  , withForeignPtr
  )
import Foreign.C (CChar, CSize, CUChar, CULLong)
import qualified Foreign.Marshal.Array as Foreign
import qualified Foreign.Ptr as Foreign
import GHC.IO.Handle.Text (memcpy)
import System.IO.Unsafe (unsafeDupablePerformIO)

import LibSodium.Bindings.CryptoSign
  ( cryptoSignBytes
  , cryptoSignDetached
  , cryptoSignKeyPair
  , cryptoSignPublicKeyBytes
  , cryptoSignSecretKeyBytes
  , cryptoSignVerifyDetached
  )
import Sel.Internal

-- $introduction
--
-- Public-key Signatures work with a 'SecretKey' and 'PublicKey'
--
-- * The 'SecretKey' is used to append a signature to any number of messages. It must stay private;
-- * The 'PublicKey' is used by third-parties to to verify that the signature appended to a message was
-- issued by the creator of the public key. It must be distributed to third-parties.
--
-- Verifiers need to already know and ultimately trust a public key before messages signed
-- using it can be verified.

-- |
--
-- @since 0.0.1.0
newtype PublicKey = PublicKey (ForeignPtr CUChar)

-- |
--
-- @since 0.0.1.0
instance Eq PublicKey where
  (PublicKey pk1) == (PublicKey pk2) =
    unsafeDupablePerformIO $
      foreignPtrEq pk1 pk2 cryptoSignPublicKeyBytes

-- |
--
-- @since 0.0.1.0
instance Ord PublicKey where
  compare (PublicKey pk1) (PublicKey pk2) =
    unsafeDupablePerformIO $
      foreignPtrOrd pk1 pk2 cryptoSignPublicKeyBytes

-- |
--
-- @since 0.0.1.0
newtype SecretKey = SecretKey (ForeignPtr CUChar)

-- |
--
-- @since 0.0.1.0
instance Eq SecretKey where
  (SecretKey sk1) == (SecretKey sk2) =
    unsafeDupablePerformIO $
      foreignPtrEq sk1 sk2 cryptoSignSecretKeyBytes

-- |
--
-- @since 0.0.1.0
instance Ord SecretKey where
  compare (SecretKey sk1) (SecretKey sk2) =
    unsafeDupablePerformIO $
      foreignPtrOrd sk1 sk2 cryptoSignSecretKeyBytes

-- |
--
-- @since 0.0.1.0
data SignedMessage = SignedMessage
  { messageLength :: CSize
  , messageForeignPtr :: ForeignPtr CUChar
  , signatureForeignPtr :: ForeignPtr CUChar
  }

-- |
--
-- @since 0.0.1.0
instance Eq SignedMessage where
  (SignedMessage len1 msg1 sig1) == (SignedMessage len2 msg2 sig2) =
    unsafeDupablePerformIO $ do
      result1 <- foreignPtrEq msg1 msg2 len1
      result2 <- foreignPtrEq sig1 sig2 cryptoSignBytes
      return $ (len1 == len2) && result1 && result2

-- |
--
-- @since 0.0.1.0
instance Ord SignedMessage where
  compare (SignedMessage len1 msg1 sig1) (SignedMessage len2 msg2 sig2) =
    unsafeDupablePerformIO $ do
      result1 <- foreignPtrOrd msg1 msg2 len1
      result2 <- foreignPtrOrd sig1 sig2 cryptoSignBytes
      return $ compare len1 len2 <> result1 <> result2

-- | Generate a pair of public and secret key.
--
-- The length parameters used are 'cryptoSignPublicKeyBytes'
-- and 'cryptoSignSecretKeyBytes'.
--
-- @since 0.0.1.0
generateKeyPair :: IO (PublicKey, SecretKey)
generateKeyPair = do
  publicKeyForeignPtr <- mallocForeignPtrBytes (fromIntegral @CSize @Int cryptoSignPublicKeyBytes)
  secretKeyForeignPtr <- mallocForeignPtrBytes (fromIntegral @CSize @Int cryptoSignSecretKeyBytes)
  withForeignPtr publicKeyForeignPtr $ \pkPtr ->
    withForeignPtr secretKeyForeignPtr $ \skPtr ->
      void $
        cryptoSignKeyPair
          pkPtr
          skPtr
  pure (PublicKey publicKeyForeignPtr, SecretKey secretKeyForeignPtr)

-- | Sign a message.
--
-- @since 0.0.1.0
signMessage :: StrictByteString -> SecretKey -> IO SignedMessage
signMessage message (SecretKey skFPtr) =
  ByteString.unsafeUseAsCStringLen message $ \(cString, messageLength) -> do
    let sigLength = fromIntegral @CSize @Int cryptoSignBytes
    (messageForeignPtr :: ForeignPtr CUChar) <- Foreign.mallocForeignPtrBytes messageLength
    signatureForeignPtr <- Foreign.mallocForeignPtrBytes sigLength
    withForeignPtr messageForeignPtr $ \messagePtr ->
      withForeignPtr signatureForeignPtr $ \signaturePtr ->
        withForeignPtr skFPtr $ \skPtr -> do
          Foreign.copyArray messagePtr (Foreign.castPtr @CChar @CUChar cString) messageLength
          void $
            cryptoSignDetached
              signaturePtr
              Foreign.nullPtr -- Always of size 'cryptoSignBytes'
              (castPtr @CChar @CUChar cString)
              (fromIntegral @Int @CULLong messageLength)
              skPtr
    pure $ SignedMessage (fromIntegral @Int @CSize messageLength) messageForeignPtr signatureForeignPtr

-- | Open a signed message with the signatory's public key.
-- The function returns 'Nothing' if there is a key mismatch.
--
-- @since 0.0.1.0
openMessage :: SignedMessage -> PublicKey -> Maybe StrictByteString
openMessage SignedMessage{messageLength, messageForeignPtr, signatureForeignPtr} (PublicKey pkForeignPtr) = unsafeDupablePerformIO $
  withForeignPtr pkForeignPtr $ \publicKeyPtr ->
    withForeignPtr signatureForeignPtr $ \signaturePtr -> do
      withForeignPtr messageForeignPtr $ \messagePtr -> do
        result <-
          cryptoSignVerifyDetached
            signaturePtr
            messagePtr
            (fromIntegral @CSize @CULLong messageLength)
            publicKeyPtr
        case result of
          (-1) -> pure Nothing
          _ -> do
            bsPtr <- mallocBytes (fromIntegral messageLength)
            memcpy bsPtr (castPtr messagePtr) messageLength
            Just <$> unsafePackMallocCStringLen (castPtr bsPtr :: Ptr CChar, fromIntegral messageLength)

-- | Get the signature part of a 'SignedMessage'.
--
-- @since 0.0.1.0
getSignature :: SignedMessage -> StrictByteString
getSignature SignedMessage{signatureForeignPtr} = unsafeDupablePerformIO $
  withForeignPtr signatureForeignPtr $ \signaturePtr -> do
    bsPtr <- Foreign.mallocBytes (fromIntegral cryptoSignBytes)
    memcpy bsPtr signaturePtr cryptoSignBytes
    unsafePackMallocCStringLen (Foreign.castPtr bsPtr :: Ptr CChar, fromIntegral cryptoSignBytes)

-- | Get the message part of a 'SignedMessage' __without verifying the signature__.
--
-- @since 0.0.1.0
unsafeGetMessage :: SignedMessage -> StrictByteString
unsafeGetMessage SignedMessage{messageLength, messageForeignPtr} = unsafeDupablePerformIO $
  withForeignPtr messageForeignPtr $ \messagePtr -> do
    bsPtr <- Foreign.mallocBytes (fromIntegral messageLength)
    memcpy bsPtr messagePtr messageLength
    unsafePackMallocCStringLen (Foreign.castPtr bsPtr :: Ptr CChar, fromIntegral messageLength)

-- | Combine a message and a signature into a 'SignedMessage'.
--
-- @since 0.0.1.0
mkSignature :: StrictByteString -> StrictByteString -> SignedMessage
mkSignature message signature = unsafeDupablePerformIO $
  ByteString.unsafeUseAsCStringLen message $ \(messageStringPtr, messageLength) ->
    ByteString.unsafeUseAsCStringLen signature $ \(signatureStringPtr, _) -> do
      (messageForeignPtr :: ForeignPtr CUChar) <- Foreign.mallocForeignPtrBytes messageLength
      signatureForeignPtr <- Foreign.mallocForeignPtrBytes (fromIntegral cryptoSignBytes)
      withForeignPtr messageForeignPtr $ \messagePtr ->
        withForeignPtr signatureForeignPtr $ \signaturePtr -> do
          Foreign.copyArray messagePtr (Foreign.castPtr messageStringPtr) messageLength
          Foreign.copyArray signaturePtr (Foreign.castPtr signatureStringPtr) (fromIntegral cryptoSignBytes)
      pure $ SignedMessage (fromIntegral @Int @CSize messageLength) messageForeignPtr signatureForeignPtr