{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module      :  Crypto.Ecdsa.Signature
-- Copyright   :  Aleksandr Krupenkin 2016-2021
-- License     :  Apache-2.0
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  portable
--
-- Recoverable Ethereum signature support.
--

module Crypto.Ethereum.Signature
    (
      hashMessage
    , signMessage
    , signTransaction
    ) where

import           Crypto.Hash             (Digest, Keccak_256 (..), hashWith)
import           Crypto.PubKey.ECC.ECDSA (PrivateKey (..))
import           Data.ByteArray          (ByteArray, ByteArrayAccess, convert)
import qualified Data.ByteArray          as BA (length)
import           Data.ByteString.Builder (intDec, toLazyByteString)
import qualified Data.ByteString.Lazy    as LBS (toStrict)
import           Data.Word               (Word8)

import           Crypto.Ecdsa.Signature  (pack, sign)

-- | Make Ethereum standard signature.
--
-- The message is before enveloped as follows:
-- "\x19Ethereum Signed Message:\n" + message.length + message
--
-- /WARNING:/ Vulnerable to timing attacks.
signMessage :: (ByteArrayAccess message, ByteArray rsv)
            => PrivateKey
            -> message
            -> rsv
{-# INLINE signMessage #-}
signMessage :: PrivateKey -> message -> rsv
signMessage PrivateKey
pk = (Integer, Integer, Word8) -> rsv
forall rsv. ByteArray rsv => (Integer, Integer, Word8) -> rsv
pack ((Integer, Integer, Word8) -> rsv)
-> (message -> (Integer, Integer, Word8)) -> message -> rsv
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PrivateKey -> Digest Keccak_256 -> (Integer, Integer, Word8)
forall bin.
ByteArrayAccess bin =>
PrivateKey -> bin -> (Integer, Integer, Word8)
sign PrivateKey
pk (Digest Keccak_256 -> (Integer, Integer, Word8))
-> (message -> Digest Keccak_256)
-> message
-> (Integer, Integer, Word8)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. message -> Digest Keccak_256
forall message.
ByteArrayAccess message =>
message -> Digest Keccak_256
hashMessage

-- | Ethereum standard hashed message.
--
-- The data will be UTF-8 HEX decoded and enveloped as follows:
-- "\x19Ethereum Signed Message:\n" + message.length + message and hashed using keccak256.
hashMessage :: ByteArrayAccess message => message -> Digest Keccak_256
hashMessage :: message -> Digest Keccak_256
hashMessage message
msg = Keccak_256 -> ByteString -> Digest Keccak_256
forall ba alg.
(ByteArrayAccess ba, HashAlgorithm alg) =>
alg -> ba -> Digest alg
hashWith Keccak_256
Keccak_256 ByteString
prefixed
  where
    len :: message -> ByteString
len = ByteString -> ByteString
LBS.toStrict (ByteString -> ByteString)
-> (message -> ByteString) -> message -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Builder -> ByteString
toLazyByteString (Builder -> ByteString)
-> (message -> Builder) -> message -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Builder
intDec (Int -> Builder) -> (message -> Int) -> message -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. message -> Int
forall ba. ByteArrayAccess ba => ba -> Int
BA.length
    prefixed :: ByteString
prefixed = ByteString
"\x19" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"Ethereum Signed Message:\n" ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> message -> ByteString
len message
msg ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> message -> ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
convert message
msg

-- | Sign Ethereum transaction.
--
-- /WARNING:/ Vulnerable to timing attacks.
signTransaction :: ByteArray ba
                => (Maybe (Integer, Integer, Word8) -> ba)
                -- ^ Two way transaction packer (unsigned and signed)
                -> PrivateKey
                -- ^ Private key
                -> ba
                -- ^ Encoded transaction
signTransaction :: (Maybe (Integer, Integer, Word8) -> ba) -> PrivateKey -> ba
signTransaction Maybe (Integer, Integer, Word8) -> ba
encode PrivateKey
key = Maybe (Integer, Integer, Word8) -> ba
encode (Maybe (Integer, Integer, Word8) -> ba)
-> Maybe (Integer, Integer, Word8) -> ba
forall a b. (a -> b) -> a -> b
$ (Integer, Integer, Word8) -> Maybe (Integer, Integer, Word8)
forall a. a -> Maybe a
Just (Integer, Integer, Word8)
signed
  where
    unsigned :: ba
unsigned = Maybe (Integer, Integer, Word8) -> ba
encode Maybe (Integer, Integer, Word8)
forall a. Maybe a
Nothing
    signed :: (Integer, Integer, Word8)
signed = PrivateKey -> Digest Keccak_256 -> (Integer, Integer, Word8)
forall bin.
ByteArrayAccess bin =>
PrivateKey -> bin -> (Integer, Integer, Word8)
sign PrivateKey
key (Keccak_256 -> ba -> Digest Keccak_256
forall ba alg.
(ByteArrayAccess ba, HashAlgorithm alg) =>
alg -> ba -> Digest alg
hashWith Keccak_256
Keccak_256 ba
unsigned)