packages feed

cardano-crypto-1.3.0: src/Crypto/ECC/Ed25519Donna.hs

-- |
-- Module      : Crypto.PubKey.Ed25519
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Ed25519 support
--
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE BangPatterns #-}
module Crypto.ECC.Ed25519Donna
    ( SecretKey(..)
    , PublicKey(..)
    , Signature
    -- * Smart constructors
    , signature
    , publicKey
    , secretKey
    -- * methods
    , toPublic
    , sign
    , verify
    , publicAdd
    , secretAdd
    ) where

import           Control.DeepSeq
import           Data.Word
import           Foreign.Ptr
import           Foreign.C.Types

import           Data.ByteArray (ByteArrayAccess, withByteArray, ScrubbedBytes, Bytes)
import qualified Data.ByteArray as B
import           Crypto.Error
import           Foreign.Storable
import           System.IO.Unsafe
import           Crypto.Hash (hashWith, SHA512(..))
import           Data.ByteArray (alloc)
import           Data.Bits
import           Control.Monad

unsafeDoIO :: IO a -> a
unsafeDoIO = unsafeDupablePerformIO
{-# NOINLINE unsafeDoIO #-}

-- | An Ed25519 Secret key
newtype SecretKey = SecretKey ScrubbedBytes
    deriving (Eq,ByteArrayAccess,NFData)

-- | An Ed25519 public key
newtype PublicKey = PublicKey Bytes
    deriving (Show,Eq,ByteArrayAccess,NFData)

-- | An Ed25519 signature
newtype Signature = Signature Bytes
    deriving (Show,Eq,ByteArrayAccess,NFData)

-- | Try to build a public key from a bytearray
publicKey :: ByteArrayAccess ba => ba -> CryptoFailable PublicKey
publicKey bs
    | B.length bs == publicKeySize =
        CryptoPassed $ PublicKey $ B.copyAndFreeze bs (\_ -> return ())
    | otherwise =
        CryptoFailed $ CryptoError_PublicKeySizeInvalid

-- | Try to build a secret key from a bytearray
secretKey :: ByteArrayAccess ba => ba -> CryptoFailable SecretKey
secretKey bs
    | B.length bs == 32 = unsafePerformIO verifyAndTweak
    | otherwise         = CryptoFailed CryptoError_SecretKeySizeInvalid
  where
    k = hashWith SHA512 bs
    verifyAndTweak :: IO (CryptoFailable SecretKey)
    verifyAndTweak = withByteArray k $ \inp -> do
        b0  <- peekElemOff inp 0 :: IO Word8
        b31 <- peekElemOff inp 31
        if testBit b31 5
            then return $ CryptoFailed CryptoError_SecretKeyStructureInvalid
            else CryptoPassed . SecretKey <$> (alloc 64 $ \outp -> do
                    pokeElemOff outp 0 (b0 .&. 0xf8) -- clear lowest 3 bits
                    forM_ [1..30] $ \i -> peekElemOff inp i >>= pokeElemOff outp i
                    pokeElemOff outp 31 ((b31 .&. 0x1f) .|. 0x40) -- clear highest bit and 3rd bit, and set 2nd highest.
                    forM_ [32..63] $ \i -> peekElemOff inp i >>= pokeElemOff outp i
                    )

-- | Try to build a signature from a bytearray
signature :: ByteArrayAccess ba => ba -> CryptoFailable Signature
signature bs
    | B.length bs == signatureSize =
        CryptoPassed $ Signature $ B.copyAndFreeze bs (\_ -> return ())
    | otherwise =
        -- missing a SignatureSizeInvalid error, so use another one
        CryptoFailed $ CryptoError_SecretKeySizeInvalid

-- | Create a public key from a secret key
toPublic :: SecretKey -> PublicKey
toPublic (SecretKey sec) = PublicKey $
    B.allocAndFreeze publicKeySize $ \result ->
    withByteArray sec              $ \psec   ->
        ccryptonite_ed25519_publickey psec result
{-# NOINLINE toPublic #-}

publicAdd :: PublicKey -> PublicKey -> PublicKey
publicAdd p1 p2 =
    PublicKey $ B.allocAndFreeze publicKeySize $ \result ->
        withByteArray p1 $ \v1 ->
        withByteArray p2 $ \v2 ->
            ccryptonite_ed25519_point_add v1 v2 result

secretAdd :: SecretKey -> SecretKey -> SecretKey
secretAdd p1 p2 =
    SecretKey $ B.allocAndFreeze secretKeySize $ \result ->
        withByteArray p1 $ \v1 ->
        withByteArray p2 $ \v2 ->
            ccryptonite_ed25519_scalar_add v1 v2 result

-- | Sign a message using the key pair
sign :: (ByteArrayAccess msg, ByteArrayAccess salt) => SecretKey -> salt -> PublicKey -> msg -> Signature
sign secret salt public message =
    Signature $ B.allocAndFreeze signatureSize $ \sig ->
        withByteArray secret  $ \sec   ->
        withByteArray public  $ \pub   ->
        withByteArray salt    $ \saltP ->
        withByteArray message $ \msg   ->
             ccryptonite_ed25519_sign msg (fromIntegral msgLen) saltP (fromIntegral saltLen) sec pub sig
  where
    !msgLen  = B.length message
    !saltLen = B.length salt

-- | Verify a message
verify :: ByteArrayAccess ba => PublicKey -> ba -> Signature -> Bool
verify public message signatureVal = unsafeDoIO $
    withByteArray signatureVal $ \sig ->
    withByteArray public       $ \pub ->
    withByteArray message      $ \msg -> do
      r <- ccryptonite_ed25519_sign_open msg (fromIntegral msgLen) pub sig
      return (r == 0)
  where
    !msgLen = B.length message

publicKeySize :: Int
publicKeySize = 32

secretKeySize :: Int
secretKeySize = 64

signatureSize :: Int
signatureSize = 64

foreign import ccall "cardano_crypto_ed25519_publickey"
    ccryptonite_ed25519_publickey :: Ptr SecretKey -- secret key
                                  -> Ptr PublicKey -- public key
                                  -> IO ()

foreign import ccall "cardano_crypto_ed25519_sign_open"
    ccryptonite_ed25519_sign_open :: Ptr Word8     -- message
                                  -> CSize         -- message len
                                  -> Ptr PublicKey -- public
                                  -> Ptr Signature -- signature
                                  -> IO CInt

foreign import ccall "cardano_crypto_ed25519_sign"
    ccryptonite_ed25519_sign :: Ptr Word8     -- message
                             -> CSize         -- message len
                             -> Ptr Word8     -- salt
                             -> CSize         -- salt len
                             -> Ptr SecretKey -- secret
                             -> Ptr PublicKey -- public
                             -> Ptr Signature -- signature
                             -> IO ()

foreign import ccall "cardano_crypto_ed25519_point_add"
    ccryptonite_ed25519_point_add :: Ptr PublicKey -- p1
                                  -> Ptr PublicKey -- p2
                                  -> Ptr PublicKey -- p1 + p2
                                  -> IO ()

foreign import ccall "cardano_crypto_ed25519_scalar_add"
    ccryptonite_ed25519_scalar_add :: Ptr SecretKey -- s1
                                   -> Ptr SecretKey -- s2
                                   -> Ptr SecretKey -- s1 + s2
                                   -> IO ()