{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeSynonymInstances #-} -- | -- Module : Network.Ethereum.Account.PrivateKey -- Copyright : Alexander Krupenkin 2018 -- Roy Blankman 2018 -- License : BSD3 -- -- Maintainer : mail@akru.me -- Stability : experimental -- Portability : unportable -- module Network.Ethereum.Account.PrivateKey where import Control.Monad.State.Strict (get, runStateT) import Control.Monad.Trans (lift) import Crypto.Secp256k1 (CompactRecSig (..), SecKey, derivePubKey) import Data.ByteArray (convert) import Data.ByteString (ByteString) import Data.ByteString.Short (fromShort) import Data.Default (Default (..)) import Data.Maybe (fromJust, fromMaybe) import Data.Monoid (mempty, (<>)) import Data.Proxy (Proxy (..)) import Data.RLP (packRLP, rlpEncode) import Crypto.Ethereum (ecsign) import Data.ByteArray.HexString (HexString, toBytes) import Data.Solidity.Abi.Codec (decode, encode) import Data.Solidity.Prim.Address (fromPubKey, toHexString) import Network.Ethereum.Account.Class (Account (..)) import Network.Ethereum.Account.Internal (AccountT (..), CallParam (..), defaultCallParam, getCall, getReceipt) import qualified Network.Ethereum.Api.Eth as Eth (call, estimateGas, getTransactionCount, sendRawTransaction) import Network.Ethereum.Api.Types (Call (..), unQuantity) import Network.Ethereum.Chain (foundation) import Network.Ethereum.Contract.Method (selector) import Network.Ethereum.Unit (Shannon, toWei) -- | Local EOA params data PrivateKey = PrivateKey { privateKey :: !SecKey , privateKeyChain :: !Integer } deriving (Eq, Show) instance Default PrivateKey where def = PrivateKey "" foundation type PrivateKeyAccount = AccountT PrivateKey instance Account PrivateKey PrivateKeyAccount where withAccount a = fmap fst . flip runStateT (defaultCallParam a) . runAccountT send (args :: a) = do CallParam{..} <- get c <- getCall let dat = selector (Proxy :: Proxy a) <> encode args address = fromPubKey (derivePubKey $ privateKey _account) nonce <- lift $ Eth.getTransactionCount address _block let params = c { callFrom = Just address , callNonce = Just nonce , callData = Just $ convert dat } params' <- case callGas params of Just _ -> return params Nothing -> do gasLimit <- lift $ Eth.estimateGas params return $ params { callGas = Just gasLimit } let signed = signTransaction params' (privateKeyChain _account) (privateKey _account) lift $ getReceipt =<< Eth.sendRawTransaction signed call (args :: a) = do CallParam{..} <- get c <- getCall let dat = selector (Proxy :: Proxy a) <> encode args address = fromPubKey (derivePubKey $ privateKey _account) params = c { callFrom = Just address, callData = Just $ convert dat } res <- lift $ Eth.call params _block case decode res of Right r -> return r Left e -> fail e encodeTransaction :: Call -> Either Integer (Integer, ByteString, ByteString) -> HexString encodeTransaction Call{..} vrs = do let (to :: ByteString) = maybe mempty (toBytes . toHexString) callTo (value :: Integer) = unQuantity $ fromJust callValue (nonce :: Integer) = unQuantity $ fromJust callNonce (gasPrice :: Integer) = maybe defaultGasPrice unQuantity callGasPrice (gasLimit :: Integer) = unQuantity $ fromJust callGas (input :: ByteString) = convert $ fromMaybe mempty callData rlp $ case vrs of -- Unsigned transaction by EIP155 Left chain_id -> (nonce, gasPrice, gasLimit, to, value, input, chain_id, mempty, mempty) -- Signed transaction Right (v, r, s) -> (nonce, gasPrice, gasLimit, to, value, input, v, s, r) where rlp = convert . packRLP . rlpEncode defaultGasPrice = toWei (5 :: Shannon) signTransaction :: Call -> Integer -> SecKey -> HexString signTransaction c i key = encodeTransaction c $ Right (v', r, s) where unsigned = encodeTransaction c (Left i) recSig = ecsign key unsigned v = fromIntegral $ getCompactRecSigV recSig r = fromShort $ getCompactRecSigR recSig s = fromShort $ getCompactRecSigS recSig v' = v + 35 + 2 * i -- Improved 'v' according to EIP155