{-# LANGUAGE OverloadedStrings #-} -- | -- Module : Crypto.Ecdsa.Signature -- Copyright : Alexander Krupenkin 2018 -- License : BSD3 -- -- 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 pk = pack . sign pk . 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 msg = hashWith Keccak_256 prefixed where len = LBS.toStrict . toLazyByteString . intDec . BA.length prefixed = "\x19" <> "Ethereum Signed Message:\n" <> len msg <> convert 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 encode key = encode $ Just signed where unsigned = encode Nothing signed = sign key (hashWith Keccak_256 unsigned)