{-| Module : Helper functions to sign a transaction and derive address from Description : for the EVM given a secret key -} module EVM.Sign where import Data.ByteString qualified as BS import Data.Maybe (fromMaybe) import Data.Word import Crypto.Hash qualified as Crypto import Crypto.PubKey.ECC.ECDSA (signDigestWith, PrivateKey(..), Signature(..)) import Crypto.PubKey.ECC.Types (getCurveByName, CurveName(..), Point(..)) import Crypto.PubKey.ECC.Generate (generateQ) import Witch (unsafeInto) import EVM.ABI (encodeAbiValue, AbiValue(..)) import EVM.Types import EVM.Expr (exprToAddr) import EVM.Precompiled -- Given a secret key, generates the address deriveAddr :: Integer -> Maybe Addr deriveAddr sk = case pubPoint of PointO -> Nothing Point x y -> -- See yellow paper #286 let pub = BS.concat [ encodeInt x, encodeInt y ] addr = Lit . W256 . word256 . BS.drop 12 . BS.take 32 . keccakBytes $ pub in exprToAddr addr where curve = getCurveByName SEC_p256k1 pubPoint = generateQ curve sk encodeInt = encodeAbiValue . AbiUInt 256 . fromInteger sign :: W256 -> Integer -> (Word8, W256, W256) sign hash sk = (v, r, s) where -- setup curve params curve = getCurveByName SEC_p256k1 priv = PrivateKey curve sk digest = fromMaybe (internalError $ "could produce a digest from " <> show hash) (Crypto.digestFromByteString (word256Bytes hash)) -- sign message sig = ethsign priv digest r = unsafeInto $ sign_r sig s = unsafeInto lowS -- this is a little bit sad, but cryptonite doesn't give us back a v value -- so we compute it by guessing one, and then seeing if that gives us the right answer from ecrecover v = if ecrec 28 r s hash == deriveAddr sk then 28 else 27 -- we always use the lower S value to conform with EIP2 (re: ECDSA transaction malleability) -- https://eips.ethereum.org/EIPS/eip-2 secpOrder = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 :: Integer lowS = if sign_s sig > secpOrder `div` 2 then secpOrder - sign_s sig else sign_s sig -- | We don't want to introduce the machinery needed to sign with a random nonce, -- so we just use the same nonce every time (420). This is obviously very -- insecure, but fine for testing purposes. ethsign :: PrivateKey -> Crypto.Digest Crypto.Keccak_256 -> Signature ethsign sk digest = go 420 where go k = case signDigestWith k sk digest of Nothing -> go (k + 1) Just sig -> sig ecrec :: W256 -> W256 -> W256 -> W256 -> Maybe Addr ecrec v r s e = unsafeInto . word <$> EVM.Precompiled.execute 1 input 32 where input = BS.concat (word256Bytes <$> [e, v, r, s])