web3 0.8.2.1 → 0.8.3.0
raw patch · 35 files changed
+1394/−573 lines, 35 filesdep +uuid-typesdep −secp256k1-haskelldep −web3dep ~containersdep ~http-clientdep ~vinyl
Dependencies added: uuid-types
Dependencies removed: secp256k1-haskell, web3
Dependency ranges changed: containers, http-client, vinyl
Files
- CHANGELOG.md +9/−0
- README.md +1/−1
- cbits/solidity_lite.cpp +0/−99
- compiler/Language/Solidity/Compiler.hs +58/−0
- compiler/Language/Solidity/Compiler/Foreign.hsc +106/−0
- compiler/cbits/solidity_lite.cpp +100/−0
- src/Crypto/Ethereum.hs +13/−42
- src/Crypto/Ethereum/Keyfile.hs +230/−0
- src/Crypto/Ethereum/Signature.hs +142/−0
- src/Crypto/Ethereum/Utils.hs +53/−0
- src/Crypto/Random/HmacDrbg.hs +68/−0
- src/Data/ByteArray/HexString.hs +4/−6
- src/Data/Solidity/Prim/Address.hs +9/−9
- src/Data/Solidity/Prim/Int.hs +4/−2
- src/Language/Solidity/Compiler.hs +0/−58
- src/Language/Solidity/Compiler/Foreign.hsc +0/−106
- src/Network/Ethereum/Account.hs +11/−11
- src/Network/Ethereum/Account/LocalKey.hs +96/−0
- src/Network/Ethereum/Account/PrivateKey.hs +0/−130
- src/Network/Ethereum/Chain.hs +1/−0
- src/Network/Ethereum/Contract/TH.hs +1/−1
- src/Network/Ethereum/Transaction.hs +59/−0
- stack.yaml +2/−4
- test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs +1/−1
- test/Network/Ethereum/Web3/Test/LinearizationSpec.hs +1/−4
- test/Network/Ethereum/Web3/Test/LocalAccountSpec.hs +52/−0
- test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs +1/−1
- unit/Crypto/Ethereum/Test/EcdsaSpec.hs +0/−32
- unit/Crypto/Ethereum/Test/KeyfileSpec.hs +99/−0
- unit/Crypto/Ethereum/Test/SignatureSpec.hs +65/−0
- unit/Crypto/Random/Test/HmacDrbgSpec.hs +24/−0
- unit/Data/Solidity/Test/AddressSpec.hs +10/−9
- unit/Language/Solidity/Test/CompilerSpec.hs +4/−4
- unit/Network/Ethereum/Web3/Test/LocalAccountSpec.hs +0/−26
- web3.cabal +170/−27
CHANGELOG.md view
@@ -1,6 +1,15 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.8.3.0] 2019-01-09+### Changed +- Cryptonite based ECC signer for messages and transactions, it removes secp256k1 dependency+- Fixed dependencies bounds for stackage LTS-13++## [0.8.2.1] 2018-11-19+### Changed+- Fixed dependencies bounds for stackage distribution+ ## [0.8.2.0] 2018-11-07 ### Changed - Gas estimation runs when gas limit is not set before
README.md view
@@ -6,7 +6,7 @@ [](https://hs-web3.readthedocs.io/en/latest/?badge=latest) [](https://travis-ci.org/airalab/hs-web3) [](http://hackage.haskell.org/package/web3)-[](http://stackage.org/lts-12/package/web3)+[](http://stackage.org/lts-13/package/web3) [](http://stackage.org/nightly/package/web3) [](https://www.codetriage.com/airalab/hs-web3) 
− cbits/solidity_lite.cpp
@@ -1,99 +0,0 @@-#include <solidity_lite.h>-#include <libsolidity/interface/CompilerStack.h>-#include <libsolidity/interface/SourceReferenceFormatter.h>--using namespace dev::solidity;-using namespace std;--struct Solidity {- Solidity() : compiler(new CompilerStack)- {}-- ~Solidity()- { delete compiler; }-- CompilerStack * compiler;- map<string, dev::h160> libs;-};--#ifdef __cplusplus-extern "C"-{-#endif--namespace dev {-namespace solidity {-namespace lite {--void * create()-{- return static_cast<void *>(new Solidity);-}--void destroy(void * self)-{- delete static_cast<Solidity *>(self);-}--int addSource(void * self, const char * name, const char * source)-{- return static_cast<Solidity*>(self)->compiler->addSource(name, source);-}--int addLibrary(void * self, const char * name, const char * address)-{- static_cast<Solidity*>(self)->libs[name] = dev::h160(address);- return 0;-}--int compile(void * self, int optimize)-{- auto s = static_cast<Solidity *>(self);-- s->compiler->setOptimiserSettings(optimize);- s->compiler->setLibraries(s->libs);- s->compiler->reset(true);- if (s->compiler->parseAndAnalyze()) - if (s->compiler->compile())- return 0;-- return -1;-}--char * c_string(const std::string &str)-{- auto c_str = (char *) calloc(1, str.size()+1);- memcpy(c_str, str.c_str(), str.size());- return c_str;-}--char * abi(void * self, const char * name)-{- auto abi_value = static_cast<Solidity *>(self)->compiler->contractABI(name);- return c_string(abi_value.toStyledString());-}--char * binary(void * self, const char * name)-{- auto bin_object = static_cast<Solidity *>(self)->compiler->object(name);- return c_string(bin_object.toHex());-}--char * errors(void * self)-{- auto compiler = static_cast<Solidity *>(self)->compiler;- stringstream error_stream;- for (auto const& error: compiler->errors()) {- SourceReferenceFormatter fmt(error_stream, [&](string const& _source) -> Scanner const& { return compiler->scanner(_source); });- fmt.printExceptionInformation(*error, (error->type() == Error::Type::Warning) ? "Warning" : "Error");- }- return c_string(error_stream.str());-}--} // solidity-} // lite-} // dev--#ifdef __cplusplus-}-#endif
+ compiler/Language/Solidity/Compiler.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE CPP #-}+#ifdef SOLIDITY_COMPILER+{-# LANGUAGE RecordWildCards #-}+#endif++-- |+-- Module : Language.Solidity.Compiler+-- Copyright : Alexander Krupenkin 2017+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : portable+--+-- Solidity compiler high-level bindings.+--++module Language.Solidity.Compiler where++#ifdef SOLIDITY_COMPILER++import Data.ByteString (ByteString)+import Data.Map (Map)+import Data.Semigroup (Semigroup (..))+import qualified Language.Solidity.Compiler.Foreign as FFI+import System.IO.Unsafe (unsafePerformIO)++-- | Input contract sources+data Sources = Sources+ { sourceMap :: Map ByteString ByteString+ -- ^ Source code map from contract name in keys+ , libraries :: Map ByteString ByteString+ -- ^ Library address map for linking+ , optimization :: Bool+ -- ^ Enable optimization?+ } deriving (Eq, Show)++instance Semigroup Sources where+ a <> b = Sources (sourceMap a `mappend` sourceMap b)+ (libraries a `mappend` libraries b)+ (optimization a || optimization b)++instance Monoid Sources where+ mappend = (<>)+ mempty = Sources mempty mempty False++type Compiled = Map ByteString (ByteString, ByteString)++-- | Compile Solidity contracts+compile :: Sources+ -- ^ Contract sources+ -> Either ByteString Compiled+ -- ^ Error string or compiled contracts ABI and hex+{-# NOINLINE compile #-}+compile Sources{..} = unsafePerformIO $+ FFI.compile sourceMap libraries optimization++#endif
+ compiler/Language/Solidity/Compiler/Foreign.hsc view
@@ -0,0 +1,106 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ForeignFunctionInterface #-}++-- |+-- Module : Language.Solidity.Compiler.Foreign+-- Copyright : Alexander Krupenkin 2017+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : unportable+--+-- Ethereum Solidity library FFI.+--++module Language.Solidity.Compiler.Foreign where++#ifdef SOLIDITY_COMPILER+#include <solidity_lite.h>++import Data.Map as M+import Data.ByteString.Char8 as BS+import Data.Typeable (Typeable)+import Foreign.C.String (CString, peekCString, withCString)+import Foreign.C.Types (CInt(..))+import Foreign.Ptr (Ptr)+import Foreign (free)++data Solidity+ deriving Typeable++foreign import ccall unsafe "create" c_create+ :: IO (Ptr Solidity)+foreign import ccall unsafe "destroy" c_destroy+ :: Ptr Solidity -> IO ()+foreign import ccall unsafe "addSource" c_addSource+ :: Ptr Solidity -> CString -> CString -> IO CInt+foreign import ccall unsafe "addLibrary" c_addLibrary+ :: Ptr Solidity -> CString -> CString -> IO CInt+foreign import ccall unsafe "compile" c_compile+ :: Ptr Solidity -> CInt -> IO CInt+foreign import ccall unsafe "abi" c_abi+ :: Ptr Solidity -> CString -> IO CString+foreign import ccall unsafe "binary" c_binary+ :: Ptr Solidity -> CString -> IO CString+foreign import ccall unsafe "errors" c_errors+ :: Ptr Solidity -> IO CString++withSolidity :: (Ptr Solidity -> IO a) -> IO a+{-# INLINE withSolidity #-}+withSolidity f = do+ ptr <- c_create+ r <- f ptr+ c_destroy ptr+ return r++withCStringPair :: (CString -> CString -> IO a) -> (ByteString, ByteString) -> IO a+{-# INLINE withCStringPair #-}+withCStringPair f (a, b) =+ withCString (BS.unpack a) $ \astr ->+ withCString (BS.unpack b) $ \bstr ->+ f astr bstr++addSources :: Ptr Solidity -> Map ByteString ByteString -> IO ()+{-# INLINE addSources #-}+addSources ptr = mapM_ (withCStringPair $ c_addSource ptr) . M.toList++addLibraries :: Ptr Solidity -> Map ByteString ByteString -> IO ()+{-# INLINE addLibraries #-}+addLibraries ptr = mapM_ (withCStringPair $ c_addLibrary ptr) . M.toList++result :: Ptr Solidity -> ByteString -> IO (ByteString, ByteString)+result ptr name =+ withCString (BS.unpack name) $ \cname -> do+ abi <- c_abi ptr cname+ bin <- c_binary ptr cname+ res <- (,) <$> fmap BS.pack (peekCString abi)+ <*> fmap BS.pack (peekCString bin)+ free abi+ free bin+ return res++errors :: Ptr Solidity -> IO ByteString+errors ptr = do+ errs <- c_errors ptr+ res <- BS.pack <$> peekCString errs+ free errs+ return res++compile :: Map ByteString ByteString+ -> Map ByteString ByteString+ -> Bool+ -> IO (Either ByteString (Map ByteString (ByteString, ByteString)))+compile srcs libs opt =+ withSolidity $ \ptr -> do+ addSources ptr srcs+ addLibraries ptr libs+ res <- c_compile ptr optI+ let compiled = sequence $ M.mapWithKey (const . result ptr) srcs+ if res < 0+ then Left <$> errors ptr+ else Right <$> compiled+ where optI | opt = 1+ | otherwise = 0++#endif
+ compiler/cbits/solidity_lite.cpp view
@@ -0,0 +1,100 @@+#include <solidity_lite.h>+#include <libsolidity/interface/CompilerStack.h>+#include <liblangutil/SourceReferenceFormatter.h>++using namespace dev::solidity;+using namespace langutil;+using namespace std;++struct Solidity {+ Solidity() : compiler(new CompilerStack)+ {}++ ~Solidity()+ { delete compiler; }++ CompilerStack * compiler;+ map<string, dev::h160> libs;+};++#ifdef __cplusplus+extern "C"+{+#endif++namespace dev {+namespace solidity {+namespace lite {++void * create()+{+ return static_cast<void *>(new Solidity);+}++void destroy(void * self)+{+ delete static_cast<Solidity *>(self);+}++int addSource(void * self, const char * name, const char * source)+{+ return static_cast<Solidity*>(self)->compiler->addSource(name, source);+}++int addLibrary(void * self, const char * name, const char * address)+{+ static_cast<Solidity*>(self)->libs[name] = dev::h160(address);+ return 0;+}++int compile(void * self, int optimize)+{+ auto s = static_cast<Solidity *>(self);++ s->compiler->setOptimiserSettings(optimize);+ s->compiler->setLibraries(s->libs);+ s->compiler->reset(true);+ if (s->compiler->parseAndAnalyze()) + if (s->compiler->compile())+ return 0;++ return -1;+}++char * c_string(const std::string &str)+{+ auto c_str = (char *) calloc(1, str.size()+1);+ memcpy(c_str, str.c_str(), str.size());+ return c_str;+}++char * abi(void * self, const char * name)+{+ auto abi_value = static_cast<Solidity *>(self)->compiler->contractABI(name);+ return c_string(abi_value.toStyledString());+}++char * binary(void * self, const char * name)+{+ auto bin_object = static_cast<Solidity *>(self)->compiler->object(name);+ return c_string(bin_object.toHex());+}++char * errors(void * self)+{+ auto compiler = static_cast<Solidity *>(self)->compiler;+ stringstream error_stream;+ for (auto const& error: compiler->errors()) {+ SourceReferenceFormatter fmt(error_stream, [&](string const& _source) -> Scanner const& { return compiler->scanner(_source); });+ fmt.printExceptionInformation(*error, (error->type() == Error::Type::Warning) ? "Warning" : "Error");+ }+ return c_string(error_stream.str());+}++} // solidity+} // lite+} // dev++#ifdef __cplusplus+}+#endif
src/Crypto/Ethereum.hs view
@@ -7,53 +7,24 @@ -- Stability : experimental -- Portability : unportable ----- Ethereum ECDSA based on secp256k1 bindings.+-- Ethereum ECC support module. -- module Crypto.Ethereum (- -- * Ethereum ECDSA sign/recover- hashMessage- , ecsign- , ecrecover-- -- * Re-export useful Secp256k1 functions- , SecKey+ -- * Ethereum crypto key ops+ PrivateKey+ , PublicKey+ , importKey , derivePubKey- )where -import Crypto.Hash (Keccak_256 (..), hashWith)-import Crypto.Secp256k1 (CompactRecSig, Msg, SecKey,- derivePubKey, exportCompactRecSig,- importCompactRecSig, msg, recover,- signRecMsg)-import Data.ByteArray (ByteArrayAccess, convert)-import Data.Maybe (fromJust)--import Data.Solidity.Prim.Address (Address, fromPubKey)---- | SHA3 hash of argument-hashMessage :: ByteArrayAccess ba => ba -> Msg-hashMessage = fromJust . msg . convert . hashWith Keccak_256+ -- * Digital Signature Algorithm+ , signMessage --- | Sign message with Ethereum private key-ecsign :: ByteArrayAccess message- => SecKey- -- ^ Private key- -> message- -- ^ Message content- -> CompactRecSig- -- ^ Signature-ecsign key = exportCompactRecSig . signRecMsg key . hashMessage+ -- * Hash function+ , sha3+ ) where --- | Recover message signer Ethereum address-ecrecover :: ByteArrayAccess message- => CompactRecSig- -- ^ Signature- -> message- -- ^ Message content- -> Maybe Address- -- ^ Message signer address-ecrecover sig message = do- sig' <- importCompactRecSig sig- fromPubKey <$> recover sig' (hashMessage message)+import Crypto.Ethereum.Signature (signMessage)+import Crypto.Ethereum.Utils (derivePubKey, importKey, sha3)+import Crypto.PubKey.ECC.ECDSA (PrivateKey, PublicKey)
+ src/Crypto/Ethereum/Keyfile.hs view
@@ -0,0 +1,230 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+-- |+-- Module : Crypto.Ethereum.Keyfile+-- Copyright : Alexander Krupenkin 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : portable+--+-- Web3 Secret Storage implementation.+-- Spec https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition.+--++module Crypto.Ethereum.Keyfile+ (+ -- * Encrypted Ethereum private key+ EncryptedKey(..)+ , Cipher(..)+ , Kdf(..)++ -- * Secret storage packers+ , decrypt+ , encrypt+ ) where++import Crypto.Cipher.AES (AES128)+import Crypto.Cipher.Types (IV, cipherInit, ctrCombine, makeIV)+import Crypto.Error (throwCryptoError)+import qualified Crypto.KDF.PBKDF2 as Pbkdf2 (Parameters (..),+ fastPBKDF2_SHA256)+import qualified Crypto.KDF.Scrypt as Scrypt (Parameters (..), generate)+import Crypto.Random (MonadRandom (getRandomBytes))+import Data.Aeson (FromJSON (..), ToJSON (..), Value,+ object, withObject, (.:), (.=))+import Data.Aeson.Types (Parser)+import Data.ByteArray (ByteArray, ByteArrayAccess, convert)+import qualified Data.ByteArray as BA (drop, take, unpack)+import Data.Maybe (fromJust)+import Data.Monoid ((<>))+import Data.Text (Text)+import Data.UUID.Types (UUID)+import Data.UUID.Types.Internal (buildFromBytes)++import Crypto.Ethereum.Utils (sha3)+import Data.ByteArray.HexString (HexString)++-- | Key derivation function parameters and salt.+data Kdf = Pbkdf2 !Pbkdf2.Parameters !HexString+ | Scrypt !Scrypt.Parameters !HexString++-- | Cipher parameters.+data Cipher = Aes128Ctr+ { cipherIv :: !(IV AES128), cipherText :: !HexString }++-- | Secret Storage representation on memory.+data EncryptedKey = EncryptedKey+ { encryptedKeyId :: !UUID -- ^ Random key ID+ , encryptedKeyVersion :: !Int -- ^ Version (suppoted version 3 only)+ , encryptedKeyCipher :: !Cipher -- ^ Cipher (supported AES-128-CTR only)+ , encryptedKeyKdf :: !Kdf -- ^ Key derivation function+ , encryptedKeyMac :: !HexString -- ^ MAC+ }++instance Eq EncryptedKey where+ a == b = encryptedKeyId a == encryptedKeyId b++instance Show EncryptedKey where+ show EncryptedKey{..} = "EncryptedKey " ++ show encryptedKeyId++instance FromJSON EncryptedKey where+ parseJSON = encryptedKeyParser++instance ToJSON EncryptedKey where+ toJSON = encryptedKeyBuilder++encryptedKeyBuilder :: EncryptedKey -> Value+encryptedKeyBuilder EncryptedKey{..} = object+ [ "id" .= encryptedKeyId+ , "version" .= encryptedKeyVersion+ , "crypto" .= object+ [ "cipher" .= cipherName encryptedKeyCipher+ , "cipherparams" .= cipherParams encryptedKeyCipher+ , "ciphertext" .= cipherText encryptedKeyCipher+ , "kdf" .= kdfName encryptedKeyKdf+ , "kdfparams" .= kdfParams encryptedKeyKdf+ , "mac" .= encryptedKeyMac+ ]+ ]+ where+ cipherName :: Cipher -> Text+ cipherName Aes128Ctr{..} = "aes-128-ctr"++ cipherParams :: Cipher -> Value+ cipherParams Aes128Ctr{..} = object [ "iv" .= (convert cipherIv :: HexString) ]++ kdfName :: Kdf -> Text+ kdfName = \case+ Pbkdf2 _ _ -> "pbkdf2"+ Scrypt _ _ -> "scrypt"++ kdfParams :: Kdf -> Value+ kdfParams = \case+ Pbkdf2 params salt ->+ object [ "salt" .= salt+ , "dklen" .= Pbkdf2.outputLength params+ , "c" .= Pbkdf2.iterCounts params+ ]+ Scrypt params salt ->+ object [ "salt" .= salt+ , "dklen" .= Scrypt.outputLength params+ , "p" .= Scrypt.p params+ , "r" .= Scrypt.r params+ , "n" .= Scrypt.n params+ ]++encryptedKeyParser :: Value -> Parser EncryptedKey+encryptedKeyParser = withObject "EncryptedKey" $ \v -> do+ uuid <- v .: "id"+ version <- v .: "version"+ crypto <- v .: "crypto"+ cipher <- parseCipher crypto+ kdf <- parseKdf crypto+ mac <- withObject "Crypto" (.: "mac") crypto+ return $ EncryptedKey uuid version cipher kdf mac++parseCipher :: Value -> Parser Cipher+parseCipher = withObject "Cipher" $ \v -> do+ name <- v .: "cipher"+ case name :: Text of+ "aes-128-ctr" -> do+ params <- v .: "cipherparams"+ hexiv <- params .: "iv"+ text <- v .: "ciphertext"+ case makeIV (hexiv :: HexString) of+ Just iv -> return (Aes128Ctr iv text)+ Nothing -> fail $ "Unable to make IV from " ++ show hexiv+ _ -> fail $ show name ++ " not implemented yet"++parseKdf :: Value -> Parser Kdf+parseKdf = withObject "Kdf" $ \v -> do+ name <- v .: "kdf"+ params <- v .: "kdfparams"+ dklen <- params .: "dklen"+ salt <- params .: "salt"+ case name :: Text of+ "pbkdf2" -> do+ iterations <- params .: "c"+ prf <- params .: "prf"+ case prf :: Text of+ "hmac-sha256" -> return $ Pbkdf2 (Pbkdf2.Parameters iterations dklen) salt+ _ -> fail $ show prf ++ " not implemented yet"+ "scrypt" -> do+ p <- params .: "p"+ r <- params .: "r"+ n <- params .: "n"+ return $ Scrypt (Scrypt.Parameters n r p dklen) salt+ _ -> fail $ show name ++ " not implemented yet"++defaultKdf :: HexString -> Kdf+defaultKdf = Scrypt (Scrypt.Parameters n r p dklen)+ where+ dklen = 32+ n = 262144+ r = 1+ p = 8++deriveKey :: (ByteArrayAccess password, ByteArray ba) => Kdf -> password -> ba+deriveKey kdf password =+ case kdf of+ Pbkdf2 params salt -> Pbkdf2.fastPBKDF2_SHA256 params password salt+ Scrypt params salt -> Scrypt.generate params password salt+++-- | Decrypt Ethereum private key.+--+-- Typically Web3 Secret Storage is JSON-encoded. 'EncryptedKey' data type has 'FromJSON' instance+-- to helps decode it from JSON-encoded string or file.+--+-- @+-- let decryptJSON pass = flip decrypt pass <=< decode+-- @+--+decrypt :: (ByteArrayAccess password, ByteArray privateKey)+ => EncryptedKey+ -> password+ -> Maybe privateKey+decrypt EncryptedKey{..} password+ | mac == encryptedKeyMac = Just (convert privateKey)+ | otherwise = Nothing+ where+ privateKey = ctrCombine cipher iv ciphertext+ cipher = throwCryptoError $ cipherInit (BA.take 16 derivedKey) :: AES128+ derivedKey = deriveKey encryptedKeyKdf password+ ciphertext = cipherText encryptedKeyCipher+ mac = sha3 (BA.drop 16 derivedKey <> ciphertext)+ iv = cipherIv encryptedKeyCipher++-- | Encrypt Ethereum private key.+--+-- @+-- let encryptJSON pass key = encode <$> encrypt key pass+-- @+encrypt :: (ByteArray privateKey, ByteArrayAccess password, MonadRandom m)+ => privateKey+ -> password+ -> m EncryptedKey+encrypt privateKey password = do+ kdf <- defaultKdf <$> getRandomBytes 16+ iv <- randomIV+ let derivedKey = deriveKey kdf password+ cipher = throwCryptoError $ cipherInit (BA.take 16 derivedKey) :: AES128+ ciphertext = ctrCombine cipher iv privateKey+ mac = sha3 (BA.drop 16 derivedKey <> ciphertext)+ uuid <- randomUUID+ return $ EncryptedKey uuid 3 (Aes128Ctr iv $ convert ciphertext) kdf mac+ where+ randomUUID = do+ uuid <- getRandomBytes 16+ let bs = BA.unpack (uuid :: HexString)+ return $ buildFromBytes 4+ (head bs) (bs !! 1) (bs !! 2) (bs !! 3)+ (bs !! 4) (bs !! 5) (bs !! 6) (bs !! 7)+ (bs !! 8) (bs !! 9) (bs !! 10) (bs !! 11)+ (bs !! 12) (bs !! 13) (bs !! 14) (bs !! 15)+ randomIV = do+ iv <- getRandomBytes 16+ return $ fromJust $ makeIV (iv :: HexString)
+ src/Crypto/Ethereum/Signature.hs view
@@ -0,0 +1,142 @@+{-# LANGUAGE OverloadedStrings #-}++-- |+-- Module : Crypto.Ethereum+-- Copyright : Alexander Krupenkin 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : portable+--+-- Recoverable ECC signature support.+--++module Crypto.Ethereum.Signature+ (+ hashMessage+ , signMessage+ , signTransaction+ , pack+ , unpack+ ) where++import Control.Monad (when)+import Crypto.Hash (Digest, Keccak_256 (..), SHA256,+ hashWith)+import Crypto.Number.Generate (generateBetween)+import Crypto.Number.ModArithmetic (inverse)+import Crypto.Number.Serialize (i2osp, os2ip)+import Crypto.PubKey.ECC.ECDSA (PrivateKey (..))+import Crypto.PubKey.ECC.Prim (pointMul)+import Crypto.PubKey.ECC.Types (CurveCommon (ecc_g, ecc_n),+ Point (..), common_curve)+import Crypto.Random (MonadRandom, withDRG)+import Crypto.Random.HmacDrbg (HmacDrbg, initialize)+import Data.Bits (xor, (.|.))+import Data.ByteArray (ByteArray, ByteArrayAccess,+ convert, singleton, takeView,+ view)+import qualified Data.ByteArray as BA (length, unpack)+import Data.ByteString.Builder (intDec, toLazyByteString)+import qualified Data.ByteString.Lazy as LBS (toStrict)+import Data.Monoid ((<>))+import Data.Word (Word8)++import Crypto.Ethereum.Utils (exportKey)+import Data.ByteArray.HexString (HexString)+import Data.Solidity.Prim.Address (Address)++-- | 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)++-- | Sign arbitrary data by given private key.+--+-- /WARNING:/ Vulnerable to timing attacks.+sign :: ByteArrayAccess bin+ => PrivateKey+ -> bin+ -> (Integer, Integer, Word8)+sign pk bin = fst $ withDRG hmac_drbg $ ecsign pk (os2ip truncated)+ where+ hmac_drbg :: HmacDrbg SHA256+ hmac_drbg = initialize $ exportKey pk <> truncated+ truncated = convert $ takeView bin 32 :: HexString++ecsign :: MonadRandom m+ => PrivateKey+ -> Integer+ -> m (Integer, Integer, Word8)+ecsign pk@(PrivateKey curve d) z = do+ k <- generateBetween 0 (n - 1)+ case trySign k of+ Nothing -> ecsign pk z+ Just rsv -> return rsv+ where+ n = ecc_n (common_curve curve)+ g = ecc_g (common_curve curve)+ recoveryParam x y r = fromIntegral $+ fromEnum (odd y) .|. if x /= r then 2 else 0+ trySign k = do+ (kpX, kpY) <- case pointMul curve k g of+ PointO -> Nothing+ Point x y -> return (x, y)+ let r = kpX `mod` n+ kInv <- inverse k n+ let s = kInv * (z + r * d) `mod` n+ when (r == 0 || s == 0) Nothing+ -- Recovery param+ let v = recoveryParam kpX kpY r+ -- Use complement of s if it > n / 2+ let (s', v') | s > n `div` 2 = (n - s, v `xor` 1)+ | otherwise = (s, v)+ return $ (r, s', v' + 27)++-- | Unpack recoverable signature from byte array.+--+-- Input array should have 65 byte length.+unpack :: ByteArrayAccess rsv => rsv -> (Integer, Integer, Word8)+unpack vrs = (r, s, v)+ where+ r = os2ip (view vrs 1 33)+ s = os2ip (view vrs 33 65)+ v = head (BA.unpack vrs)++-- | Pack recoverable signature as byte array (65 byte length).+pack :: ByteArray rsv => (Integer, Integer, Word8) -> rsv+pack (r, s, v) = i2osp r <> i2osp s <> singleton v
+ src/Crypto/Ethereum/Utils.hs view
@@ -0,0 +1,53 @@+-- |+-- Module : Crypto.Ethereum.Utils+-- Copyright : Alexander Krupenkin 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : portable+--+-- EC cryptography on Secp256k1 curve.+--++module Crypto.Ethereum.Utils where++import Crypto.Hash (Keccak_256 (..), hashWith)+import Crypto.Number.Serialize (i2osp, os2ip)+import Crypto.PubKey.ECC.ECDSA (PrivateKey (..), PublicKey (..))+import Crypto.PubKey.ECC.Generate (generateQ)+import Crypto.PubKey.ECC.Types (CurveName (SEC_p256k1), Point (..),+ getCurveByName)+import Data.ByteArray (ByteArray, ByteArrayAccess,+ convert)+import Data.Monoid ((<>))++-- | Import Ethereum private key from byte array.+--+-- Input array should have 32 byte length.+importKey :: ByteArrayAccess privateKey => privateKey -> PrivateKey+{-# INLINE importKey #-}+importKey = PrivateKey (getCurveByName SEC_p256k1) . os2ip++-- | Export private key to byte array (32 byte length).+exportKey :: ByteArray privateKey => PrivateKey -> privateKey+{-# INLINE exportKey #-}+exportKey (PrivateKey _ key) = i2osp key++-- | Get public key appropriate to private key.+--+-- /WARNING:/ Vulnerable to timing attacks.+derivePubKey :: PrivateKey -> PublicKey+{-# INLINE derivePubKey #-}+derivePubKey (PrivateKey curve p) = PublicKey curve (generateQ curve p)++-- | Export public key to byte array (64 byte length).+exportPubKey :: ByteArray publicKey => PublicKey -> publicKey+{-# INLINE exportPubKey #-}+exportPubKey (PublicKey _ (Point x y)) = i2osp x <> i2osp y+exportPubKey (PublicKey _ PointO) = mempty++-- | Keccak 256 hash function.+sha3 :: (ByteArrayAccess bin, ByteArray bout) => bin -> bout+{-# INLINE sha3 #-}+sha3 = convert . hashWith Keccak_256
+ src/Crypto/Random/HmacDrbg.hs view
@@ -0,0 +1,68 @@+-- |+-- Module : Crypto.Random.HmacDrbg+-- Copyright : Alexander Krupenkin 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : portable+--+-- NIST standardized number-theoretically secure random number generator.+-- https://csrc.nist.gov/csrc/media/events/random-number-generation-workshop-2004/documents/hashblockcipherdrbg.pdf+--+-- XXX: This algorithm requires reseed after 2^48 iterations.+--+-- > Inspired by https://github.com/TomMD/DRBG and https://github.com/indutny/hmac-drbg.+--++module Crypto.Random.HmacDrbg+ (+ HmacDrbg+ , initialize+ ) where+++import Crypto.Hash (HashAlgorithm, digestFromByteString,+ hashDigestSize)+import Crypto.MAC.HMAC (HMAC (..), hmac)+import Crypto.Random (DRG (..))+import Data.ByteArray (ByteArray, convert, singleton)+import qualified Data.ByteArray as BA (null, take)+import qualified Data.ByteString as B (replicate)+import Data.Maybe (fromJust)+import Data.Monoid ((<>))+import Data.Word (Word8)++-- | HMAC Deterministic Random Bytes Generator.+newtype HmacDrbg a = HmacDrbg (HMAC a, HMAC a)+ deriving Eq++instance HashAlgorithm a => DRG (HmacDrbg a) where+ randomBytesGenerate = generate++update :: (ByteArray bin, HashAlgorithm a)+ => bin+ -> HmacDrbg a+ -> HmacDrbg a+update input | BA.null input = go 0x00+ | otherwise = go 0x01 . go 0x00+ where+ go :: HashAlgorithm a => Word8 -> HmacDrbg a -> HmacDrbg a+ go c (HmacDrbg (k, v)) = let k' = hmac k (convert v <> singleton c <> input)+ v' = hmac k' v+ in HmacDrbg (k', v')++-- | Initialize HMAC-DRBG by seed.+initialize :: (ByteArray seed, HashAlgorithm a)+ => seed+ -> HmacDrbg a+initialize = flip update $ HmacDrbg (hmac0 undefined 0x00, hmac0 undefined 0x01)+ where+ hmac0 :: HashAlgorithm a => a -> Word8 -> HMAC a+ hmac0 a = HMAC . fromJust . digestFromByteString . B.replicate (hashDigestSize a)++generate :: (HashAlgorithm a, ByteArray output) => Int -> HmacDrbg a -> (output, HmacDrbg a)+generate reqBytes (HmacDrbg (k, v)) = (output, HmacDrbg (k, vFinal))+ where+ getV (u, rest) = let v' = hmac k u in (v', rest <> convert v')+ (vFinal, output) = BA.take reqBytes <$> iterate getV (v, mempty) !! reqBytes
src/Data/ByteArray/HexString.hs view
@@ -48,15 +48,13 @@ instance ToJSON HexString where toJSON = String . toText --- | Smart constructor which validates that all the text are actually--- have `0x` prefix, hexadecimal characters and length is even.+-- | Smart constructor which trims '0x' and validates length is even. hexString :: ByteArray ba => ba -> Either String HexString-hexString bs- | BA.take 2 bs == hexStart = HexString <$> bs'- | otherwise = Left "Hex string should start from '0x'"+hexString bs = HexString <$> convertFromBase Base16 bs' where hexStart = convert ("0x" :: ByteString)- bs' = convertFromBase Base16 (BA.drop 2 bs)+ bs' | BA.take 2 bs == hexStart = BA.drop 2 bs+ | otherwise = bs -- | Reads a raw bytes and converts to hex representation. fromBytes :: ByteArrayAccess ba => ba -> HexString
src/Data/Solidity/Prim/Address.hs view
@@ -23,7 +23,7 @@ , toHexString , fromHexString - -- * Public key to @Address@ convertor+ -- * Derive address from public key , fromPubKey -- * EIP55 Mix-case checksum address encoding@@ -32,12 +32,11 @@ ) where import Control.Monad ((<=<))-import Crypto.Hash (Keccak_256 (..), hashWith)-import Crypto.Secp256k1 (PubKey, exportPubKey)+import Crypto.PubKey.ECC.ECDSA (PublicKey) import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Bits ((.&.)) import Data.Bool (bool)-import Data.ByteArray (convert, zero)+import Data.ByteArray (zero) import qualified Data.ByteArray as BA (drop) import Data.ByteString (ByteString) import qualified Data.ByteString as BS (take, unpack)@@ -50,6 +49,7 @@ import Generics.SOP (Generic) import qualified GHC.Generics as GHC (Generic) +import Crypto.Ethereum.Utils (exportPubKey, sha3) import Data.ByteArray.HexString (HexString, fromBytes, toBytes, toText) import Data.Solidity.Abi (AbiGet (..), AbiPut (..),@@ -88,14 +88,14 @@ toJSON = toJSON . toHexString -- | Derive address from secp256k1 public key-fromPubKey :: PubKey -> Address+fromPubKey :: PublicKey -> Address fromPubKey key =- case decode $ zero 12 <> BA.drop 12 (sha3 key) of+ case decode $ zero 12 <> toAddress (exportPubKey key) of Right a -> a Left e -> error $ "Impossible error: " ++ e where- sha3 :: PubKey -> ByteString- sha3 = convert . hashWith Keccak_256 . BA.drop 1 . exportPubKey False+ toAddress :: HexString -> HexString+ toAddress = BA.drop 12 . sha3 -- | Decode address from hex string fromHexString :: HexString -> Either String Address@@ -113,7 +113,7 @@ toChecksum :: ByteString -> ByteString toChecksum addr = ("0x" <>) . C8.pack $ zipWith ($) upcaseVector lower where- upcaseVector = (>>= fourthBits) . BS.unpack . BS.take 20 . convert $ hashWith Keccak_256 (C8.pack lower)+ upcaseVector = (>>= fourthBits) . BS.unpack . BS.take 20 $ sha3 (C8.pack lower) fourthBits n = bool id C.toUpper <$> [n .&. 0x80 /= 0, n .&. 0x08 /= 0] lower = drop 2 . fmap C.toLower . C8.unpack $ addr
src/Data/Solidity/Prim/Int.hs view
@@ -51,7 +51,7 @@ toInteger = Basement.toInteger quotRem a b = (Basement.quot a b, Basement.rem a b) --- | Unsigned integer with fixed length in bits+-- | Unsigned integer with fixed length in bits. newtype UIntN (n :: Nat) = UIntN { unUIntN :: Word256 } deriving (Eq, Ord, Enum, Bits, Generic) @@ -91,7 +91,7 @@ instance (n <= 256) => AbiPut (UIntN n) where abiPut = putWord256 . unUIntN --- Signed integer with fixed length in bits+-- | Signed integer with fixed length in bits. newtype IntN (n :: Nat) = IntN { unIntN :: Word256 } deriving (Eq, Ord, Enum, Bits, Generic) @@ -131,9 +131,11 @@ instance (n <= 256) => AbiPut (IntN n) where abiPut = putWord256 . unIntN +-- | Serialize 256 bit unsigned integer. putWord256 :: Putter Word256 putWord256 (Word256 a3 a2 a1 a0) = put a3 >> put a2 >> put a1 >> put a0 +-- | Deserialize 256 bit unsigned integer. getWord256 :: Get Word256 getWord256 = Word256 <$> get <*> get <*> get <*> get
− src/Language/Solidity/Compiler.hs
@@ -1,58 +0,0 @@-{-# LANGUAGE CPP #-}-#ifdef SOLIDITY_COMPILER-{-# LANGUAGE RecordWildCards #-}-#endif---- |--- Module : Language.Solidity.Compiler--- Copyright : Alexander Krupenkin 2017--- License : BSD3------ Maintainer : mail@akru.me--- Stability : experimental--- Portability : portable------ Solidity compiler high-level bindings.-----module Language.Solidity.Compiler where--#ifdef SOLIDITY_COMPILER--import Data.ByteString (ByteString)-import Data.Map (Map)-import Data.Semigroup (Semigroup (..))-import qualified Language.Solidity.Compiler.Foreign as FFI-import System.IO.Unsafe (unsafePerformIO)---- | Input contract sources-data Sources = Sources- { sourceMap :: Map ByteString ByteString- -- ^ Source code map from contract name in keys- , libraries :: Map ByteString ByteString- -- ^ Library address map for linking- , optimization :: Bool- -- ^ Enable optimization?- } deriving (Eq, Show)--instance Semigroup Sources where- a <> b = Sources (sourceMap a `mappend` sourceMap b)- (libraries a `mappend` libraries b)- (optimization a || optimization b)--instance Monoid Sources where- mappend = (<>)- mempty = Sources mempty mempty False--type Compiled = Map ByteString (ByteString, ByteString)---- | Compile Solidity contracts-compile :: Sources- -- ^ Contract sources- -> Either ByteString Compiled- -- ^ Error string or compiled contracts ABI and hex-{-# NOINLINE compile #-}-compile Sources{..} = unsafePerformIO $- FFI.compile sourceMap libraries optimization--#endif
− src/Language/Solidity/Compiler/Foreign.hsc
@@ -1,106 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE ForeignFunctionInterface #-}---- |--- Module : Language.Solidity.Compiler.Foreign--- Copyright : Alexander Krupenkin 2017--- License : BSD3------ Maintainer : mail@akru.me--- Stability : experimental--- Portability : unportable------ Ethereum Solidity library FFI.-----module Language.Solidity.Compiler.Foreign where--#ifdef SOLIDITY_COMPILER-#include <solidity_lite.h>--import Data.Map as M-import Data.ByteString.Char8 as BS-import Data.Typeable (Typeable)-import Foreign.C.String (CString, peekCString, withCString)-import Foreign.C.Types (CInt(..))-import Foreign.Ptr (Ptr)-import Foreign (free)--data Solidity- deriving Typeable--foreign import ccall unsafe "create" c_create- :: IO (Ptr Solidity)-foreign import ccall unsafe "destroy" c_destroy- :: Ptr Solidity -> IO ()-foreign import ccall unsafe "addSource" c_addSource- :: Ptr Solidity -> CString -> CString -> IO CInt-foreign import ccall unsafe "addLibrary" c_addLibrary- :: Ptr Solidity -> CString -> CString -> IO CInt-foreign import ccall unsafe "compile" c_compile- :: Ptr Solidity -> CInt -> IO CInt-foreign import ccall unsafe "abi" c_abi- :: Ptr Solidity -> CString -> IO CString-foreign import ccall unsafe "binary" c_binary- :: Ptr Solidity -> CString -> IO CString-foreign import ccall unsafe "errors" c_errors- :: Ptr Solidity -> IO CString--withSolidity :: (Ptr Solidity -> IO a) -> IO a-{-# INLINE withSolidity #-}-withSolidity f = do- ptr <- c_create- r <- f ptr- c_destroy ptr- return r--withCStringPair :: (CString -> CString -> IO a) -> (ByteString, ByteString) -> IO a-{-# INLINE withCStringPair #-}-withCStringPair f (a, b) =- withCString (BS.unpack a) $ \astr ->- withCString (BS.unpack b) $ \bstr ->- f astr bstr--addSources :: Ptr Solidity -> Map ByteString ByteString -> IO ()-{-# INLINE addSources #-}-addSources ptr = mapM_ (withCStringPair $ c_addSource ptr) . M.toList--addLibraries :: Ptr Solidity -> Map ByteString ByteString -> IO ()-{-# INLINE addLibraries #-}-addLibraries ptr = mapM_ (withCStringPair $ c_addLibrary ptr) . M.toList--result :: Ptr Solidity -> ByteString -> IO (ByteString, ByteString)-result ptr name =- withCString (BS.unpack name) $ \cname -> do- abi <- c_abi ptr cname- bin <- c_binary ptr cname- res <- (,) <$> fmap BS.pack (peekCString abi)- <*> fmap BS.pack (peekCString bin)- free abi- free bin- return res--errors :: Ptr Solidity -> IO ByteString-errors ptr = do- errs <- c_errors ptr- res <- BS.pack <$> peekCString errs- free errs- return res--compile :: Map ByteString ByteString- -> Map ByteString ByteString- -> Bool- -> IO (Either ByteString (Map ByteString (ByteString, ByteString)))-compile srcs libs opt =- withSolidity $ \ptr -> do- addSources ptr srcs- addLibraries ptr libs- res <- c_compile ptr optI- let compiled = sequence $ M.mapWithKey (const . result ptr) srcs- if res < 0- then Left <$> errors ptr- else Right <$> compiled- where optI | opt = 1- | otherwise = 0--#endif
src/Network/Ethereum/Account.hs view
@@ -29,8 +29,8 @@ , Personal(..) -- * Local key account- , PrivateKeyAccount- , PrivateKey(..)+ , LocalKeyAccount+ , LocalKey(..) -- * Transaction paramitrization function and lenses , withParam@@ -43,12 +43,12 @@ ) where -import Network.Ethereum.Account.Class (Account (..))-import Network.Ethereum.Account.Default (DefaultAccount)-import Network.Ethereum.Account.Internal (account, block, gasLimit,- gasPrice, to, value,- withParam)-import Network.Ethereum.Account.Personal (Personal (..),- PersonalAccount)-import Network.Ethereum.Account.PrivateKey (PrivateKey (..),- PrivateKeyAccount)+import Network.Ethereum.Account.Class (Account (..))+import Network.Ethereum.Account.Default (DefaultAccount)+import Network.Ethereum.Account.Internal (account, block, gasLimit,+ gasPrice, to, value,+ withParam)+import Network.Ethereum.Account.LocalKey (LocalKey (..),+ LocalKeyAccount)+import Network.Ethereum.Account.Personal (Personal (..),+ PersonalAccount)
+ src/Network/Ethereum/Account/LocalKey.hs view
@@ -0,0 +1,96 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeSynonymInstances #-}++-- |+-- Module : Network.Ethereum.Account.LocalKey+-- Copyright : Alexander Krupenkin 2018+-- Roy Blankman 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : unportable+--+-- Using ECC for singing transactions locally, e.g. out of Ethereum node.+-- Transaction will send using 'eth_sendRawTransacion' JSON-RPC method.+--++module Network.Ethereum.Account.LocalKey where++import Control.Monad.State.Strict (get, runStateT)+import Control.Monad.Trans (lift)+import Crypto.PubKey.ECC.ECDSA (PrivateKey)+import Data.ByteArray (convert)+import Data.ByteString (empty)+import Data.Default (Default (..))+import Data.Monoid ((<>))+import Data.Proxy (Proxy (..))++import Crypto.Ethereum (derivePubKey, importKey)+import Crypto.Ethereum.Signature (signTransaction)+import Data.Solidity.Abi.Codec (decode, encode)+import Data.Solidity.Prim.Address (fromPubKey)+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 (..))+import Network.Ethereum.Chain (foundation)+import Network.Ethereum.Contract.Method (selector)+import Network.Ethereum.Transaction (encodeTransaction)++-- | Local EOA params+data LocalKey = LocalKey+ { localKeyPrivate :: !PrivateKey+ , localKeyChainId :: !Integer+ } deriving (Eq, Show)++instance Default LocalKey where+ def = LocalKey (importKey empty) foundation++type LocalKeyAccount = AccountT LocalKey++instance Account LocalKey LocalKeyAccount 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 $ localKeyPrivate _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 packer = encodeTransaction params' (localKeyChainId _account)+ signed = signTransaction packer (localKeyPrivate _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 $ localKeyPrivate _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
− src/Network/Ethereum/Account/PrivateKey.hs
@@ -1,130 +0,0 @@-{-# 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
src/Network/Ethereum/Chain.hs view
@@ -7,6 +7,7 @@ -- Stability : experimental -- Portability : unportable --+-- Ethereum chain IDs. -- module Network.Ethereum.Chain where
src/Network/Ethereum/Contract/TH.hs view
@@ -193,7 +193,7 @@ , instanceD' nonIndexedName (conT ''Generic) [] , instanceD' nonIndexedName (conT ''AbiType) [funD' 'isDynamic [] [|const False|]] , instanceD' nonIndexedName (conT ''AbiGet) []- , dataD' allName (recC allName (map (\(n, a) -> ((\(b,t) -> return (n,b,t)) <=< toBang <=< typeQ $ a)) allArgs)) derivingD+ , dataD' allName (recC allName (map (\(n, a) -> (\(b,t) -> return (n,b,t)) <=< toBang <=< typeQ $ a) allArgs)) derivingD , instanceD' allName (conT ''Generic) [] , instanceD (cxt []) (pure $ ConT ''IndexedEvent `AppT` ConT indexedName `AppT` ConT nonIndexedName `AppT` ConT allName)
+ src/Network/Ethereum/Transaction.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- |+-- Module : Network.Ethereum.Transaction+-- Copyright : Alexander Krupenkin 2018+-- Roy Blankman 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : unportable+--+-- Transaction managing utils.+--++module Network.Ethereum.Transaction where++import Data.ByteArray (ByteArray, convert)+import Data.ByteString (ByteString, empty)+import Data.Maybe (fromJust, fromMaybe)+import Data.RLP (packRLP, rlpEncode)+import Data.Word (Word8)++import Data.ByteArray.HexString (toBytes)+import Data.Solidity.Prim.Address (toHexString)+import Network.Ethereum.Api.Types (Call (..), Quantity (unQuantity))+import Network.Ethereum.Unit (Shannon, toWei)++-- | Ethereum transaction packer.+--+-- Two way RLP encoding of Ethereum transaction: for unsigned and signed.+-- Packing scheme described in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md+encodeTransaction :: ByteArray ba+ => Call+ -- ^ Transaction call+ -> Integer+ -- ^ Chain ID+ -> Maybe (Integer, Integer, Word8)+ -- ^ Should contain signature when transaction signed+ -> ba+ -- ^ RLP encoded transaction+encodeTransaction Call{..} chain_id rsv =+ 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++ in convert . packRLP $ case rsv of+ -- Unsigned transaction by EIP155+ Nothing -> rlpEncode (nonce, gasPrice, gasLimit, to, value, input, chain_id, empty, empty)+ -- Signed transaction+ Just (r, s, v) ->+ let v' = fromIntegral v + 8 + 2 * chain_id -- Improved 'v' according to EIP155+ in rlpEncode (nonce, gasPrice, gasLimit, to, value, input, v', r, s)+ where+ defaultGasPrice = toWei (10 :: Shannon)
stack.yaml view
@@ -1,5 +1,5 @@ # Resolver to choose a 'specific' stackage snapshot or a compiler version.-resolver: lts-12.19+resolver: lts-13.2 # User packages to be built. packages:@@ -7,7 +7,6 @@ # Extra package dependencies extra-deps:-- secp256k1-haskell-0.1.4 - relapse-1.0.0.0 # Dependencies bounds@@ -21,6 +20,5 @@ - jsoncpp - solc - solc.dev- - secp256k1 - haskellPackages.hlint- - haskellPackages.stylish-haskell+ #- haskellPackages.stylish-haskell
test/Network/Ethereum/Web3/Test/ComplexStorageSpec.hs view
@@ -10,7 +10,7 @@ {-# LANGUAGE TemplateHaskell #-} -- |--- Module : Network.Ethereum.Web3.Test.ComplexStorage+-- Module : Network.Ethereum.Web3.Test.ComplexStorageSpec -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 --
test/Network/Ethereum/Web3/Test/LinearizationSpec.hs view
@@ -13,7 +13,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} --- Module : Network.Ethereum.Web3.Test.SimpleStorage+-- Module : Network.Ethereum.Web3.Test.LinearizationSpec -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 --@@ -21,9 +21,6 @@ -- Stability : experimental -- Portability : unportable ----- SimpleStorage is a Solidity contract which stores a uint256.--- The point of this test is to test function calls to update and--- read the value, as well as an event monitor. module Network.Ethereum.Web3.Test.LinearizationSpec where
+ test/Network/Ethereum/Web3/Test/LocalAccountSpec.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE OverloadedStrings #-}++-- Module : Network.Ethereum.Web3.Test.LocalAccountSpec+-- Copyright : Alexander Krupenkin 2018+-- License : BSD3+--+-- Maintainer : mail@akru.me+-- Stability : experimental+-- Portability : unportable+--+-- Simple local account transaction test.+--++module Network.Ethereum.Web3.Test.LocalAccountSpec where++import Lens.Micro ((.~))+import Lens.Micro.Mtl ((.=))+import Test.Hspec++import Crypto.Ethereum.Utils (derivePubKey, importKey)+import Data.ByteArray.HexString (HexString)+import Data.Solidity.Prim.Address (fromPubKey)+import Network.Ethereum.Account (LocalKey (..), send, to,+ value, withAccount,+ withParam)+import Network.Ethereum.Api.Eth (getBalance)+import Network.Ethereum.Api.Types (DefaultBlock (Pending))+import Network.Ethereum.Unit (Ether, toWei)+import Network.Ethereum.Web3.Test.Utils (web3)++spec :: Spec+spec = describe "Local account transactions" $ do+ it "should send value" $ do+ let key = "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" :: HexString+ local = LocalKey (importKey key) 420123+ localAddress = fromPubKey (derivePubKey $ importKey key)+ dest = "0x0000000000000000000000000000000000000042"++ -- Prepare+ web3 $ withAccount () $+ withParam (to .~ localAddress) $ do+ value .= (1 :: Ether)+ send ()++ balance <- web3 $ do+ withAccount local $+ withParam (to .~ dest) $ do+ value .= (0.5 :: Ether)+ send ()+ getBalance dest Pending++ fromIntegral balance `shouldBe` toWei (0.5 :: Ether)
test/Network/Ethereum/Web3/Test/SimpleStorageSpec.hs view
@@ -12,7 +12,7 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} --- Module : Network.Ethereum.Web3.Test.SimpleStorage+-- Module : Network.Ethereum.Web3.Test.SimpleStorageSpec -- Copyright : Alexander Krupenkin 2016 -- License : BSD3 --
− unit/Crypto/Ethereum/Test/EcdsaSpec.hs
@@ -1,32 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Crypto.Ethereum.Test.EcdsaSpec where--import Crypto.Secp256k1 (SecKey, derivePubKey)-import Data.ByteArray (convert)-import Data.ByteString (ByteString, pack)-import Data.Serialize (decode)-import Test.Hspec-import Test.Hspec.QuickCheck--import Crypto.Ethereum-import Data.ByteArray.HexString (HexString)-import Data.Solidity.Prim.Address (fromPubKey)--spec :: Spec-spec = do- describe "Ethereum signatures" $ do- let key = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" :: SecKey- address = fromPubKey (derivePubKey key)- message = "Hello World!" :: ByteString- sign = "0xd4a9620cd94387a31b9333935f1e76fbee8467c283d07c39c1606dc4e2af021e317293af09601dbb48f547e40f6b98fe8a67a23dcd1f7f8d054695a81521177001" :: HexString- sign' = either error id $ decode (convert sign)-- it "sign message by Ethereum private key" $ do- ecsign key message `shouldBe` sign'-- it "verify message by Ethereum public key" $ do- ecrecover sign' message `shouldBe` Just address-- prop "round robin sign/verify for random message" $ \chars ->- let msg = pack chars- in ecrecover (ecsign key msg) msg `shouldBe` Just address
+ unit/Crypto/Ethereum/Test/KeyfileSpec.hs view
@@ -0,0 +1,99 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Ethereum.Test.KeyfileSpec where++import Data.Aeson (eitherDecode)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as L (ByteString)+import Data.UUID.Types (UUID)+import Test.Hspec++import Crypto.Ethereum.Keyfile+import Data.ByteArray.HexString (HexString)++pbkdf2_test_keyfile :: L.ByteString+pbkdf2_test_keyfile = "\+\{\+\ \"crypto\" : {\+\ \"cipher\" : \"aes-128-ctr\",\+\ \"cipherparams\" : {\+\ \"iv\" : \"6087dab2f9fdbbfaddc31a909735c1e6\"\+\ },\+\ \"ciphertext\" : \"5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46\",\+\ \"kdf\" : \"pbkdf2\",\+\ \"kdfparams\" : {\+\ \"c\" : 262144,\+\ \"dklen\" : 32,\+\ \"prf\" : \"hmac-sha256\",\+\ \"salt\" : \"ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd\"\+\ },\+\ \"mac\" : \"517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2\"\+\ },\+\ \"id\" : \"3198bc9c-6672-5ab3-d995-4942343ae5b6\",\+\ \"version\" : 3\+\}"++scrypt_test_keyfile :: L.ByteString+scrypt_test_keyfile = "\+\{\+\ \"crypto\" : {\+\ \"cipher\" : \"aes-128-ctr\",\+\ \"cipherparams\" : {\+\ \"iv\" : \"83dbcc02d8ccb40e466191a123791e0e\"\+\ },\+\ \"ciphertext\" : \"d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c\",\+\ \"kdf\" : \"scrypt\",\+\ \"kdfparams\" : {\+\ \"dklen\" : 32,\+\ \"n\" : 262144,\+\ \"r\" : 1,\+\ \"p\" : 8,\+\ \"salt\" : \"ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19\"\+\ },\+\ \"mac\" : \"2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097\"\+\ },\+\ \"id\" : \"3198bc9c-6672-5ab3-d995-4942343ae5b6\",\+\ \"version\" : 3\+\}"++test_uuid :: UUID+test_uuid = read "3198bc9c-6672-5ab3-d995-4942343ae5b6"++test_pbkdf2_mac :: HexString+test_pbkdf2_mac = "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"++test_scrypt_mac :: HexString+test_scrypt_mac = "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"++test_password :: ByteString+test_password = "testpassword"++test_private :: HexString+test_private = "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"++spec :: Spec+spec = do+ describe "AES-128-CTR and PBKDF2-SHA-256" $ do+ it "can decode keyfile" $+ case eitherDecode pbkdf2_test_keyfile of+ Left e -> error e+ Right ekey -> do+ encryptedKeyId ekey `shouldBe` test_uuid+ encryptedKeyMac ekey `shouldBe` test_pbkdf2_mac++ it "can decrypt keyfile" $ do+ case eitherDecode pbkdf2_test_keyfile of+ Left e -> error e+ Right ekey -> decrypt ekey test_password `shouldBe` Just test_private++ describe "AES-128-CTR and Scrypt" $ do+ it "can decode keyfile" $+ case eitherDecode scrypt_test_keyfile of+ Left e -> error e+ Right ekey -> do+ encryptedKeyId ekey `shouldBe` test_uuid+ encryptedKeyMac ekey `shouldBe` test_scrypt_mac++ it "can decrypt keyfile" $+ case eitherDecode scrypt_test_keyfile of+ Left e -> error e+ Right ekey -> decrypt ekey test_password `shouldBe` Just test_private
+ unit/Crypto/Ethereum/Test/SignatureSpec.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Ethereum.Test.SignatureSpec where++import Data.ByteArray (convert)+import Data.Foldable (for_)+import Test.Hspec+import Test.Hspec.QuickCheck++import Crypto.Ethereum.Signature (hashMessage, signMessage,+ signTransaction)+import Crypto.Ethereum.Utils (importKey)+import Data.ByteArray.HexString (HexString)+import Data.Solidity.Prim.Address (fromPubKey)+import Network.Ethereum.Api.Types (Call (..))+import Network.Ethereum.Transaction (encodeTransaction)++test_vector :: [(HexString, HexString, HexString)]+test_vector = [+ ("0xa8510b592963f52d2d8b3413ab5822111c68d45370f6104a869fe6e72c58952fe6b420115a9dfadaae2956ad761db2f113", "0x5cc3704a552b40515909a220d3cb91e850d4d6c6c0180424e433d00400612ed6", "0x3477bfac3621680c30cf3ad59da9288bc53e46773af4911592c6f3b2ba9029861fac19b0fa0cc543731133346eb817cd7f3aa4cabac5e613919567bc9704af861c"),+ ("0x5680f1d195bbe2b0f1601a039ad23a6ad488c1f0949a68e84bffe398ad", "0xcaf03f0aa6862758e526dd76782a4afa7ad48533f00f8f9e8731d4a6b1656d9f", "0x659263cb4d54c653e5ef41550f959e15a71b01f19bce88750c8c3cad8ab293d12138db412f471df6c67575d49e9c25b6973a3405396a3e6985a3f391efee74751c"),+ ("0x487f9a", "0xa13bf6fdb2e2a5790f4a80d822180c7851b227d16c1893de2359dc98b1e27756", "0x143da940f9e144218979074cfde306d0697b3ddaec4c5e464a5aa093733f957776ba158ec4492a6cdc9951c6e0c1b10040916cb503ad240ed3b76677f9ac4ad41c"),+ ("0x958c2c5bafc5eb97004774f4c9b56aaecd37c06befac0042a93c67", "0x496b49cc02b63f927f2669a32dece10876e1a33dc0bd684f7643f849ecbe7c73", "0x34e768e959b89d305f16f796d0ff2da4e5923d6a6f33ba66b29471953e10be1a183743fb764f806ca8d97ad29dad94115d0368f374d5e085348267cc1c5bce411b"),+ ("0x02e2", "0x0425c9ac326acac4b93ff871f22c1dfe69c05038c2ac5b9eadb7e09743baa603", "0xbf52108b598a02aeeea1f9ee84947300985953edcfd07351f038bfe0de4c62df5cf467caaa7a33c3c4ebdf4aeaf05b1f4674f6b82b27e87fbf8d2a0980e68f1d1b"),+ ("0x74391586ff29f0638d7f7971640cefac3bfa3865ec5310e15a", "0x104432a31597e399f3f0f34e70b52a062feeb65aa3ad5383d2d7b90b0ab50126", "0x78c81fff1acdd1773badea5f35b0149e14b3dffe8de0e554b0edd80526acd55f1fc315b96afe5964df24f28b756f5651a6a52b2b9e0ffc87cda2a4a8f448a8951c"),+ ("0xd3ba712ef9dd8f8b37d805a9d1c2b5ee92db66dda0fd8582d55cec1b2519d18dfff2333e29081e80544af7244f2f22", "0x330ee59f080c59252218d1245029d8689b48b021dcf2c438938264bbc61d05af", "0xcf96a0f05e40d05ebcedbff0af522a68e6c846e63b44245a1208592ddf03cc12136ad522656f08fd838c55ff78e093d387d23b30b88a57e467636e63c786e04e1c"),+ ("0x1e99da3e7bb02e1f239bbf0b2ea113848673dd52da24c87f2f40", "0x52c58101fe861f9292fb6ad3df74551b5fb3feca9983b108645a5a9354bfbe99", "0x8eebcef599eb67e9379e7d6b074123628c1a65f1b754647704eb003fa61aff0a0e5c79fb022970fea80409e20a12bd4b8e1a79a787394a547fbc93c2d3c1e2dc1c"),+ ("0x1e973d1b5b158acc7d710099700c793e702b215a1fa534317d5c44406000fc727c", "0x1280413acfcd88a97b759e72e60069a1dc4f0a595e815aad9a1a83fa73f81af2", "0x7a77a37f2f4378dab5a0ba7f55b858a2a116f885f2eeab30dcd0b6d1f7286fbb7cbdccbd52721ce68fbcf2448a2f450a6bc6bc7f0027906259821bb3800133181b"),+ ("0x95da865540bd1336402a325a0435a920768f47d4b1ec0d89e0063f811872d1cb6423b5f4a4b931c9f41b216def", "0x0d537e54120ea923621225e3114b91a568a1abe7b7315b642017cad28cfad40b", "0xe04196529154ab89ceb598654f7d1ae178ecdcf73395aa8e8abb9200b504c39c58a93a6e502aaaddc2cbd712b436e9c9fb1010323927835ac54a7a77f11957f91c")+ ]++test_key :: HexString+test_key = "29461542faa1acbc968bcb332115e0537c023f8df416317602ca5d15ca12d02d"++spec :: Spec+spec = do+ describe "Ethereum ECDSA" $ do+ it "can hash Ethereum prefixed message" $ for_ test_vector $ \(msg, msgHash, _) ->+ convert (hashMessage msg) `shouldBe` msgHash++ it "can sign Ethereum prefixed message" $ for_ test_vector $ \(msg, _, msgSig) ->+ signMessage (importKey test_key) msg `shouldBe` msgSig++ it "can create valid raw transaction" $ do+ -- using same example as in this blog post:+ -- https://medium.com/@codetractio/walkthrough-of-an-ethereum-improvement-proposal-eip-6fda3966d171+ let testCall = Call Nothing+ (Just "0x3535353535353535353535353535353535353535")+ (Just 21000)+ (Just 20000000000)+ (Just 1000000000000000000)+ Nothing+ (Just 9)+ key = "4646464646464646464646464646464646464646464646464646464646464646" :: HexString+ correctSignedTx = "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"+ signTransaction (encodeTransaction testCall 1) (importKey key) `shouldBe` (correctSignedTx :: HexString)++{-+ it "verify message by Ethereum public key" $ do+ ecrecover sign' message `shouldBe` Just address++ prop "round robin sign/verify for random message" $ \chars ->+ let msg = pack chars+ in ecrecover (ecsign key msg) msg `shouldBe` Just address+-}+
+ unit/Crypto/Random/Test/HmacDrbgSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}+-- Inspired by https://github.com/indutny/hmac-drbg+module Crypto.Random.Test.HmacDrbgSpec where++import Crypto.Hash.Algorithms (SHA256)+import Crypto.Random (randomBytesGenerate)+import Data.ByteString (ByteString)+import Test.Hspec++import Crypto.Random.HmacDrbg+import Data.ByteArray.HexString (HexString)++spec :: Spec+spec = do+ describe "HMAC-DRBG-SHA256" $ do+ it "indutny/hmac-drbg test vectors" $ do+ let doDrbg :: ByteString -> HexString+ doDrbg seed = fst (randomBytesGenerate 32 (initialize seed) :: (HexString, HmacDrbg SHA256))++ doDrbg "totally random0123456789secret noncemy drbg"+ `shouldBe` "018ec5f8e08c41e5ac974eb129ac297c5388ee1864324fa13d9b15cf98d9a157"++ doDrbg "totally random0123456789secret nonce"+ `shouldBe` "ed5d61ecf0ef38258e62f03bbb49f19f2cd07ba5145a840d83b134d5963b3633"
unit/Data/Solidity/Test/AddressSpec.hs view
@@ -1,22 +1,23 @@ {-# LANGUAGE OverloadedStrings #-} module Data.Solidity.Test.AddressSpec where -import Crypto.Secp256k1 (SecKey, derivePubKey) import Data.ByteString (ByteString) import Data.ByteString.Char8 (unpack) import Data.Foldable (for_) import Data.Monoid ((<>)) import Test.Hspec +import Crypto.Ethereum.Utils (derivePubKey, importKey)+import Data.ByteArray.HexString (HexString) import Data.Solidity.Prim.Address spec :: Spec spec = do- describe "EIP55 Test Vectors" $ for_ checksummedAddrs (\addr ->- it (unpack addr <> " should be checksummed") $ verifyChecksum addr `shouldBe` True)+ describe "EIP55 Test Vectors" $ for_ checksummedAddrs $ \addr ->+ it (unpack addr <> " should be checksummed") $ verifyChecksum addr `shouldBe` True - describe "EIP55 Test Vectors Tampered" $ for_ unchecksummedAddrs (\addr ->- it (unpack addr <> " should not be checksummed") $ verifyChecksum addr `shouldBe` False)+ describe "EIP55 Test Vectors Tampered" $ for_ unchecksummedAddrs $ \addr ->+ it (unpack addr <> " should not be checksummed") $ verifyChecksum addr `shouldBe` False describe "Conversion from/to hex string" $ do it "should convert from/to on valid hex" $ do@@ -31,10 +32,10 @@ `shouldBe` Left "Incorrect address length: 1" - describe "Conversion from Secp256k1 keys" $ do- it "derivation from private key" $ do- let key = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" :: SecKey- fromPubKey (derivePubKey key)+ describe "Conversion from ECC keys" $ do+ it "can derive address from public key" $ do+ let key = "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20" :: HexString+ fromPubKey (derivePubKey $ importKey key) `shouldBe` "0x6370eF2f4Db3611D657b90667De398a2Cc2a370C" checksummedAddrs :: [ByteString]
unit/Language/Solidity/Test/CompilerSpec.hs view
@@ -11,18 +11,18 @@ import Language.Solidity.Compiler spec :: Spec-spec = describe "solidity" $ do+spec = describe "solidity compiler" $ do it "can compile empty contract" $ do compile (Sources [("A", "contract A {}")] [] True)- `shouldBe` Right [("A", ("[]\n", "6080604052348015600f57600080fd5b50603580601d6000396000f3006080604052600080fd00a165627a7a72305820613b8f0a4aab50fea86c1e4943bcddad6d753778395309a4e055efcda614b0690029"))]+ `shouldBe` Right [("A", ("[]\n", "6080604052348015600f57600080fd5b50603580601d6000396000f3fe6080604052600080fdfea165627a7a7230582055975a3cf5eb9a652c2154ede405cdb2137a09e47088fb89162c336da0b415c40029"))] it "can handle broken contract" $ do compile (Sources [("Fail", "contract Fail {")] [] True) `shouldBe` Left "Fail:1:16: Error: Function, variable, struct or modifier declaration expected.\ncontract Fail {\n ^\n" it "can compile simple contract" $ do- compile (Sources [("SimpleStorage", "contract SimpleStorage { uint256 public a; function set(uint256 _a) { a = _a; } }")] [] True)- `shouldBe` Right [("SimpleStorage", ("[\n\t{\n\t\t\"constant\" : true,\n\t\t\"inputs\" : [],\n\t\t\"name\" : \"a\",\n\t\t\"outputs\" : \n\t\t[\n\t\t\t{\n\t\t\t\t\"name\" : \"\",\n\t\t\t\t\"type\" : \"uint256\"\n\t\t\t}\n\t\t],\n\t\t\"payable\" : false,\n\t\t\"stateMutability\" : \"view\",\n\t\t\"type\" : \"function\"\n\t},\n\t{\n\t\t\"constant\" : false,\n\t\t\"inputs\" : \n\t\t[\n\t\t\t{\n\t\t\t\t\"name\" : \"_a\",\n\t\t\t\t\"type\" : \"uint256\"\n\t\t\t}\n\t\t],\n\t\t\"name\" : \"set\",\n\t\t\"outputs\" : [],\n\t\t\"payable\" : false,\n\t\t\"stateMutability\" : \"nonpayable\",\n\t\t\"type\" : \"function\"\n\t}\n]\n", "608060405234801561001057600080fd5b5060dc8061001f6000396000f3006080604052600436106049576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630dbe671f14604e57806360fe47b1146076575b600080fd5b348015605957600080fd5b50606060a0565b6040518082815260200191505060405180910390f35b348015608157600080fd5b50609e6004803603810190808035906020019092919050505060a6565b005b60005481565b80600081905550505600a165627a7a7230582053764f3cc73b0960abb0d97899a8133bee5eb98c131978821f7add85f33d4f2e0029"))]+ compile (Sources [("SimpleStorage", "contract SimpleStorage { uint256 public a; function set(uint256 _a) public { a = _a; } }")] [] True)+ `shouldBe` Right [("SimpleStorage", ("[\n\t{\n\t\t\"constant\" : true,\n\t\t\"inputs\" : [],\n\t\t\"name\" : \"a\",\n\t\t\"outputs\" : \n\t\t[\n\t\t\t{\n\t\t\t\t\"name\" : \"\",\n\t\t\t\t\"type\" : \"uint256\"\n\t\t\t}\n\t\t],\n\t\t\"payable\" : false,\n\t\t\"stateMutability\" : \"view\",\n\t\t\"type\" : \"function\"\n\t},\n\t{\n\t\t\"constant\" : false,\n\t\t\"inputs\" : \n\t\t[\n\t\t\t{\n\t\t\t\t\"name\" : \"_a\",\n\t\t\t\t\"type\" : \"uint256\"\n\t\t\t}\n\t\t],\n\t\t\"name\" : \"set\",\n\t\t\"outputs\" : [],\n\t\t\"payable\" : false,\n\t\t\"stateMutability\" : \"nonpayable\",\n\t\t\"type\" : \"function\"\n\t}\n]\n", "608060405234801561001057600080fd5b5060e38061001f6000396000f3fe6080604052600436106043576000357c0100000000000000000000000000000000000000000000000000000000900480630dbe671f14604857806360fe47b1146070575b600080fd5b348015605357600080fd5b50605a60a7565b6040518082815260200191505060405180910390f35b348015607b57600080fd5b5060a560048036036020811015609057600080fd5b810190808035906020019092919050505060ad565b005b60005481565b806000819055505056fea165627a7a7230582077f3d0f8c042c028ca18fb49065c0091b05c6b70dd5aee2b8a4388f7ecaa308f0029"))] #else
− unit/Network/Ethereum/Web3/Test/LocalAccountSpec.hs
@@ -1,26 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module Network.Ethereum.Web3.Test.LocalAccountSpec where--import Crypto.Ethereum (SecKey)-import Test.Hspec--import Network.Ethereum.Account.PrivateKey (signTransaction)-import Network.Ethereum.Api.Types (Call (..), Quantity (..))---- using same example as in this blog post:--- https://medium.com/@codetractio/walkthrough-of-an-ethereum-improvement-proposal-eip-6fda3966d171-spec :: Spec-spec = describe "transaction signing" $ do- let testCall = Call Nothing- (Just "0x3535353535353535353535353535353535353535")- (Just . Quantity $ 21000)- (Just . Quantity $ 20000000000)- (Just . Quantity $ 1000000000000000000)- Nothing- (Just 9)- privKey = "4646464646464646464646464646464646464646464646464646464646464646" :: SecKey- correctSignedTx = "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"-- it "can create valid raw transaction" $- signTransaction testCall 1 privKey `shouldBe` correctSignedTx
web3.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: web3-version: 0.8.2.1+version: 0.8.3.0 license: BSD3 license-file: LICENSE copyright: (c) Alexander Krupenkin 2016@@ -29,21 +29,25 @@ type: git location: https://github.com/airalab/hs-web3 -flag debug+flag compiler description:- Enable debug compiler options+ Enable Solidity compiler default: False manual: True -flag solidity+flag debug description:- Enable Solidity compiler+ Enable debug compiler options default: False manual: True library exposed-modules: Crypto.Ethereum+ Crypto.Ethereum.Keyfile+ Crypto.Ethereum.Signature+ Crypto.Ethereum.Utils+ Crypto.Random.HmacDrbg Data.ByteArray.HexString Data.Solidity.Abi Data.Solidity.Abi.Codec@@ -62,14 +66,12 @@ Data.Solidity.Prim.Tuple.TH Data.String.Extra Language.Solidity.Abi- Language.Solidity.Compiler- Language.Solidity.Compiler.Foreign Network.Ethereum.Account Network.Ethereum.Account.Class Network.Ethereum.Account.Default Network.Ethereum.Account.Internal+ Network.Ethereum.Account.LocalKey Network.Ethereum.Account.Personal- Network.Ethereum.Account.PrivateKey Network.Ethereum.Account.Safe Network.Ethereum.Api.Eth Network.Ethereum.Api.Net@@ -88,6 +90,7 @@ Network.Ethereum.Ens Network.Ethereum.Ens.PublicResolver Network.Ethereum.Ens.Registry+ Network.Ethereum.Transaction Network.Ethereum.Unit Network.Ethereum.Web3 Network.JsonRpc.TinyClient@@ -116,7 +119,7 @@ data-default >=0.7.1.1 && <0.8, exceptions >=0.8.3 && <0.11, generics-sop >=0.3.1.0 && <0.5,- http-client >=0.5.7.1 && <0.6,+ http-client >=0.5.7.1 && <0.7, http-client-tls >=0.3.5.1 && <0.4, machines >=0.6.3 && <0.7, memory >=0.14.11 && <0.15,@@ -127,37 +130,93 @@ mtl >=2.2.1 && <2.3, parsec >=3.1.11 && <3.2, relapse >=1.0.0.0 && <2.0,- secp256k1-haskell >=0.1.4 && <0.2, tagged >=0.8.5 && <0.9, template-haskell >=2.11.1.0 && <2.15, text >=1.2.2.2 && <1.3, transformers >=0.5.2.0 && <0.6,- vinyl >=0.5.3 && <0.11+ uuid-types >=1.0.3 && <1.1,+ vinyl >=0.5.3 && <0.12 if flag(debug) ghc-options: -ddump-splices - if flag(solidity)+ if flag(compiler) cpp-options: -DSOLIDITY_COMPILER c-sources:- ./cbits/solidity_lite.cpp+ ./compiler/cbits/solidity_lite.cpp+ hs-source-dirs: compiler+ other-modules:+ Language.Solidity.Compiler+ Language.Solidity.Compiler.Foreign extra-libraries: solidity- include-dirs: ./cbits+ include-dirs: ./compiler/cbits build-depends:- containers >=0.5.11.0 && <0.6+ containers >=0.6.0.1 && <0.7 test-suite live type: exitcode-stdio-1.0 main-is: Spec.hs- hs-source-dirs: test+ hs-source-dirs: test src other-modules: Network.Ethereum.Web3.Test.ComplexStorageSpec Network.Ethereum.Web3.Test.ERC20Spec Network.Ethereum.Web3.Test.LinearizationSpec+ Network.Ethereum.Web3.Test.LocalAccountSpec Network.Ethereum.Web3.Test.RegistrySpec Network.Ethereum.Web3.Test.SimpleStorageSpec Network.Ethereum.Web3.Test.Utils+ Crypto.Ethereum+ Crypto.Ethereum.Keyfile+ Crypto.Ethereum.Signature+ Crypto.Ethereum.Utils+ Crypto.Random.HmacDrbg+ Data.ByteArray.HexString+ Data.Solidity.Abi+ Data.Solidity.Abi.Codec+ Data.Solidity.Abi.Generic+ Data.Solidity.Event+ Data.Solidity.Event.Internal+ Data.Solidity.Prim+ Data.Solidity.Prim.Address+ Data.Solidity.Prim.Bool+ Data.Solidity.Prim.Bytes+ Data.Solidity.Prim.Int+ Data.Solidity.Prim.List+ Data.Solidity.Prim.String+ Data.Solidity.Prim.Tagged+ Data.Solidity.Prim.Tuple+ Data.Solidity.Prim.Tuple.TH+ Data.String.Extra+ Language.Solidity.Abi+ Network.Ethereum.Account+ Network.Ethereum.Account.Class+ Network.Ethereum.Account.Default+ Network.Ethereum.Account.Internal+ Network.Ethereum.Account.LocalKey+ Network.Ethereum.Account.Personal+ Network.Ethereum.Account.Safe+ Network.Ethereum.Api.Eth+ Network.Ethereum.Api.Net+ Network.Ethereum.Api.Personal+ Network.Ethereum.Api.Provider+ Network.Ethereum.Api.Types+ Network.Ethereum.Api.Web3+ Network.Ethereum.Chain+ Network.Ethereum.Contract+ Network.Ethereum.Contract.Event+ Network.Ethereum.Contract.Event.Common+ Network.Ethereum.Contract.Event.MultiFilter+ Network.Ethereum.Contract.Event.SingleFilter+ Network.Ethereum.Contract.Method+ Network.Ethereum.Contract.TH+ Network.Ethereum.Ens+ Network.Ethereum.Ens.PublicResolver+ Network.Ethereum.Ens.Registry+ Network.Ethereum.Transaction+ Network.Ethereum.Unit+ Network.Ethereum.Web3+ Network.JsonRpc.TinyClient Paths_web3 default-language: Haskell2010 ghc-options: -funbox-strict-fields -Wduplicate-exports@@ -185,7 +244,7 @@ hspec-contrib >=0.4.0 && <0.6, hspec-discover >=2.4.4 && <2.7, hspec-expectations >=0.8.2 && <0.9,- http-client >=0.5.7.1 && <0.6,+ http-client >=0.5.7.1 && <0.7, http-client-tls >=0.3.5.1 && <0.4, machines >=0.6.3 && <0.7, memory >=0.14.11 && <0.15,@@ -197,7 +256,6 @@ parsec >=3.1.11 && <3.2, random ==1.1.*, relapse >=1.0.0.0 && <2.0,- secp256k1-haskell >=0.1.4 && <0.2, split >=0.2.3 && <0.3, stm >=2.4.4 && <2.6, tagged >=0.8.5 && <0.9,@@ -205,22 +263,91 @@ text >=1.2.2.2 && <1.3, time >=1.6.0 && <1.9, transformers >=0.5.2.0 && <0.6,- vinyl >=0.5.3 && <0.11,- web3 -any+ uuid-types >=1.0.3 && <1.1,+ vinyl >=0.5.3 && <0.12+ + if flag(debug)+ ghc-options: -ddump-splices+ + if flag(compiler)+ cpp-options: -DSOLIDITY_COMPILER+ c-sources:+ ./compiler/cbits/solidity_lite.cpp+ hs-source-dirs: compiler+ other-modules:+ Language.Solidity.Compiler+ Language.Solidity.Compiler.Foreign+ extra-libraries:+ solidity+ include-dirs: ./compiler/cbits+ build-depends:+ containers >=0.6.0.1 && <0.7 test-suite unit type: exitcode-stdio-1.0 main-is: Spec.hs- hs-source-dirs: unit+ hs-source-dirs: unit src other-modules:- Crypto.Ethereum.Test.EcdsaSpec+ Crypto.Ethereum.Test.KeyfileSpec+ Crypto.Ethereum.Test.SignatureSpec+ Crypto.Random.Test.HmacDrbgSpec Data.Solidity.Test.AddressSpec Data.Solidity.Test.EncodingSpec Data.Solidity.Test.IntSpec Language.Solidity.Test.CompilerSpec Network.Ethereum.Web3.Test.EventSpec- Network.Ethereum.Web3.Test.LocalAccountSpec Network.Ethereum.Web3.Test.MethodDumpSpec+ Crypto.Ethereum+ Crypto.Ethereum.Keyfile+ Crypto.Ethereum.Signature+ Crypto.Ethereum.Utils+ Crypto.Random.HmacDrbg+ Data.ByteArray.HexString+ Data.Solidity.Abi+ Data.Solidity.Abi.Codec+ Data.Solidity.Abi.Generic+ Data.Solidity.Event+ Data.Solidity.Event.Internal+ Data.Solidity.Prim+ Data.Solidity.Prim.Address+ Data.Solidity.Prim.Bool+ Data.Solidity.Prim.Bytes+ Data.Solidity.Prim.Int+ Data.Solidity.Prim.List+ Data.Solidity.Prim.String+ Data.Solidity.Prim.Tagged+ Data.Solidity.Prim.Tuple+ Data.Solidity.Prim.Tuple.TH+ Data.String.Extra+ Language.Solidity.Abi+ Network.Ethereum.Account+ Network.Ethereum.Account.Class+ Network.Ethereum.Account.Default+ Network.Ethereum.Account.Internal+ Network.Ethereum.Account.LocalKey+ Network.Ethereum.Account.Personal+ Network.Ethereum.Account.Safe+ Network.Ethereum.Api.Eth+ Network.Ethereum.Api.Net+ Network.Ethereum.Api.Personal+ Network.Ethereum.Api.Provider+ Network.Ethereum.Api.Types+ Network.Ethereum.Api.Web3+ Network.Ethereum.Chain+ Network.Ethereum.Contract+ Network.Ethereum.Contract.Event+ Network.Ethereum.Contract.Event.Common+ Network.Ethereum.Contract.Event.MultiFilter+ Network.Ethereum.Contract.Event.SingleFilter+ Network.Ethereum.Contract.Method+ Network.Ethereum.Contract.TH+ Network.Ethereum.Ens+ Network.Ethereum.Ens.PublicResolver+ Network.Ethereum.Ens.Registry+ Network.Ethereum.Transaction+ Network.Ethereum.Unit+ Network.Ethereum.Web3+ Network.JsonRpc.TinyClient Paths_web3 default-language: Haskell2010 ghc-options: -funbox-strict-fields -Wduplicate-exports@@ -248,7 +375,7 @@ hspec-contrib >=0.4.0 && <0.6, hspec-discover >=2.4.4 && <2.7, hspec-expectations >=0.8.2 && <0.9,- http-client >=0.5.7.1 && <0.6,+ http-client >=0.5.7.1 && <0.7, http-client-tls >=0.3.5.1 && <0.4, machines >=0.6.3 && <0.7, memory >=0.14.11 && <0.15,@@ -259,10 +386,26 @@ mtl >=2.2.1 && <2.3, parsec >=3.1.11 && <3.2, relapse >=1.0.0.0 && <2.0,- secp256k1-haskell >=0.1.4 && <0.2, tagged >=0.8.5 && <0.9, template-haskell >=2.11.1.0 && <2.15, text >=1.2.2.2 && <1.3, transformers >=0.5.2.0 && <0.6,- vinyl >=0.5.3 && <0.11,- web3 -any+ uuid-types >=1.0.3 && <1.1,+ vinyl >=0.5.3 && <0.12+ + if flag(debug)+ ghc-options: -ddump-splices+ + if flag(compiler)+ cpp-options: -DSOLIDITY_COMPILER+ c-sources:+ ./compiler/cbits/solidity_lite.cpp+ hs-source-dirs: compiler+ other-modules:+ Language.Solidity.Compiler+ Language.Solidity.Compiler.Foreign+ extra-libraries:+ solidity+ include-dirs: ./compiler/cbits+ build-depends:+ containers >=0.6.0.1 && <0.7