├── .circleci └── config.yml ├── .credo.exs ├── .dialyzer.ignore-warnings ├── .formatter.exs ├── .gitignore ├── .gitmodules ├── .tool-versions ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── apps ├── abi │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── abi.ex │ │ └── abi │ │ │ ├── function_selector.ex │ │ │ ├── parser.ex │ │ │ ├── spec.ex │ │ │ ├── type_decoder.ex │ │ │ └── type_encoder.ex │ ├── mix.exs │ ├── priv │ │ └── dog.abi.json │ ├── src │ │ ├── ethereum_abi_lexer.xrl │ │ └── ethereum_abi_parser.yrl │ └── test │ │ ├── abi │ │ ├── function_selector_test.exs │ │ ├── spec_test.exs │ │ ├── type_decoder_test.exs │ │ └── type_encoder_test.exs │ │ ├── abi_test.exs │ │ └── test_helper.exs ├── blockchain │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── bit_helper.ex │ │ ├── blockchain.ex │ │ ├── blockchain │ │ │ ├── account.ex │ │ │ ├── application.ex │ │ │ ├── block.ex │ │ │ ├── blocktree.ex │ │ │ ├── bloom.ex │ │ │ ├── chain.ex │ │ │ ├── contract.ex │ │ │ ├── interface │ │ │ │ ├── account_interface.ex │ │ │ │ └── block_interface.ex │ │ │ ├── transaction.ex │ │ │ ├── transaction │ │ │ │ ├── receipt.ex │ │ │ │ └── signature.ex │ │ │ └── validation.ex │ │ ├── eth_common_test │ │ │ ├── harness.ex │ │ │ └── helpers.ex │ │ └── test.ex │ ├── mix.exs │ ├── priv │ │ └── ropsten.json │ └── test │ │ ├── bit_helper_test.exs │ │ ├── blockchain │ │ ├── account_test.exs │ │ ├── block_test.exs │ │ ├── blocktree_test.exs │ │ ├── bloom_test.exs │ │ ├── chain_test.exs │ │ ├── contract_test.exs │ │ ├── interface │ │ │ ├── account_interface_test.exs │ │ │ └── block_interface_test.exs │ │ ├── state_test.exs │ │ ├── transaction │ │ │ ├── receipt_test.exs │ │ │ └── signature_test.exs │ │ └── transaction_test.exs │ │ ├── blockchain_test.exs │ │ ├── ropsten_test.exs │ │ ├── support │ │ └── ropsten_blocks.dat │ │ └── test_helper.exs ├── evm │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── evm.ex │ │ ├── evm │ │ │ ├── address.ex │ │ │ ├── application.ex │ │ │ ├── block │ │ │ │ └── header.ex │ │ │ ├── builtin.ex │ │ │ ├── debugger.ex │ │ │ ├── debugger │ │ │ │ ├── breakpoint.ex │ │ │ │ └── command.ex │ │ │ ├── exec_env.ex │ │ │ ├── functions.ex │ │ │ ├── gas.ex │ │ │ ├── interface │ │ │ │ ├── account_interface.ex │ │ │ │ ├── block_interface.ex │ │ │ │ └── mock │ │ │ │ │ ├── mock_account_interface.ex │ │ │ │ │ └── mock_block_interface.ex │ │ │ ├── log_entry.ex │ │ │ ├── machine_code.ex │ │ │ ├── machine_state.ex │ │ │ ├── memory.ex │ │ │ ├── operation.ex │ │ │ ├── operation │ │ │ │ ├── block_information.ex │ │ │ │ ├── comparison_and_bitwise_logic.ex │ │ │ │ ├── duplication.ex │ │ │ │ ├── environmental_information.ex │ │ │ │ ├── exchange.ex │ │ │ │ ├── logging.ex │ │ │ │ ├── metadata.ex │ │ │ │ ├── metadata │ │ │ │ │ ├── block_information.ex │ │ │ │ │ ├── comparison_and_bitwise_logic.ex │ │ │ │ │ ├── duplication.ex │ │ │ │ │ ├── environmental_information.ex │ │ │ │ │ ├── exchange.ex │ │ │ │ │ ├── logging.ex │ │ │ │ │ ├── push.ex │ │ │ │ │ ├── sha3.ex │ │ │ │ │ ├── stack_memory_storage_and_flow.ex │ │ │ │ │ ├── stop_and_arithmetic.ex │ │ │ │ │ └── system.ex │ │ │ │ ├── push.ex │ │ │ │ ├── sha3.ex │ │ │ │ ├── stack_memory_storage_and_flow.ex │ │ │ │ ├── stop_and_arithmetic.ex │ │ │ │ └── system.ex │ │ │ ├── program_counter.ex │ │ │ ├── stack.ex │ │ │ ├── sub_state.ex │ │ │ ├── vm.ex │ │ │ └── wei.ex │ │ ├── helpers.ex │ │ └── math_helper.ex │ ├── mix.exs │ └── test │ │ ├── block │ │ └── header_test.exs │ │ ├── evm │ │ ├── breakpoint │ │ │ └── breakpoint_test.exs │ │ ├── debugger_test.exs │ │ ├── exec_env_test.exs │ │ ├── functions_test.exs │ │ ├── gas_test.exs │ │ ├── interface │ │ │ └── mock │ │ │ │ ├── mock_account_interface_test.exs │ │ │ │ └── mock_block_interface_test.exs │ │ ├── log_entry_test.exs │ │ ├── machine_code_test.exs │ │ ├── machine_state_test.exs │ │ ├── memory_test.exs │ │ ├── operation │ │ │ ├── block_information_test.exs │ │ │ ├── comparison_and_bitwise_logic_test.exs │ │ │ ├── duplication_test.exs │ │ │ ├── environmental_information_test.exs │ │ │ ├── exchange_test.exs │ │ │ ├── logging_test.exs │ │ │ ├── metadata_test.exs │ │ │ ├── push_test.exs │ │ │ ├── sha3_test.exs │ │ │ ├── stack_memory_storage_and_flow_test.exs │ │ │ ├── stop_and_arithmetic_test.exs │ │ │ └── system_test.exs │ │ ├── operation_test.exs │ │ ├── program_counter_test.exs │ │ ├── stack_test.exs │ │ ├── sub_state_test.exs │ │ ├── vm_test.exs │ │ └── wei_test.exs │ │ ├── evm_test.exs │ │ ├── helpers_test.exs │ │ ├── math_helper_test.exs │ │ └── test_helper.exs ├── ex_rlp │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── ex_rlp.ex │ │ └── ex_rlp │ │ │ ├── decode.ex │ │ │ ├── encode.ex │ │ │ └── utils.ex │ ├── mix.exs │ └── test │ │ ├── ex_rlp │ │ ├── decode_test.exs │ │ ├── encode_test.exs │ │ └── protocols_test.exs │ │ ├── ex_rlp_test.exs │ │ ├── support │ │ ├── log_entry.ex │ │ └── test_utils.ex │ │ └── test_helper.exs ├── ex_wire │ ├── CHANGELOG.md │ ├── README.md │ ├── chains │ │ └── ropsten.json │ ├── config │ │ ├── config.exs │ │ ├── dev.exs │ │ ├── prod.exs │ │ └── test.exs │ ├── lib │ │ ├── ex_wire.ex │ │ └── ex_wire │ │ │ ├── adapter │ │ │ ├── tcp.ex │ │ │ └── udp.ex │ │ │ ├── config.ex │ │ │ ├── crypto.ex │ │ │ ├── discovery.ex │ │ │ ├── exth.ex │ │ │ ├── framing │ │ │ ├── frame.ex │ │ │ └── secrets.ex │ │ │ ├── handler.ex │ │ │ ├── handler │ │ │ ├── find_neighbours.ex │ │ │ ├── neighbours.ex │ │ │ ├── ping.ex │ │ │ └── pong.ex │ │ │ ├── handshake │ │ │ ├── eip_8.ex │ │ │ ├── handshake.ex │ │ │ ├── message_handler.ex │ │ │ └── struct │ │ │ │ ├── ack_resp_v4.ex │ │ │ │ └── auth_msg_v4.ex │ │ │ ├── message.ex │ │ │ ├── message │ │ │ ├── find_neighbours.ex │ │ │ ├── neighbours.ex │ │ │ ├── ping.ex │ │ │ └── pong.ex │ │ │ ├── network.ex │ │ │ ├── packet.ex │ │ │ ├── packet │ │ │ ├── block_bodies.ex │ │ │ ├── block_headers.ex │ │ │ ├── disconnect.ex │ │ │ ├── get_block_bodies.ex │ │ │ ├── get_block_headers.ex │ │ │ ├── hello.ex │ │ │ ├── new_block.ex │ │ │ ├── new_block_hashes.ex │ │ │ ├── ping.ex │ │ │ ├── pong.ex │ │ │ ├── status.ex │ │ │ └── transactions.ex │ │ │ ├── peer_supervisor.ex │ │ │ ├── protocol.ex │ │ │ ├── struct │ │ │ ├── block.ex │ │ │ ├── block_queue.ex │ │ │ ├── endpoint.ex │ │ │ ├── neighbour.ex │ │ │ └── peer.ex │ │ │ ├── sync.ex │ │ │ └── util │ │ │ └── timestamp.ex │ ├── mix.exs │ └── test │ │ ├── ex_wire │ │ ├── adapters │ │ │ └── udp_test.exs │ │ ├── config_test.exs │ │ ├── crypto_test.exs │ │ ├── framing │ │ │ ├── frame_test.exs │ │ │ └── secrets_test.exs │ │ ├── handler │ │ │ ├── find_neighbours_test.exs │ │ │ └── ping_test.exs │ │ ├── handler_test.exs │ │ ├── handshake │ │ │ ├── eip_8_test.exs │ │ │ ├── handshake_test.exs │ │ │ └── struct │ │ │ │ ├── ack_resp_v4_test.exs │ │ │ │ └── auth_msg_v4_test.exs │ │ ├── message │ │ │ ├── find_neighbours_test.exs │ │ │ ├── neighbours_test.exs │ │ │ ├── ping_test.exs │ │ │ └── pong_test.exs │ │ ├── message_test.exs │ │ ├── network_test.exs │ │ ├── packet │ │ │ ├── block_bodies_test.exs │ │ │ ├── block_headers_test.exs │ │ │ ├── disconnect_test.exs │ │ │ ├── get_block_bodies_test.exs │ │ │ ├── get_block_headers_test.exs │ │ │ ├── hello_test.exs │ │ │ ├── new_block_hashes_test.exs │ │ │ ├── new_block_test.exs │ │ │ ├── ping_test.exs │ │ │ ├── pong_test.exs │ │ │ ├── status_test.exs │ │ │ └── transactions_test.exs │ │ ├── packet_test.exs │ │ ├── peer_supervisor_test.exs │ │ ├── protocol_test.exs │ │ ├── struct │ │ │ ├── block_queue_test.exs │ │ │ ├── block_test.exs │ │ │ ├── endpoint_test.exs │ │ │ ├── neighbour_test.exs │ │ │ └── peer_test.exs │ │ ├── sync_test.exs │ │ └── util │ │ │ └── timestamp_test.exs │ │ ├── ex_wire_test.exs │ │ ├── integration │ │ ├── remote_connection_test.exs │ │ └── wire_to_wire_test.exs │ │ ├── support │ │ └── ex_wire │ │ │ └── adapter │ │ │ └── test.ex │ │ └── test_helper.exs ├── exth_crypto │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ ├── aes │ │ │ └── aes.ex │ │ ├── cipher.ex │ │ ├── ecies │ │ │ ├── ecdh.ex │ │ │ ├── ecies.ex │ │ │ └── parameters.ex │ │ ├── exth_crypto.ex │ │ ├── hash │ │ │ ├── fake.ex │ │ │ ├── hash.ex │ │ │ ├── keccak.ex │ │ │ └── sha.ex │ │ ├── kdf │ │ │ └── nist_sp_800_56.ex │ │ ├── key │ │ │ └── key.ex │ │ ├── mac │ │ │ └── mac.ex │ │ ├── math │ │ │ └── math.ex │ │ ├── signature │ │ │ └── signature.ex │ │ └── test.ex │ ├── mix.exs │ └── test │ │ ├── aes │ │ └── aes_test.exs │ │ ├── cipher_test.exs │ │ ├── ecies │ │ ├── ecdh_test.exs │ │ ├── ecies_test.exs │ │ └── parameters_test.exs │ │ ├── exth_crypto_test.exs │ │ ├── hash │ │ ├── fake_test.exs │ │ ├── keccak_test.exs │ │ └── sha_test.exs │ │ ├── hash_test.exs │ │ ├── kdf │ │ └── nist_sp_800_65_test.exs │ │ ├── mac │ │ └── mac_test.exs │ │ ├── math │ │ └── math_test.exs │ │ ├── signature │ │ └── signature_test.exs │ │ └── test_helper.exs ├── hex_prefix │ ├── README.md │ ├── config │ │ └── config.exs │ ├── lib │ │ └── hex_prefix.ex │ ├── mix.exs │ └── test │ │ ├── hex_prefix_test.exs │ │ └── test_helper.exs └── merkle_patricia_tree │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ └── config.exs │ ├── lib │ ├── db.ex │ ├── db │ │ ├── ets.ex │ │ └── leveldb.ex │ ├── list_helper.ex │ ├── merkle_patricia_tree.ex │ ├── test.ex │ ├── trie.ex │ └── trie │ │ ├── builder.ex │ │ ├── destroyer.ex │ │ ├── helper.ex │ │ ├── inspector.ex │ │ ├── node.ex │ │ ├── storage.ex │ │ └── verifier.ex │ ├── mix.exs │ └── test │ ├── list_helper_test.exs │ ├── merkle_patricia_tree │ ├── db │ │ ├── ets_test.exs │ │ └── leveldb_test.exs │ ├── db_test.exs │ ├── merkle_patricia_tree_test.exs │ ├── test_test.exs │ ├── trie │ │ ├── helper_test.exs │ │ ├── inspector_test.exs │ │ ├── node_test.exs │ │ └── storage_test.exs │ └── trie_test.exs │ └── test_helper.exs ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── coveralls.json ├── mix.exs ├── mix.lock └── rel ├── config.exs └── plugins └── .gitignore /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | inputs: [ 3 | "mix.exs", 4 | "{config,lib,test}/**/*.{ex,exs}", 5 | "apps/*/{config,lib,test}/**/*.{ex,exs}", 6 | "apps/*/mix.exs" 7 | ] 8 | ] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover 6 | #coveralls 7 | apps/*/cover 8 | 9 | # The directory Mix downloads your dependencies sources to. 10 | /deps 11 | 12 | # Where 3rd-party dependencies like ExDoc output generated docs. 13 | /doc 14 | 15 | # Ignore .fetch files in case you like to edit your project deps locally. 16 | /.fetch 17 | 18 | # If the VM crashes, it generates a dump, let's ignore it too. 19 | erl_crash.dump 20 | 21 | # Also ignore archive artifacts (built via "mix archive.build"). 22 | *.ez 23 | 24 | # MacOS Files 25 | *.DS_Store 26 | 27 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/support/ethereum_common_tests"] 2 | path = test/support/ethereum_common_tests 3 | url = git@github.com:ethereum/tests.git 4 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 21.1 2 | elixir 1.7-otp-21 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.0 2 | * Initial version of umbrella app -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018 Geoffrey Hayes, Ayrat Badykov, Mason Fischer 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | This repository will not be maintained in favour of [Mana](https://github.com/mana-ethereum/mana). 4 | 5 | # Exthereum 6 | 7 | Exthereum is an Elixir client for the Ethereum blockchain. 8 | 9 | ## Installation 10 | 11 | First, add Ethereum to your `mix.exs` dependencies: 12 | 13 | ```elixir 14 | def deps do 15 | [{:ethereum, "~> 0.2.0"}] 16 | end 17 | ``` 18 | 19 | Then, update your dependencies: 20 | 21 | ```sh-session 22 | $ mix deps.get 23 | ``` 24 | 25 | ## Usage 26 | 27 | Currently, Exthereum is a set of libraries. In time, this section will include how to run and sync the chain. 28 | 29 | ## Architecture 30 | 31 | This app is devided into different sub-apps, described here: 32 | 33 | * `apps/abi` - The ABI encoding library (for interaction with Solidity) 34 | * `apps/blockchain` - Validates and connects blocks into a chain 35 | * `apps/evm` - Runs the Ethereum VM (EVM1) 36 | * `apps/ex_rlp` - Recrusive-length encoding format used in Ethereum 37 | * `apps/ex_wire` - The DevP2P protocol 38 | * `apps/exth_crypto` - Wrappers for ethereum-specific cryptographic protocols 39 | * `apps/hex_prefix` - Encoding format used in Ethereum 40 | * `apps/merkle_patricia_tree` - A tree to canonically store data returning a state root 41 | 42 | ## License 43 | 44 | Exthereum is released under the MIT license. 45 | 46 | ## Contributing 47 | 48 | Create a pull request or come visit us in [Gitter](https://gitter.im/exthereum/exthereum). -------------------------------------------------------------------------------- /apps/abi/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore generated code from yecc and leex 2 | src/*_lexer.erl 3 | src/*_parser.erl -------------------------------------------------------------------------------- /apps/abi/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.12 2 | * Fix `string` decoding to truncate on encountering NUL 3 | * Fix some edge-cases in `tuple` encoding/decoding 4 | # 0.1.11 5 | * Add support for method ID calculation of all standard types 6 | # 0.1.10 7 | * Fix parsing of function names containing uppercase letters/digits/underscores 8 | * Add support for `bytes` 9 | # 0.1.9 10 | * Add support for parsing ABI specification documents (`.abi.json` files) 11 | * Reimplement function signature parsing using a BNF grammar 12 | * Fix potential stack overflow during encoding/decoding 13 | # 0.1.8 14 | * Fix ordering of elements in tuples 15 | # 0.1.7 16 | * Fix support for arrays of uint types 17 | # 0.1.6 18 | * Add public interface to raw function versions. 19 | # 0.1.5 20 | * Bugfix so that addresses are still left padded. 21 | # 0.1.4 22 | * Bugfix for tuples to properly handle tail pointer poisition. 23 | # 0.1.3 24 | * Bugfix for tuples to properly handle head/tail encoding 25 | # 0.1.2 26 | * Add support for tuples, fixed-length and variable length arrays 27 | -------------------------------------------------------------------------------- /apps/abi/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :abi, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:abi, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /apps/abi/lib/abi/parser.ex: -------------------------------------------------------------------------------- 1 | defmodule ABI.Parser do 2 | @moduledoc false 3 | 4 | @doc false 5 | def parse!(str, opts \\ []) do 6 | {:ok, tokens0, _} = str |> String.to_charlist() |> :ethereum_abi_lexer.string() 7 | 8 | tokens = 9 | case opts[:as] do 10 | nil -> tokens0 11 | :type -> [{:"expecting type", 1} | tokens0] 12 | :selector -> [{:"expecting selector", 1} | tokens0] 13 | end 14 | 15 | {:ok, ast} = :ethereum_abi_parser.parse(tokens) 16 | 17 | case ast do 18 | {:type, type} -> type 19 | {:selector, selector_parts} -> struct!(ABI.FunctionSelector, selector_parts) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /apps/abi/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ABI.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :abi, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | description: "Ethereum's ABI Interface", 14 | elixirc_options: [warnings_as_errors: true], 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Mason Fischer"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | preferred_cli_env: [ 23 | coveralls: :test, 24 | "coveralls.detail": :test, 25 | "coveralls.post": :test, 26 | "coveralls.html": :test, 27 | dialyzer: :test 28 | ], 29 | start_permanent: Mix.env() == :prod, 30 | test_coverage: [tool: ExCoveralls] 31 | ] 32 | end 33 | 34 | # Run "mix help compile.app" to learn about applications. 35 | def application do 36 | [ 37 | extra_applications: [:logger] 38 | ] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Umbrella 45 | {:exth_crypto, in_umbrella: true}, 46 | 47 | # Libraries 48 | {:poison, "~> 4.0.1", only: [:dev, :test]}, 49 | 50 | # Common 51 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 52 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 53 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 54 | ] 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /apps/abi/priv/dog.abi.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [ 5 | { 6 | "name": "at", 7 | "type": "address" 8 | }, 9 | { 10 | "name": "loudly", 11 | "type": "bool" 12 | } 13 | ], 14 | "name": "bark", 15 | "outputs": [], 16 | "payable": false, 17 | "stateMutability": "nonpayable", 18 | "type": "function" 19 | }, 20 | 21 | { 22 | "constant": true, 23 | "inputs": [], 24 | "name": "rollover", 25 | "outputs": [ 26 | { 27 | "name": "is_a_good_boy", 28 | "type": "bool" 29 | } 30 | ], 31 | "payable": false, 32 | "stateMutability": "nonpayable", 33 | "type": "function" 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /apps/abi/src/ethereum_abi_lexer.xrl: -------------------------------------------------------------------------------- 1 | Definitions. 2 | 3 | INT = [0-9]+ 4 | LETTERS = [a-zA-Z_]+ 5 | WHITESPACE = [\s\t\n\r] 6 | TYPES = uint|int|address|bool|fixed|uint|ufixed|bytes|function|string 7 | 8 | Rules. 9 | 10 | {TYPES} : {token, {typename, TokenLine, TokenChars}}. 11 | {INT} : {token, {digits, TokenLine, TokenChars}}. 12 | {LETTERS} : {token, {letters, TokenLine, TokenChars}}. 13 | \[ : {token, {'[', TokenLine}}. 14 | \] : {token, {']', TokenLine}}. 15 | \( : {token, {'(', TokenLine}}. 16 | \) : {token, {')', TokenLine}}. 17 | , : {token, {',', TokenLine}}. 18 | -> : {token, {'->', TokenLine}}. 19 | {WHITESPACE}+ : skip_token. 20 | 21 | Erlang code. 22 | -------------------------------------------------------------------------------- /apps/abi/test/abi/function_selector_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ABI.FunctionSelectorTest do 2 | use ExUnit.Case, async: true 3 | doctest ABI.FunctionSelector 4 | end 5 | -------------------------------------------------------------------------------- /apps/abi/test/abi/type_decoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ABI.TypeDecoderTest do 2 | use ExUnit.Case, async: true 3 | doctest ABI.TypeDecoder 4 | end 5 | -------------------------------------------------------------------------------- /apps/abi/test/abi/type_encoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ABI.TypeEncoderTest do 2 | use ExUnit.Case, async: true 3 | doctest ABI.TypeEncoder 4 | end 5 | -------------------------------------------------------------------------------- /apps/abi/test/abi_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ABITest do 2 | use ExUnit.Case 3 | doctest ABI 4 | end 5 | -------------------------------------------------------------------------------- /apps/abi/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/blockchain/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.6 2 | * Continue to pass more state common tests (CALLCODE) 3 | * Add core Bloom Filter implementation 4 | # 0.1.5 5 | * Integrate with EVM debugger 6 | * Update `verify_and_add_block` command's interface 7 | # 0.1.4 8 | * Pass common tests for block state and transactions. 9 | # 0.1.3 10 | * Add basic support for chains, such as Ropsten versus Homestead. 11 | * Upgrade EVM to and move `Block.Header` to the EVM project. 12 | * Add interfaces for interacting with accounts, contracts or blocks in the EVM 13 | # 0.1.2 14 | * Upgrade KECCAK version to match Ethereum spec 15 | # 0.1.1 16 | * Fixes all typespec issues 17 | # 0.1.0 18 | * Initial commit of basic blockchain functionality 19 | -------------------------------------------------------------------------------- /apps/blockchain/README.md: -------------------------------------------------------------------------------- 1 | # Exthereum Blockchain [![CircleCI](https://circleci.com/gh/exthereum/blockchain.svg?style=svg)](https://circleci.com/gh/exthereum/blockchain) 2 | 3 | Elixir implementation of Ethereum's Blockchain. This includes functionality to build and verify a chain of Ethereum blocks that may be advertised from any peer. We complete the resultant state of the blocktree and form a canonical blockchain based on difficulty. 4 | 5 | Exthereum's blocks are specified in a variety of sections throughout [the yellow paper](http://yellowpaper.io/), but it's best to start looking under Section 4.4. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | mix deps.get 11 | mix compile 12 | ``` 13 | 14 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 15 | by adding `blockchain` to your list of dependencies in `mix.exs`: 16 | 17 | ```elixir 18 | def deps do 19 | [{:blockchain, "~> 0.1.6"}] 20 | end 21 | ``` 22 | 23 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 24 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 25 | be found at [https://hexdocs.pm/blockchain](https://hexdocs.pm/blockchain). 26 | 27 | ## Debugging 28 | 29 | To debug a given run of the blockchain, you can set breakpoints on contract addresses by setting the `BREAKPOINT` environment variable and specifying a contract address to break on. E.g. 30 | 31 | ```bash 32 | BREAKPOINT=bc1ffc1620da1468624a596cb841d35e6b2f1fb6 iex -S mix 33 | 34 | ... 35 | 36 | 00:04:18.739 [warn] Debugger has been enabled. Set breakpoint #1 on contract address 0xbc1ffc1620da1468624a596cb841d35e6b2f1fb6. 37 | 38 | ... 39 | 40 | -- Breakpoint #1 triggered with conditions contract address 0xbc1ffc1620da1468624a596cb841d35e6b2f1fb6 (start) -- 41 | 42 | gas: 277888 | pc: 0 | memory: 0 | words: 0 | # stack: 0 43 | 44 | ----> [ 0] push2 45 | [ 1] 0 46 | [ 2] 4 47 | [ 3] dup1 48 | [ 4] push2 49 | [ 5] 0 50 | [ 6] 14 51 | [ 7] push1 52 | [ 8] 0 53 | [ 9] codecopy 54 | 55 | Enter a debug command or type `h` for help. 56 | 57 | >> 58 | ``` 59 | 60 | ## Contributing 61 | 62 | 1. [Fork it!](https://github.com/exthereum/blockchain/fork) 63 | 2. Create your feature branch (`git checkout -b my-new-feature`) 64 | 3. Commit your changes (`git commit -am 'Add some feature'`) 65 | 4. Push to the branch (`git push origin my-new-feature`) 66 | 5. Create new Pull Request 67 | 68 | ## Author 69 | 70 | Geoffrey Hayes (@hayesgm) 71 | Ayrat Badykov (@ayrat555) 72 | Mason Fischer (@masonforest) 73 | 74 | ## License 75 | 76 | Exthereum's Blockchain is released under the MIT License. See the LICENSE file for further details. 77 | -------------------------------------------------------------------------------- /apps/blockchain/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :blockchain, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:blockchain, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /apps/blockchain/lib/blockchain.ex: -------------------------------------------------------------------------------- 1 | defmodule Blockchain do 2 | @moduledoc """ 3 | Documentation for Blockchain. 4 | """ 5 | end 6 | -------------------------------------------------------------------------------- /apps/blockchain/lib/blockchain/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Application do 2 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | alias EVM.Debugger 8 | require Logger 9 | 10 | def start(_type, _args) do 11 | import Supervisor.Spec, warn: false 12 | 13 | if breakpoint_address_hex = System.get_env("BREAKPOINT") do 14 | case Base.decode16(breakpoint_address_hex, case: :mixed) do 15 | {:ok, breakpoint_address} -> 16 | Debugger.enable() 17 | id = Debugger.break_on(address: breakpoint_address) 18 | 19 | Logger.warn( 20 | "Debugger has been enabled. Set breakpoint ##{id} on contract address 0x#{ 21 | breakpoint_address_hex 22 | }." 23 | ) 24 | 25 | :error -> 26 | Logger.error("Invalid breakpoint address: #{breakpoint_address_hex}") 27 | end 28 | end 29 | 30 | # Define workers and child supervisors to be supervised 31 | children = [ 32 | # Starts a worker by calling: Blockchain.Worker.start_link(arg1, arg2, arg3) 33 | # worker(Blockchain.Worker, [arg1, arg2, arg3]), 34 | ] 35 | 36 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 37 | # for other strategies and supported options 38 | opts = [strategy: :one_for_one, name: Blockchain.Supervisor] 39 | Supervisor.start_link(children, opts) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /apps/blockchain/lib/blockchain/bloom.ex: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Bloom do 2 | @moduledoc """ 3 | Utility reducing the content of blockchain domain objects, such as logs into bloom filter hashes. 4 | """ 5 | 6 | use Bitwise 7 | 8 | @spec create(binary()) :: integer() 9 | def create(data) when is_binary(data) do 10 | bloom(0, data) 11 | end 12 | 13 | @spec add(integer(), binary()) :: integer() 14 | def add(bloom_number, data) when is_binary(data) do 15 | bloom(bloom_number, data) 16 | end 17 | 18 | @spec contains?(integer(), binary()) :: boolean() 19 | def contains?(current_bloom, val) 20 | when is_integer(current_bloom) and is_binary(val) do 21 | bloom = create(val) 22 | 23 | (bloom &&& current_bloom) == bloom 24 | end 25 | 26 | @spec bloom(integer(), binary()) :: integer() 27 | defp bloom(number, data) do 28 | bits = 29 | data 30 | |> sha3_hash 31 | |> bit_numbers 32 | 33 | number |> add_bits(bits) 34 | end 35 | 36 | @spec sha3_hash(binary()) :: binary() 37 | defp sha3_hash(data) do 38 | data |> :keccakf1600.sha3_256() 39 | end 40 | 41 | @spec add_bits(integer(), [integer()]) :: integer() 42 | defp add_bits(bloom_number, bits) do 43 | bits 44 | |> Enum.reduce(bloom_number, fn bit_number, bloom -> 45 | bloom ||| 1 <<< bit_number 46 | end) 47 | end 48 | 49 | @spec bit_numbers(binary()) :: [integer()] 50 | defp bit_numbers(hash) do 51 | {result, _} = 52 | 1..3 53 | |> Enum.reduce({[], hash}, fn _, acc -> 54 | {bits, <>} = acc 55 | new_bit = head &&& 2047 56 | 57 | {[new_bit | bits], tail} 58 | end) 59 | 60 | result 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /apps/blockchain/lib/blockchain/transaction/receipt.ex: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Transaction.Receipt do 2 | @moduledoc """ 3 | This module specifies functions to create and 4 | interact with the transaction receipt, defined 5 | in Section 4.4.1 of the Yellow Paper. 6 | 7 | Transaction receipts track incremental state changes 8 | after each transaction (e.g. how much gas has been 9 | expended). 10 | """ 11 | 12 | # Defined in Eq.(19) 13 | defstruct state: <<>>, 14 | cumulative_gas: 0, 15 | bloom_filter: <<>>, 16 | logs: <<>> 17 | 18 | # Types defined in Eq.(20) 19 | @type t :: %__MODULE__{ 20 | state: EVM.trie_root(), 21 | # Defined in Eq.(21) 22 | cumulative_gas: EVM.Gas.t(), 23 | # TODO: Bloom filter 24 | bloom_filter: binary(), 25 | logs: EVM.SubState.logs() 26 | } 27 | 28 | @doc """ 29 | Encodes a transaction receipt such that it can be 30 | RLP encoded. This is defined in Eq.(20) of the Yellow 31 | Paper. 32 | 33 | ## Examples 34 | 35 | iex> Blockchain.Transaction.Receipt.serialize(%Blockchain.Transaction.Receipt{}) 36 | [<<>>, 0, <<>>, <<>>] 37 | 38 | iex> Blockchain.Transaction.Receipt.serialize(%Blockchain.Transaction.Receipt{state: <<1,2,3>>, cumulative_gas: 5, bloom_filter: <<2,3,4>>, logs: "hi mom"}) 39 | [<<1,2,3>>, 5, <<2,3,4>>, "hi mom"] 40 | """ 41 | @spec serialize(t) :: ExRLP.t() 42 | def serialize(trx_receipt) do 43 | [ 44 | trx_receipt.state, 45 | trx_receipt.cumulative_gas, 46 | trx_receipt.bloom_filter, 47 | trx_receipt.logs 48 | ] 49 | end 50 | 51 | @doc """ 52 | Decodes a transaction receipt based on the serialization format 53 | defined in Eq.(20). This is the inverse of `serialize/1`. 54 | 55 | ## Examples 56 | 57 | iex> Blockchain.Transaction.Receipt.deserialize([<<1,2,3>>, <<5>>, <<2,3,4>>, "hi mom"]) 58 | %Blockchain.Transaction.Receipt{state: <<1,2,3>>, cumulative_gas: 5, bloom_filter: <<2,3,4>>, logs: "hi mom"} 59 | 60 | iex> Blockchain.Transaction.Receipt.deserialize([<<>>, <<0>>, <<>>, <<>>]) 61 | %Blockchain.Transaction.Receipt{} 62 | """ 63 | @spec deserialize(ExRLP.t()) :: t 64 | def deserialize(rlp) do 65 | [ 66 | state, 67 | cumulative_gas, 68 | bloom_filter, 69 | logs 70 | ] = rlp 71 | 72 | %__MODULE__{ 73 | state: state, 74 | cumulative_gas: :binary.decode_unsigned(cumulative_gas), 75 | bloom_filter: bloom_filter, 76 | logs: logs 77 | } 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /apps/blockchain/lib/eth_common_test/harness.ex: -------------------------------------------------------------------------------- 1 | defmodule EthCommonTest.Harness do 2 | @moduledoc """ 3 | Harness for running tests off of the Ethereum Common Test suite. 4 | """ 5 | alias EthCommonTest.Helpers 6 | 7 | defmacro __using__(_opts) do 8 | quote do 9 | import EthCommonTest.Helpers 10 | import EthCommonTest.Harness, only: [eth_test: 4] 11 | end 12 | end 13 | 14 | defmacro eth_test(test_set, test_subset_or_subsets, tests, fun) do 15 | test_subsets = 16 | case test_subset_or_subsets do 17 | test_subsets when is_list(test_subsets) -> test_subsets 18 | test_subset -> [test_subset] 19 | end 20 | 21 | for test_subset <- test_subsets do 22 | for {test_name, test} <- Helpers.read_test_file(test_set, test_subset), 23 | tests == :all or Enum.member?(tests, String.to_atom(test_name)) do 24 | json = Poison.encode!(test) 25 | 26 | quote do 27 | test( 28 | "#{unquote(test_set)} - #{unquote(test_subset)} - #{unquote(test_name)}", 29 | test_params 30 | ) do 31 | test = unquote(json) |> Poison.decode!() 32 | unquote(fun).(test, unquote(test_subset), unquote(test_name), test_params) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /apps/blockchain/lib/eth_common_test/helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule EthCommonTest.Helpers do 2 | @moduledoc """ 3 | Helper functions that will be generally available to test cases 4 | when they use `EthCommonTest`. 5 | """ 6 | 7 | require Integer 8 | 9 | @spec load_integer(String.t()) :: integer() | nil 10 | def load_integer(""), do: 0 11 | def load_integer("x" <> data), do: maybe_hex(data, :integer) 12 | def load_integer("0x" <> data), do: maybe_hex(data, :integer) 13 | def load_integer(data), do: maybe_dec(data) 14 | 15 | @spec maybe_hex(String.t() | nil) :: binary() | nil 16 | def maybe_address(hex_data), do: maybe_hex(hex_data) 17 | 18 | @spec maybe_hex(String.t() | nil) :: binary() | nil 19 | def maybe_hex(hex_data, type \\ :raw) 20 | def maybe_hex(nil, _), do: nil 21 | def maybe_hex(hex_data, :raw), do: load_raw_hex(hex_data) 22 | def maybe_hex(hex_data, :integer), do: load_hex(hex_data) 23 | 24 | @spec maybe_dec(String.t() | nil) :: integer() | nil 25 | def maybe_dec(nil), do: nil 26 | def maybe_dec(els), do: load_decimal(els) 27 | 28 | @spec load_decimal(String.t()) :: integer() 29 | def load_decimal(dec_data) do 30 | {res, ""} = Integer.parse(dec_data) 31 | 32 | res 33 | end 34 | 35 | @spec load_raw_hex(String.t()) :: binary() 36 | def load_raw_hex("0x" <> hex_data), do: load_raw_hex(hex_data) 37 | 38 | def load_raw_hex(hex_data) when Integer.is_odd(byte_size(hex_data)), 39 | do: load_raw_hex("0" <> hex_data) 40 | 41 | def load_raw_hex(hex_data) do 42 | Base.decode16!(hex_data, case: :mixed) 43 | end 44 | 45 | @spec load_hex(String.t()) :: non_neg_integer() 46 | def load_hex(hex_data), do: hex_data |> load_raw_hex |> :binary.decode_unsigned() 47 | 48 | @spec read_test_file(atom(), atom()) :: any() 49 | def read_test_file(test_set, test_subset) do 50 | # This is pretty terrible, but the JSON is just messed up in a number 51 | # of these tests (it contains duplicate keys with very strange values) 52 | body = 53 | File.read!(test_file_name(test_set, test_subset)) 54 | |> String.split("\n") 55 | |> Enum.filter(fn x -> not (x |> String.contains?("secretkey ")) end) 56 | |> Enum.join("\n") 57 | 58 | Poison.decode!(body) 59 | end 60 | 61 | @spec test_file_name(atom(), atom()) :: String.t() 62 | def test_file_name(test_set, test_subset) do 63 | "../../test/support/ethereum_common_tests/#{test_set}/#{to_string(test_subset)}.json" 64 | end 65 | 66 | @spec load_src(String.t(), String.t()) :: any() 67 | def load_src(filler_type, filler) do 68 | read_test_file("src/#{filler_type}", filler) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /apps/blockchain/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :blockchain, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | description: "Ethereum's Blockchain Manager", 14 | elixirc_options: [warnings_as_errors: true], 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Ayrat Badykov", "Mason Forest"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | preferred_cli_env: [ 23 | coveralls: :test, 24 | "coveralls.detail": :test, 25 | "coveralls.post": :test, 26 | "coveralls.html": :test, 27 | dialyzer: :test 28 | ], 29 | start_permanent: Mix.env() == :prod, 30 | test_coverage: [tool: ExCoveralls] 31 | ] 32 | end 33 | 34 | # Configuration for the OTP application 35 | # 36 | # Type "mix help compile.app" for more information 37 | def application do 38 | # Specify extra applications you'll use from Erlang/Elixir 39 | [extra_applications: [:logger], mod: {Blockchain.Application, []}] 40 | end 41 | 42 | # Run "mix help deps" to learn about dependencies. 43 | defp deps do 44 | [ 45 | # Umbrella 46 | {:evm, in_umbrella: true}, 47 | {:ex_rlp, in_umbrella: true}, 48 | {:merkle_patricia_tree, in_umbrella: true}, 49 | 50 | # Libaries 51 | {:keccakf1600, "~> 2.0.0", hex: :keccakf1600_orig}, 52 | {:libsecp256k1, "~> 0.1.9"}, 53 | {:poison, "~> 4.0.1"}, 54 | 55 | # Common 56 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 57 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 58 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 59 | ] 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /apps/blockchain/test/bit_helper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BitHelperTest do 2 | use ExUnit.Case, async: true 3 | doctest BitHelper 4 | end 5 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/blocktree_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.BlocktreeTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Blocktree 4 | alias Blockchain.Blocktree 5 | alias EVM.Block.Header 6 | 7 | test "multi-level tree" do 8 | block_10 = %Blockchain.Block{ 9 | block_hash: <<10>>, 10 | header: %Header{number: 0, parent_hash: <<0::256>>, difficulty: 100} 11 | } 12 | 13 | block_20 = %Blockchain.Block{ 14 | block_hash: <<20>>, 15 | header: %Header{number: 1, parent_hash: <<10>>, difficulty: 110} 16 | } 17 | 18 | block_21 = %Blockchain.Block{ 19 | block_hash: <<21>>, 20 | header: %Header{number: 1, parent_hash: <<10>>, difficulty: 120} 21 | } 22 | 23 | block_30 = %Blockchain.Block{ 24 | block_hash: <<30>>, 25 | header: %Header{number: 2, parent_hash: <<20>>, difficulty: 120} 26 | } 27 | 28 | block_40 = %Blockchain.Block{ 29 | block_hash: <<40>>, 30 | header: %Header{number: 3, parent_hash: <<30>>, difficulty: 120} 31 | } 32 | 33 | tree = 34 | Blocktree.new_tree() 35 | |> Blocktree.add_block(block_10) 36 | |> Blocktree.add_block(block_20) 37 | |> Blocktree.add_block(block_21) 38 | |> Blocktree.add_block(block_30) 39 | |> Blocktree.add_block(block_40) 40 | 41 | assert Blocktree.inspect_tree(tree) == 42 | [ 43 | :root, 44 | [ 45 | {0, <<10>>}, 46 | [ 47 | {1, <<20>>}, 48 | [ 49 | {2, <<30>>}, 50 | [{3, <<40>>}] 51 | ] 52 | ], 53 | [ 54 | {1, <<21>>} 55 | ] 56 | ] 57 | ] 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/bloom_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.BloomTest do 2 | use ExUnit.Case 3 | alias Blockchain.Bloom 4 | 5 | @default_filter 364_236_115_780_354_413_527_177_740_824_718_248_475_191_169_123_433_179_704_674_083_584_030_171_707_548_895_141_763_338_864_017_157_468_044_931_245_187_476_551_639_917_006_481_686_358_559_826_056_819_612_140_499_456_696_964_058_447_681_377_941_080_842_953_023_782_344_796_365_486_712_958_202_642_967_905_678_562_795_813_182_653_349_819_408_730_761_190_197_772_532_753_708_011_416_878_102_770_310_284_058_867_224_260_870_159_690_230_717_337_345_574_083_724_951_534_664_321_152_788_810_106_636_281_468_311_475_567_122_070_065_682_554_961_594_780_791_945_980_493_299_780_947_753_165_983_844_578_885_074_727_735_505_366_654_109_046_841_451_940_478_976 6 | 7 | describe "EthBloom.create/1" do 8 | test "creates bloom filter" do 9 | filter = Bloom.create("rock") 10 | 11 | assert filter == @default_filter 12 | end 13 | end 14 | 15 | describe "EthBloom.add/2" do 16 | test "adds element to bloom filter" do 17 | new_filter = Bloom.add(@default_filter, "punk") 18 | 19 | assert Bloom.contains?(new_filter, "punk") 20 | assert Bloom.contains?(new_filter, "rock") 21 | refute Bloom.contains?(new_filter, "blues") 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/chain_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.ChainTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Chain 4 | alias Blockchain.Chain 5 | 6 | test "loads ropsten" do 7 | assert Chain.load_chain(:ropsten) == Blockchain.Test.ropsten_chain() 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/contract_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.ContractTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Contract 4 | end 5 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/interface/account_interface_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Interface.AccountInterfaceTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Interface.AccountInterface 4 | doctest EVM.Interface.AccountInterface.Blockchain.Interface.AccountInterface 5 | end 6 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/interface/block_interface_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Interface.BlockInterfaceTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Interface.BlockInterface 4 | doctest EVM.Interface.BlockInterface.Blockchain.Interface.BlockInterface 5 | end 6 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/transaction/receipt_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Transaction.ReceiptTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Transaction.Receipt 4 | alias Blockchain.Transaction.Receipt 5 | 6 | test "serilalize and deserialize" do 7 | receipt = %Receipt{ 8 | state: <<1, 2, 3>>, 9 | cumulative_gas: 5, 10 | bloom_filter: <<2, 3, 4>>, 11 | logs: "hi mom" 12 | } 13 | 14 | assert receipt == 15 | receipt 16 | |> Receipt.serialize() 17 | |> ExRLP.encode() 18 | |> ExRLP.decode() 19 | |> Receipt.deserialize() 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain/transaction/signature_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Blockchain.Transaction.SignatureTest do 2 | use ExUnit.Case, async: true 3 | doctest Blockchain.Transaction.Signature 4 | end 5 | -------------------------------------------------------------------------------- /apps/blockchain/test/blockchain_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainTest do 2 | use ExUnit.Case 3 | doctest Blockchain 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /apps/blockchain/test/ropsten_test.exs: -------------------------------------------------------------------------------- 1 | defmodule RopstenTest do 2 | @moduledoc """ 3 | This test case has the first n blocks of Ropsten, which we 4 | will verify and add to a block tree. As we process in our 5 | progress with Exthereum, we will be able to load and verify 6 | more of the Ropsten chain. 7 | """ 8 | 9 | use ExUnit.Case, async: true 10 | alias MerklePatriciaTree.Test 11 | alias Blockchain.Blocktree 12 | alias Blockchain.Test, as: BlockchainTest 13 | @n 11 14 | 15 | setup_all do 16 | blocks = 17 | File.read!("test/support/ropsten_blocks.dat") 18 | |> BitHelper.from_hex() 19 | |> ExRLP.decode() 20 | |> Enum.map(fn block -> 21 | block |> ExRLP.decode() |> Blockchain.Block.deserialize() 22 | end) 23 | |> Enum.take(@n) 24 | 25 | {:ok, 26 | %{ 27 | blocks: blocks 28 | }} 29 | end 30 | 31 | test "processing the first #{@n} blocks of the live ropsten block tree", %{blocks: blocks} do 32 | db = Test.random_ets_db() 33 | tree = Blocktree.new_tree() 34 | chain = BlockchainTest.ropsten_chain() 35 | 36 | Enum.reduce(blocks, tree, fn block, tree -> 37 | {:ok, new_tree} = Blocktree.verify_and_add_block(tree, chain, block, db) 38 | 39 | new_tree 40 | end) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /apps/blockchain/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/evm/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.14 2 | * Update CALL opcode to pass blockchain tests 3 | # 0.1.13 4 | * Update gas calculations for CALL and RETURN opcodes 5 | # 0.1.12 6 | * Move state management out of the EVM and use `AccountInterface` instead 7 | # 0.1.11 8 | * Add basic implementation of the CALL opcode 9 | # 0.1.10 10 | * Update interfaces to match `Blockchain` downstream 11 | * Add `Block.Header.hash/1` function 12 | # 0.1.9 13 | * Subtract gas before running operations 14 | * Move program counter logic to its own module 15 | * Refactor memory gas cost calculations 16 | # 0.1.8 17 | * Update gas cost calculations 18 | * Move process counter manipulation into the opcode implementations 19 | * Fixed `BLOCKHASH` opcode 20 | # 0.1.7 21 | * Add in a full debugger 22 | # 0.1.6 23 | * Add in parameters that could be tweaked by different chains (e.g. `min_gas_limit` in Ropsten) 24 | * Add Bitwise logic opcodes and SHA3 25 | # 0.1.5 26 | * Add `CREATE`, `CALL`, `CALLCODE` and `DELEGATECALL` op calls 27 | * Add block information opcodes 28 | # 0.1.4 29 | * Large refactor / cleanup of how opcodes are run / organized. 30 | * Added significant number of mathematic opcodes. 31 | * Added common test suite for verify result of opcodes. 32 | * Fixed gas calculation for a number of opcodes. 33 | # 0.1.3 34 | * Expand allowed trie definition from EVM. 35 | # 0.1.2 36 | * Fix typespec to allow nil return from `EVM.VM.run/3`. 37 | # 0.1.1 38 | * Fix all typespec issues and get dialyzer returning straight greens. 39 | -------------------------------------------------------------------------------- /apps/evm/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :evm, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:evm, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /apps/evm/lib/evm.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM do 2 | @moduledoc """ 3 | Documentation for EVM. 4 | """ 5 | 6 | # σ[a] 7 | @type account :: %{ 8 | # σ[a]n 9 | nonce: integer(), 10 | # σ[a]b 11 | balance: integer(), 12 | # σ[a]s 13 | storage: MerklePatriciaTree.Trie.t(), 14 | # σ[a]c 15 | code: binary() 16 | } 17 | # σ 18 | @type world_state :: %{ 19 | address() => account() 20 | } 21 | @type trie_root :: MerklePatriciaTree.Trie.root_hash() 22 | @type val :: integer() 23 | @type address :: <<_::160>> 24 | @type hash :: <<_::256>> 25 | @type timestamp :: integer() 26 | 27 | @word_size_in_bytes 4 28 | @byte_size 8 29 | @int_size 256 30 | @max_int round(:math.pow(2, @int_size)) 31 | 32 | @doc """ 33 | Returns maximum allowed integer size. 34 | """ 35 | def max_int(), do: @max_int 36 | def int_size(), do: @int_size 37 | def byte_size(), do: @byte_size 38 | 39 | @doc """ 40 | Returns word size in bits. 41 | """ 42 | def word_size(), do: @word_size_in_bytes * @byte_size 43 | end 44 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/address.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Address do 2 | @moduledoc """ 3 | EVM address functions and constants. 4 | """ 5 | alias EVM 6 | alias EVM.Helpers 7 | 8 | @size 20 9 | @max round(:math.pow(2, @size * EVM.byte_size())) 10 | 11 | @doc """ 12 | Returns the maximum allowed address size. 13 | """ 14 | @spec size() :: 20 15 | def size(), do: @size 16 | 17 | @doc """ 18 | Returns the maximum allowed address value. 19 | """ 20 | @spec max() :: integer() 21 | def max(), do: @max 22 | 23 | @doc """ 24 | Returns an address given an integer. 25 | """ 26 | 27 | @spec new(non_neg_integer()) :: binary() 28 | def new(address) when is_number(address) do 29 | address 30 | |> :binary.encode_unsigned() 31 | |> Helpers.left_pad_bytes(@size) 32 | end 33 | 34 | @doc """ 35 | Returns an address given an address and a nonce. 36 | """ 37 | @spec new(integer(), integer()) :: non_neg_integer() 38 | def new(address, nonce) do 39 | ExRLP.encode([address, nonce]) 40 | |> :keccakf1600.sha3_256() 41 | |> Helpers.take_n_last_bytes(@size) 42 | |> :binary.decode_unsigned() 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/application.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Application do 2 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | def start(_type, _args) do 9 | import Supervisor.Spec, warn: false 10 | 11 | # Define workers and child supervisors to be supervised 12 | children = [ 13 | # Starts a worker by calling: EVM.Worker.start_link(arg1, arg2, arg3) 14 | # worker(EVM.Worker, [arg1, arg2, arg3]), 15 | ] 16 | 17 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 18 | # for other strategies and supported options 19 | opts = [strategy: :one_for_one, name: EVM.Supervisor] 20 | Supervisor.start_link(children, opts) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/builtin.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Builtin do 2 | @moduledoc """ 3 | Implements the built-in functions as defined in Appendix E 4 | of the Yellow Paper. These are contract functions that 5 | natively exist in Ethereum. 6 | 7 | TODO: Implement and add doc tests. 8 | """ 9 | 10 | @spec run_ecrec(EVM.Gas.t(), EVM.ExecEnv.t()) :: 11 | {EVM.Gas.t(), EVM.SubState.t(), EVM.ExecEnv.t(), VM.output()} 12 | def run_ecrec(gas, exec_env), do: {gas, %EVM.SubState{}, exec_env, <<>>} 13 | 14 | @spec run_sha256(EVM.Gas.t(), EVM.ExecEnv.t()) :: 15 | {EVM.Gas.t(), EVM.SubState.t(), EVM.ExecEnv.t(), VM.output()} 16 | def run_sha256(gas, exec_env), do: {gas, %EVM.SubState{}, exec_env, <<>>} 17 | 18 | @spec run_rip160(EVM.Gas.t(), EVM.ExecEnv.t()) :: 19 | {EVM.Gas.t(), EVM.SubState.t(), EVM.ExecEnv.t(), VM.output()} 20 | def run_rip160(gas, exec_env), do: {gas, %EVM.SubState{}, exec_env, <<>>} 21 | 22 | @spec run_id(EVM.Gas.t(), EVM.ExecEnv.t()) :: 23 | {EVM.Gas.t(), EVM.SubState.t(), EVM.ExecEnv.t(), VM.output()} 24 | def run_id(gas, exec_env), do: {gas, %EVM.SubState{}, exec_env, <<>>} 25 | end 26 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/debugger/command.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Debugger.Command do 2 | @moduledoc """ 3 | Defines a command that can be run in the debugger. 4 | """ 5 | 6 | @type t :: %__MODULE__{ 7 | command: atom(), 8 | name: String.t(), 9 | shortcut: String.t(), 10 | description: String.t() 11 | } 12 | 13 | defstruct command: nil, 14 | name: nil, 15 | shortcut: nil, 16 | description: nil 17 | end 18 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/interface/account_interface.ex: -------------------------------------------------------------------------------- 1 | defprotocol EVM.Interface.AccountInterface do 2 | alias Blockchain.Contract 3 | 4 | @moduledoc """ 5 | Interface for interacting with accounts. 6 | """ 7 | 8 | @type t :: module() 9 | 10 | @spec account_exists?(t, EVM.address()) :: boolean() 11 | def account_exists?(t, address) 12 | 13 | @spec get_account_balance(t, EVM.address()) :: nil | EVM.Wei.t() 14 | def get_account_balance(t, address) 15 | 16 | @spec add_wei(t, EVM.address(), integer()) :: nil | EVM.Wei.t() 17 | def add_wei(t, address, value) 18 | 19 | @spec transfer(t, EVM.address(), EVM.address(), integer()) :: nil | EVM.Wei.t() 20 | def transfer(t, from, to, value) 21 | 22 | @spec get_account_code(t, EVM.address()) :: nil | binary() 23 | def get_account_code(t, address) 24 | 25 | @spec get_account_nonce(EVM.Interface.AccountInterface.t(), EVM.address()) :: nil | integer() 26 | def get_account_nonce(mock_account_interface, address) 27 | 28 | @spec increment_account_nonce(t, EVM.address()) :: t 29 | def increment_account_nonce(t, address) 30 | 31 | @spec get_storage(t, EVM.address(), integer()) :: 32 | {:ok, integer()} | :account_not_found | :key_not_found 33 | def get_storage(t, address, key) 34 | 35 | @spec put_storage(t, EVM.address(), integer(), integer()) :: t 36 | def put_storage(t, address, key, value) 37 | 38 | @spec suicide_account(t, EVM.address()) :: t 39 | def suicide_account(t, address) 40 | 41 | @spec dump_storage(t) :: %{EVM.address() => EVM.val()} 42 | def dump_storage(t) 43 | 44 | @spec message_call( 45 | t, 46 | Contract.t(), 47 | EVM.address(), 48 | EVM.address(), 49 | EVM.Wei.t(), 50 | binary() 51 | ) :: {t, EVM.Gas.t(), EVM.SubState.t(), VM.output()} 52 | def message_call( 53 | t, 54 | contract0, 55 | recipient, 56 | contract, 57 | apparent_value, 58 | data 59 | ) 60 | 61 | @spec create_contract( 62 | t, 63 | Contract.t() | map(), 64 | MachineCode.t() 65 | ) :: {t, EVM.Gas.t(), EVM.SubState.t()} 66 | def create_contract( 67 | t, 68 | contract, 69 | init_code 70 | ) 71 | 72 | @spec new_contract_address(t, EVM.address(), integer()) :: EVM.address() 73 | def new_contract_address(t, address, nonce) 74 | end 75 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/interface/block_interface.ex: -------------------------------------------------------------------------------- 1 | defprotocol EVM.Interface.BlockInterface do 2 | @moduledoc """ 3 | Interface for interacting with block headers. 4 | """ 5 | 6 | @type t :: module() 7 | 8 | @spec get_block_header(t) :: Block.Header.t() 9 | def get_block_header(t) 10 | 11 | @spec get_block_by_hash(t, EVM.hash()) :: Block.Header.t() | nil 12 | def get_block_by_hash(t, block_hash) 13 | 14 | @spec get_block_by_number(t, non_neg_integer()) :: Block.Header.t() | nil 15 | def get_block_by_number(t, steps) 16 | end 17 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/interface/mock/mock_block_interface.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Interface.Mock.MockBlockInterface do 2 | @moduledoc """ 3 | Simple implementation of a block interface that allows 4 | us to specify the given block headers. 5 | """ 6 | 7 | defstruct block_header: nil, 8 | block_map: %{} 9 | 10 | @spec new(Block.Header.t(), %{EVM.hash() => Block.Header.t()}) :: 11 | EVM.Interface.BlockInterface.t() 12 | def new(block_header, block_map \\ %{}) do 13 | %__MODULE__{ 14 | block_header: block_header, 15 | block_map: block_map 16 | } 17 | end 18 | end 19 | 20 | defimpl EVM.Interface.BlockInterface, for: EVM.Interface.Mock.MockBlockInterface do 21 | @spec get_block_header(EVM.Interface.BlockInterface.t()) :: Block.Header.t() 22 | def get_block_header(mock_block_interface) do 23 | mock_block_interface.block_header 24 | end 25 | 26 | @spec get_block_by_hash(EVM.Interface.BlockInterface.t(), EVM.hash()) :: Block.Header.t() 27 | def get_block_by_hash(mock_block_interface, block_hash) do 28 | mock_block_interface.block_map[block_hash] 29 | end 30 | 31 | @spec get_block_by_number(EVM.Interface.BlockInterface.t(), integer()) :: Block.Header.t() 32 | def get_block_by_number(mock_block_interface, number) do 33 | block = 34 | Map.values(mock_block_interface.block_map) 35 | |> Enum.find(fn block -> block.number == number end) 36 | 37 | if block do 38 | mock_block_interface.block_map[block.mix_hash] 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/duplication.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Duplication do 2 | alias EVM.Operation 3 | 4 | @doc """ 5 | Duplicate stack item n-times. 6 | 7 | ## Examples 8 | 9 | iex> EVM.Operation.Duplication.dup([1, 2, 3], %{}) 10 | [3, 1, 2, 3] 11 | 12 | iex> EVM.Operation.Duplication.dup([1, 2, 3, 4, 5], %{}) 13 | [5, 1, 2, 3, 4, 5] 14 | """ 15 | @spec dup(Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() 16 | def dup(list, _) do 17 | last = List.last(list) 18 | 19 | [last | list] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/exchange.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Exchange do 2 | alias EVM.Operation 3 | 4 | @doc """ 5 | Swaps the first and last values on the stack. 6 | 7 | ## Examples 8 | 9 | iex> EVM.Operation.Exchange.swap([1, 2, 3], %{}) 10 | [3, 2, 1] 11 | iex> EVM.Operation.Exchange.swap([1, 2, 3, 4, 5, 6], %{}) 12 | [6, 2, 3, 4, 5, 1] 13 | """ 14 | @spec swap(Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() 15 | def swap([first | rest], _vm_map) do 16 | [last | middle] = :lists.reverse(rest) 17 | [last | :lists.reverse([first | middle])] 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata do 2 | @moduledoc """ 3 | A simple struct to store metadata about all VM instructions. 4 | """ 5 | 6 | defstruct id: nil, 7 | sym: nil, 8 | fun: nil, 9 | args: [], 10 | input_count: nil, 11 | output_count: nil, 12 | description: nil, 13 | group: :other, 14 | machine_code_offset: 0 15 | 16 | @type t :: %__MODULE__{ 17 | :id => integer(), 18 | :sym => atom(), 19 | :fun => atom(), 20 | :args => [], 21 | # Denoted as δin the Yellow Paper 22 | :input_count => integer(), 23 | # Denoted as αin the Yellow Paper 24 | :output_count => integer(), 25 | :description => String.t(), 26 | :group => atom(), 27 | :machine_code_offset => integer() | nil 28 | } 29 | end 30 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/block_information.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.BlockInformation do 2 | @operations for operation <- [ 3 | %{ 4 | id: 0x40, 5 | description: "Get the hash of one of the 256 most recent complete blocks", 6 | sym: :blockhash, 7 | input_count: 1, 8 | output_count: 1, 9 | group: :block_information 10 | }, 11 | %{ 12 | id: 0x41, 13 | description: "Get the block’s beneficiary address", 14 | sym: :coinbase, 15 | input_count: 0, 16 | output_count: 1, 17 | group: :block_information 18 | }, 19 | %{ 20 | id: 0x42, 21 | description: "Get the block’s timestamp", 22 | sym: :timestamp, 23 | input_count: 0, 24 | output_count: 1, 25 | group: :block_information 26 | }, 27 | %{ 28 | id: 0x43, 29 | description: "Get the block’s number.", 30 | sym: :number, 31 | input_count: 0, 32 | output_count: 1, 33 | group: :block_information 34 | }, 35 | %{ 36 | id: 0x44, 37 | description: "Get the block’s difficulty.", 38 | sym: :difficulty, 39 | input_count: 0, 40 | output_count: 1, 41 | group: :block_information 42 | }, 43 | %{ 44 | id: 0x45, 45 | description: "Get the block’s gas limit.", 46 | sym: :gaslimit, 47 | input_count: 0, 48 | output_count: 1, 49 | group: :block_information 50 | } 51 | ], 52 | do: struct(EVM.Operation.Metadata, operation) 53 | 54 | def operations, do: @operations 55 | end 56 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/duplication.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.Duplication do 2 | @operations for n <- 1..17, 3 | do: %EVM.Operation.Metadata{ 4 | # 0x80..0x8e 5 | id: n + 0x7F, 6 | sym: :"dup#{n}", 7 | description: "Duplicate #{n}st stack item.", 8 | fun: :dup, 9 | input_count: n, 10 | output_count: 2, 11 | group: :duplication 12 | } 13 | def operations, do: @operations 14 | end 15 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/exchange.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.Exchange do 2 | alias EVM.Operation.Metadata 3 | 4 | @operations for n <- 1..17, 5 | do: %Metadata{ 6 | # 0x90..0x9e 7 | id: n + 0x8F, 8 | description: "Exchange #{n}st and #{n + 1}nd stack items.", 9 | sym: :"swap#{n}", 10 | fun: :swap, 11 | input_count: n + 1, 12 | output_count: 2, 13 | group: :exchange 14 | } 15 | 16 | def operations, do: @operations 17 | end 18 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/logging.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.Logging do 2 | @operations for n <- 0..4, 3 | do: %EVM.Operation.Metadata{ 4 | # 0xa0 - 0xa3 5 | id: 0xA0 + n, 6 | description: "Append log record with no topics.", 7 | sym: :"log#{n}", 8 | input_count: 2 + n, 9 | output_count: 0, 10 | group: :logging 11 | } 12 | 13 | def operations, do: @operations 14 | end 15 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/push.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.Push do 2 | @operations for n <- 1..32, 3 | do: %EVM.Operation.Metadata{ 4 | # 0x60..0x7f 5 | id: n + 0x5F, 6 | sym: :"push#{n}", 7 | description: "Place #{n}-byte item on stack", 8 | fun: :push_n, 9 | args: [n], 10 | input_count: 0, 11 | output_count: n, 12 | group: :push, 13 | machine_code_offset: n 14 | } 15 | def operations, do: @operations 16 | end 17 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/sha3.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.SHA3 do 2 | @operations [ 3 | %EVM.Operation.Metadata{ 4 | id: 0x20, 5 | description: "Compute Keccak-256 hash.", 6 | sym: :sha3, 7 | input_count: 2, 8 | output_count: 1, 9 | group: :sha3 10 | } 11 | ] 12 | 13 | def operations, do: @operations 14 | end 15 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/metadata/system.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Metadata.System do 2 | @operations for operation <- [ 3 | %{ 4 | id: 0xF0, 5 | description: "Create a new account with associated code.", 6 | sym: :create, 7 | input_count: 3, 8 | output_count: 1, 9 | group: :system 10 | }, 11 | %{ 12 | id: 0xF1, 13 | description: "Message-call into an account.,", 14 | sym: :call, 15 | input_count: 7, 16 | output_count: 1, 17 | group: :system 18 | }, 19 | %{ 20 | id: 0xF2, 21 | description: 22 | "Message-call into this account with an alternative account’s code.,", 23 | sym: :callcode, 24 | input_count: 7, 25 | output_count: 1, 26 | group: :system 27 | }, 28 | %{ 29 | id: 0xF3, 30 | description: "Halt execution returning output data,", 31 | sym: :return, 32 | input_count: 2, 33 | output_count: 0, 34 | group: :system 35 | }, 36 | %{ 37 | id: 0xF4, 38 | description: 39 | "Message-call into this account with an alternative account’s code, but persisting the current values for sender and value.", 40 | sym: :delegatecall, 41 | input_count: 6, 42 | output_count: 1, 43 | group: :system 44 | }, 45 | %{ 46 | id: 0xFF, 47 | description: "Halt execution and register account for later deletion.", 48 | sym: :suicide, 49 | input_count: 1, 50 | output_count: 0, 51 | group: :system 52 | } 53 | ], 54 | do: struct(EVM.Operation.Metadata, operation) 55 | 56 | def operations, do: @operations 57 | end 58 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/push.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Push do 2 | alias EVM.Operation 3 | alias EVM.Memory 4 | 5 | @doc """ 6 | Place n-byte item on stack 7 | 8 | ## Examples 9 | 10 | iex> EVM.Operation.Push.push_n(1, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 1}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 11 | 0x12 12 | 13 | iex> EVM.Operation.Push.push_n(1, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 2}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 14 | 0x13 15 | 16 | iex> EVM.Operation.Push.push_n(1, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 3}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 17 | 0x00 18 | 19 | iex> EVM.Operation.Push.push_n(1, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 4}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 20 | 0x00 21 | 22 | iex> EVM.Operation.Push.push_n(1, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 100}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 23 | 0x00 24 | 25 | iex> EVM.Operation.Push.push_n(6, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 0}, exec_env: %EVM.ExecEnv{machine_code: <<0xFF, 0x10, 0x11, 0x12, 0x13>>}}) 26 | 17665503723520 27 | 28 | iex> EVM.Operation.Push.push_n(16, [], %{machine_state: %EVM.MachineState{stack: [], program_counter: 100}, exec_env: %EVM.ExecEnv{machine_code: <<0x10, 0x11, 0x12, 0x13>>}}) 29 | 0x00 30 | """ 31 | @spec push_n(integer(), Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() 32 | def push_n(n, _args, %{machine_state: machine_state, exec_env: %{machine_code: machine_code}}) do 33 | Memory.read_zeroed_memory(machine_code, machine_state.program_counter + 1, n) 34 | |> :binary.decode_unsigned() 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/operation/sha3.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Sha3 do 2 | alias EVM 3 | alias EVM.Helpers 4 | alias EVM.Operation 5 | alias EVM.Stack 6 | alias EVM.Memory 7 | 8 | @doc """ 9 | Compute Keccak-256 hash. 10 | 11 | 12 | ## Examples 13 | 14 | iex> EVM.Operation.Sha3.sha3([1, 0], %{machine_state: %EVM.MachineState{}}) 15 | %EVM.MachineState{active_words: 0, gas: nil, memory: "", program_counter: 0, previously_active_words: 0, stack: [89477152217924674838424037953991966239322087453347756267410168184682657981552]} 16 | """ 17 | @spec sha3(Operation.stack_args(), Operation.vm_map()) :: Operation.op_result() 18 | def sha3([s0, s1], %{machine_state: machine_state}) do 19 | {value, machine_state} = Memory.read(machine_state, s0, s1) 20 | 21 | hash = Helpers.encode_val(:keccakf1600.sha3_256(value)) 22 | 23 | %{machine_state | stack: Stack.push(machine_state.stack, hash)} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/program_counter.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.ProgramCounter do 2 | alias EVM.Operation.Metadata 3 | 4 | @moduledoc """ 5 | Module for manipulating the program counter which keeps track 6 | of where we are in the contract code. 7 | 8 | Reffered to as `pc` in the Yellow Paper. 9 | """ 10 | 11 | @doc """ 12 | Increments the program counter 13 | 14 | ## Examples 15 | 16 | iex> EVM.ProgramCounter.next(9, EVM.Operation.metadata(:add), [1, 1]) 17 | 10 18 | iex> EVM.ProgramCounter.next(10, EVM.Operation.metadata(:push2), [1, 1]) 19 | 13 20 | iex> EVM.ProgramCounter.next(7, EVM.Operation.metadata(:jumpi), [1, 1]) 21 | 1 22 | iex> EVM.ProgramCounter.next(7, EVM.Operation.metadata(:jumpi), [1, 0]) 23 | 8 24 | """ 25 | @spec next(integer(), Metadata.t(), list(EVM.val())) :: integer() 26 | def next(_current_position, %{sym: :jump}, [position]) do 27 | position 28 | end 29 | 30 | def next(current_position, %{sym: :jumpi}, [position, condition]) do 31 | if condition == 0 do 32 | current_position + 1 33 | else 34 | position 35 | end 36 | end 37 | 38 | def next(current_position, %{machine_code_offset: machine_code_offset}, _inputs) do 39 | current_position + machine_code_offset + 1 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/sub_state.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.SubState do 2 | @moduledoc """ 3 | Functions for handling the sub-state that exists only 4 | between operations in an execution for a contract. 5 | """ 6 | 7 | alias EVM.LogEntry 8 | alias EVM.Operation 9 | 10 | defstruct suicide_list: [], 11 | logs: [], 12 | refund: 0 13 | 14 | @type suicide_list :: [EVM.address()] 15 | @type logs :: [LogEntry.t()] 16 | @type refund :: EVM.Wei.t() 17 | 18 | @type t :: %__MODULE__{ 19 | suicide_list: suicide_list, 20 | logs: logs, 21 | refund: refund 22 | } 23 | 24 | @doc """ 25 | Adds log entry to substate's log entry list. 26 | 27 | ## Examples 28 | 29 | iex> sub_state = %EVM.SubState{suicide_list: [], logs: [], refund: 0} 30 | iex> sub_state |> EVM.SubState.add_log(0, [1, 10, 12], "adsfa") 31 | %EVM.SubState{ 32 | logs: [ 33 | %EVM.LogEntry{ 34 | address: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>, 35 | data: "adsfa", 36 | topics: [1, 10, 12] 37 | } 38 | ], 39 | refund: 0, 40 | suicide_list: [] 41 | } 42 | """ 43 | @spec add_log(t(), EVM.address(), Operation.stack_args(), binary()) :: t() 44 | def add_log(sub_state, address, topics, data) do 45 | log_entry = LogEntry.new(address, topics, data) 46 | 47 | new_logs = Enum.reverse([log_entry | sub_state.logs]) 48 | 49 | %{sub_state | logs: new_logs} 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /apps/evm/lib/evm/wei.ex: -------------------------------------------------------------------------------- 1 | defmodule EVM.Wei do 2 | @moduledoc """ 3 | Functions for handling wei and conversions. 4 | """ 5 | 6 | @type t :: EVM.val() 7 | end 8 | -------------------------------------------------------------------------------- /apps/evm/lib/math_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule MathHelper do 2 | @moduledoc """ 3 | Simple functions to help with common 4 | math functions. 5 | """ 6 | 7 | @doc """ 8 | Simple floor function that makes sure 9 | we return an integer type. 10 | 11 | ## Examples 12 | 13 | iex> MathHelper.floor(3.5) 14 | 3 15 | 16 | iex> MathHelper.floor(-3.5) 17 | -4 18 | 19 | iex> MathHelper.floor(5) 20 | 5 21 | """ 22 | @spec floor(number()) :: integer() 23 | def floor(x), do: round(:math.floor(x)) 24 | 25 | @doc """ 26 | Simple round function 27 | 28 | ## Examples 29 | 30 | iex> MathHelper.round_int(3.5) 31 | 3 32 | iex> MathHelper.round_int(-3.5) 33 | -3 34 | iex> MathHelper.round_int(-0.5) 35 | 0 36 | """ 37 | @spec round_int(number()) :: integer() 38 | def round_int(n) when n < 0, do: round(:math.ceil(n)) 39 | def round_int(n), do: round(:math.floor(n)) 40 | 41 | @doc """ 42 | Simple helper to calculate a log in any 43 | given base. E.g. the `log_15(30)`, which 44 | would be expressed at `MathHelper.log(30, 15)`. 45 | 46 | ## Examples 47 | 48 | iex> MathHelper.log(225, 15) 49 | 2.0 50 | 51 | iex> MathHelper.log(240, 15) 52 | 2.0238320992392618 53 | 54 | iex> MathHelper.log(1024, 10) 55 | 3.0102999566398116 56 | 57 | iex> MathHelper.log(999999, 9999) 58 | 1.500016178459417 59 | """ 60 | @spec log(number(), number()) :: float() 61 | def log(x, b), do: :math.log(x) / :math.log(b) 62 | 63 | @doc """ 64 | Returns the byte size of an integer 65 | 66 | ## Examples 67 | 68 | iex> MathHelper.integer_byte_size(0) 69 | 0 70 | 71 | iex> MathHelper.integer_byte_size(1) 72 | 1 73 | 74 | iex> MathHelper.integer_byte_size(0xfffffffff) 75 | 5 76 | 77 | """ 78 | @spec integer_byte_size(number()) :: non_neg_integer() 79 | def integer_byte_size(n) when n == 0, do: 0 80 | def integer_byte_size(n), do: byte_size(:binary.encode_unsigned(n)) 81 | 82 | @doc """ 83 | Bits to words 84 | 85 | ## Examples 86 | 87 | iex> MathHelper.bits_to_words(0) 88 | 0 89 | 90 | iex> MathHelper.bits_to_words(9) 91 | 1 92 | 93 | iex> MathHelper.bits_to_words(256) 94 | 8 95 | 96 | """ 97 | @spec bits_to_words(number()) :: integer() 98 | def bits_to_words(n), do: round(:math.ceil(n / EVM.word_size())) 99 | end 100 | -------------------------------------------------------------------------------- /apps/evm/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :evm, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Ethereum's Virtual Machine, in all its glory.", 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Ayrat Badykov", "Mason Forest"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | dialyzer: [ignore_warnings: "../../.dialyzer.ignore-warnings"], 23 | preferred_cli_env: [ 24 | coveralls: :test, 25 | "coveralls.detail": :test, 26 | "coveralls.post": :test, 27 | "coveralls.html": :test, 28 | dialyzer: :test 29 | ], 30 | start_permanent: Mix.env() == :prod, 31 | test_coverage: [tool: ExCoveralls] 32 | ] 33 | end 34 | 35 | # Configuration for the OTP application 36 | # 37 | # Type "mix help compile.app" for more information 38 | def application do 39 | # Specify extra applications you'll use from Erlang/Elixir 40 | [extra_applications: [:logger], mod: {EVM.Application, []}] 41 | end 42 | 43 | # Run "mix help deps" to learn about dependencies. 44 | defp deps do 45 | [ 46 | # Umbrella 47 | {:ex_rlp, in_umbrella: true}, 48 | {:merkle_patricia_tree, in_umbrella: true}, 49 | 50 | # Libraries 51 | {:keccakf1600, "~> 2.0.0", hex: :keccakf1600_orig}, 52 | {:poison, "~> 4.0.1", only: [:dev, :test], runtime: false}, 53 | 54 | # Common 55 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 56 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 57 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 58 | ] 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /apps/evm/test/block/header_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Block.HeaderTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Block.Header 4 | alias EVM.Block.Header 5 | 6 | test "serialize and deserialize" do 7 | header = %Header{ 8 | parent_hash: <<1::256>>, 9 | ommers_hash: <<2::256>>, 10 | beneficiary: <<3::160>>, 11 | state_root: <<4::256>>, 12 | transactions_root: <<5::256>>, 13 | receipts_root: <<6::256>>, 14 | logs_bloom: <<>>, 15 | difficulty: 5, 16 | number: 1, 17 | gas_limit: 5, 18 | gas_used: 3, 19 | timestamp: 6, 20 | extra_data: "Hi mom", 21 | mix_hash: <<7::256>>, 22 | nonce: <<8::64>> 23 | } 24 | 25 | assert header == 26 | header 27 | |> Header.serialize() 28 | |> ExRLP.encode() 29 | |> ExRLP.decode() 30 | |> Header.deserialize() 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /apps/evm/test/evm/breakpoint/breakpoint_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Debugger.BreakpointTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Debugger.Breakpoint 4 | 5 | setup_all do 6 | EVM.Debugger.Breakpoint.init() 7 | 8 | :ok 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /apps/evm/test/evm/debugger_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.DebuggerTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Debugger 4 | 5 | setup_all do 6 | EVM.Debugger.Breakpoint.init() 7 | 8 | :ok 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /apps/evm/test/evm/exec_env_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.ExecEnvTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.ExecEnv 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/functions_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.FunctionsTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Functions 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/gas_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.GasTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Gas 4 | alias EVM.MachineCode 5 | alias EVM.MachineState 6 | alias EVM.ExecEnv 7 | alias EVM.Gas 8 | alias EVM.Interface.Mock.MockAccountInterface 9 | 10 | test "Gas cost: CALL" do 11 | to_address = 0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6 12 | inputs = [3000, to_address, 0, 0, 32, 32, 32] 13 | machine_state = %MachineState{program_counter: 0, stack: inputs} 14 | account_interface = MockAccountInterface.new() 15 | 16 | exec_env = %ExecEnv{ 17 | machine_code: MachineCode.compile([:call]), 18 | address: to_address, 19 | account_interface: account_interface 20 | } 21 | 22 | cost = Gas.cost(machine_state, exec_env) 23 | 24 | assert cost == 28046 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /apps/evm/test/evm/interface/mock/mock_account_interface_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Interface.Mock.MockAccountInterfaceTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Interface.Mock.MockAccountInterface 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/interface/mock/mock_block_interface_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Interface.Mock.MockBlockInterfaceTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Interface.Mock.MockBlockInterface 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/log_entry_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.LogEntryTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.LogEntry 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/machine_code_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.MachineCodeTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.MachineCode 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/machine_state_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.MachineStateTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.MachineState 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/memory_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.MemoryTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Memory 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/block_information_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.BlockInformationTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.BlockInformation 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/comparison_and_bitwise_logic_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.ComparisonAndBitwiseLogicTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.ComparisonAndBitwiseLogic 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/duplication_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.DuplicationTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Duplication 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/environmental_information_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.EnvironmentalInformationTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.EnvironmentalInformation 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/exchange_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.ExchangeTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Exchange 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/logging_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.LoggingTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Logging 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/metadata_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.MetadataTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Metadata 4 | 5 | # TODO: Add real tests here 6 | end 7 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/push_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.PushTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Push 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/sha3_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.Sha3Test do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.Sha3 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/stack_memory_storage_and_flow_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.StackMemoryStorageAndFlowTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.StackMemoryStorageAndFlow 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/stop_and_arithmetic_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.StopAndArithmeticTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.StopAndArithmetic 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation/system_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.Operation.SystemTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation.System 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/operation_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.OperationTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Operation 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/program_counter_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.ProgramCounterTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.ProgramCounter 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/stack_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.StackTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Stack 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/sub_state_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.SubStateTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.SubState 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/evm/vm_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.VMTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.VM 4 | 5 | alias EVM.VM 6 | alias EVM.MachineCode 7 | alias EVM.ExecEnv 8 | alias EVM.Interface.Mock.MockAccountInterface 9 | alias EVM.SubState 10 | 11 | setup do 12 | account_interface = MockAccountInterface.new() 13 | 14 | {:ok, 15 | %{ 16 | account_interface: account_interface 17 | }} 18 | end 19 | 20 | test "simple program with return value", %{} do 21 | instructions = [ 22 | :push1, 23 | 3, 24 | :push1, 25 | 5, 26 | :add, 27 | :push1, 28 | 0x00, 29 | :mstore, 30 | :push1, 31 | 32, 32 | :push1, 33 | 0, 34 | :return 35 | ] 36 | 37 | exec_env = %ExecEnv{machine_code: MachineCode.compile(instructions)} 38 | result = VM.run(24, exec_env) 39 | 40 | assert result == 41 | {0, %SubState{logs: [], refund: 0, suicide_list: []}, exec_env, <<0x08::256>>} 42 | end 43 | 44 | test "simple program with block storage", %{account_interface: account_interface} do 45 | address = 0x0000000000000000000000000000000000000001 46 | 47 | instructions = [ 48 | :push1, 49 | 3, 50 | :push1, 51 | 5, 52 | :sstore, 53 | :stop 54 | ] 55 | 56 | exec_env = %ExecEnv{ 57 | machine_code: MachineCode.compile(instructions), 58 | address: address, 59 | account_interface: account_interface 60 | } 61 | 62 | result = VM.run(20006, exec_env) 63 | 64 | expected_account_state = %{ 65 | address => %{ 66 | balance: 0, 67 | nonce: 0, 68 | storage: %{5 => 3} 69 | } 70 | } 71 | 72 | expected_account_interface = MockAccountInterface.new(expected_account_state) 73 | 74 | expected_exec_env = Map.put(exec_env, :account_interface, expected_account_interface) 75 | 76 | assert result == {0, %SubState{logs: [], refund: 0, suicide_list: []}, expected_exec_env, ""} 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /apps/evm/test/evm/wei_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.WeiTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Wei 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/helpers_test.exs: -------------------------------------------------------------------------------- 1 | defmodule EVM.HelpersTest do 2 | use ExUnit.Case, async: true 3 | doctest EVM.Helpers 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/math_helper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MathHelperTest do 2 | use ExUnit.Case 3 | doctest MathHelper 4 | end 5 | -------------------------------------------------------------------------------- /apps/evm/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/ex_rlp/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.3.0 2 | * Remove protocols for Map because it overrides custom struct protocols. (https://github.com/exthereum/ex_rlp/pull/12) 3 | # 0.2.1 4 | * Improve typespecs to allow for integers as a valid value to encode in RLP. (https://github.com/exthereum/ex_rlp/pull/6) 5 | # 0.2.0 6 | * Breaking: added option to encode RLP to either hex strings (`"8055FF"`) or binaries (`<<0x80, 0x55, 0xFF>`). The default is now `:binary`. (https://github.com/exthereum/ex_rlp/pull/3) 7 | * Added typespecs and additional test coverage through doctests. (https://github.com/exthereum/ex_rlp/pull/3) 8 | # 0.1.1 9 | * Adds protocols for encoding/decoding maps (https://github.com/exthereum/ex_rlp/commit/76451f707a961a3985e00be4ed3ea77fc27d5c83) 10 | -------------------------------------------------------------------------------- /apps/ex_rlp/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :ex_rlp, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:ex_rlp, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /apps/ex_rlp/lib/ex_rlp/encode.ex: -------------------------------------------------------------------------------- 1 | defprotocol ExRLP.Encode do 2 | def encode(value, options \\ []) 3 | end 4 | 5 | defimpl ExRLP.Encode, for: BitString do 6 | alias ExRLP.Utils 7 | 8 | @spec encode(ExRLP.t(), keyword()) :: binary() 9 | def encode(value, options \\ []) do 10 | value 11 | |> encode_item 12 | |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary)) 13 | end 14 | 15 | @spec encode_item(binary()) :: <<_::8, _::_*8>> 16 | defp encode_item(<> = item) when byte_size(item) == 1 and byte < 128 do 17 | item 18 | end 19 | 20 | defp encode_item(item) when is_binary(item) and byte_size(item) < 56 do 21 | prefix = 128 + byte_size(item) 22 | 23 | <> <> item 24 | end 25 | 26 | defp encode_item(item) when is_binary(item) do 27 | be_size = item |> Utils.big_endian_size() 28 | byte_size = be_size |> byte_size 29 | 30 | <<183 + byte_size>> <> be_size <> item 31 | end 32 | end 33 | 34 | defimpl ExRLP.Encode, for: Integer do 35 | alias ExRLP.Utils 36 | alias ExRLP.Encode 37 | 38 | @spec encode(ExRLP.t(), keyword()) :: binary() 39 | def encode(value, options \\ []) when value >= 0 do 40 | value 41 | |> to_binary() 42 | |> Encode.encode() 43 | |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary)) 44 | end 45 | 46 | @spec to_binary(integer()) :: binary() 47 | defp to_binary(object) when is_integer(object) and object == 0 do 48 | "" 49 | end 50 | 51 | defp to_binary(object) when is_integer(object) and object > 0 do 52 | object |> :binary.encode_unsigned() 53 | end 54 | end 55 | 56 | defimpl ExRLP.Encode, for: List do 57 | alias ExRLP.Utils 58 | alias ExRLP.Encode 59 | 60 | @spec encode([ExRLP.t()], keyword()) :: binary() 61 | def encode(values, options \\ []) do 62 | values 63 | |> encode_items("") 64 | |> Utils.maybe_encode_hex(Keyword.get(options, :encoding, :binary)) 65 | end 66 | 67 | @spec encode_items([ExRLP.t()], binary()) :: <<_::8, _::_*8>> 68 | defp encode_items([], acc) do 69 | acc |> prefix_list 70 | end 71 | 72 | defp encode_items([item | tail], acc) do 73 | encoded_item = item |> Encode.encode() 74 | 75 | tail |> encode_items(acc <> encoded_item) 76 | end 77 | 78 | @spec prefix_list(binary()) :: <<_::8, _::_*8>> 79 | defp prefix_list(encoded_concat) when byte_size(encoded_concat) < 56 do 80 | size = encoded_concat |> byte_size 81 | 82 | <<192 + size>> <> encoded_concat 83 | end 84 | 85 | defp prefix_list(encoded_concat) do 86 | be_size = encoded_concat |> Utils.big_endian_size() 87 | byte_size = be_size |> byte_size 88 | 89 | <<247 + byte_size>> <> be_size <> encoded_concat 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /apps/ex_rlp/lib/ex_rlp/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.Utils do 2 | @moduledoc false 3 | 4 | @spec maybe_encode_hex(binary(), atom()) :: binary() 5 | def maybe_encode_hex(value, :hex), do: encode_hex(value) 6 | def maybe_encode_hex(value, _encoding), do: value 7 | 8 | @spec encode_hex(binary()) :: binary() 9 | def encode_hex(binary) do 10 | binary |> Base.encode16(case: :lower) 11 | end 12 | 13 | @spec big_endian_size(binary()) :: bitstring() 14 | def big_endian_size(binary) do 15 | binary 16 | |> byte_size 17 | |> :binary.encode_unsigned() 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /apps/ex_rlp/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ex_rlp, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Ethereum's Recursive Length Prefix (RLP) encoding", 15 | package: [ 16 | maintainers: ["Ayrat Badykov", "Geoffrey Hayes"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ex_rlp"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | dialyzer: [ignore_warnings: "../../.dialyzer.ignore-warnings"], 23 | elixirc_paths: elixirc_paths(Mix.env()), 24 | preferred_cli_env: [ 25 | coveralls: :test, 26 | "coveralls.detail": :test, 27 | "coveralls.post": :test, 28 | "coveralls.html": :test, 29 | dialyzer: :test 30 | ], 31 | start_permanent: Mix.env() == :prod, 32 | test_coverage: [tool: ExCoveralls] 33 | ] 34 | end 35 | 36 | def application do 37 | [extra_applications: [:logger]] 38 | end 39 | 40 | # Run "mix help deps" to learn about dependencies. 41 | defp deps do 42 | [ 43 | # Umbrella 44 | 45 | # Libaries 46 | {:poison, "~> 4.0.1", only: [:dev, :test]}, 47 | 48 | # Common 49 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 50 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 51 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 52 | ] 53 | end 54 | 55 | defp elixirc_paths(:test), do: ["lib", "test/support"] 56 | defp elixirc_paths(_), do: ["lib"] 57 | end 58 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/ex_rlp/decode_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.DecodeTest do 2 | use ExUnit.Case 3 | alias ExRLP.Decode 4 | import ExRLP.TestUtils 5 | 6 | describe "decode/2" do 7 | test "correctly decodes objects" do 8 | test_file_name = "rlptest.json" 9 | 10 | test_file_name 11 | |> read_json_file 12 | |> Enum.each(fn {test_name, %{"in" => expected_result, "out" => input}} -> 13 | expected_result = expected_result |> normalize_data 14 | 15 | result = 16 | input 17 | |> Decode.decode(encoding: :hex) 18 | |> normalize_decoded_data(expected_result) 19 | 20 | assert result == expected_result, 21 | "Test for #{test_name} failed, expected #{result} to equal to #{expected_result}" 22 | end) 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/ex_rlp/encode_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.EncodeTest do 2 | use ExUnit.Case 3 | alias ExRLP.Encode 4 | import ExRLP.TestUtils 5 | 6 | describe "encode/2" do 7 | test "correctly encodes objects" do 8 | test_file_name = "rlptest.json" 9 | 10 | test_file_name 11 | |> read_json_file 12 | |> Enum.each(fn {test_name, %{"in" => input, "out" => expected_result}} -> 13 | result = 14 | input 15 | |> normalize_data() 16 | |> Encode.encode(encoding: :hex) 17 | 18 | assert result == expected_result, 19 | "Test for #{test_name} failed, expected #{result} to equal to #{expected_result}" 20 | end) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/ex_rlp/protocols_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.ProtocolsTest do 2 | use ExUnit.Case 3 | alias ExRLP.LogEntry 4 | 5 | @log_entry LogEntry.new( 6 | <<15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 108, 123, 7 | 94, 198>>, 8 | [0, 0, 0], 9 | <<255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 10 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255>> 11 | ) 12 | 13 | describe "encode/2" do 14 | test "encodes custom struct" do 15 | expected_result = 16 | <<248, 58, 148, 15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 109, 45, 17 | 108, 123, 94, 198, 195, 128, 128, 128, 160, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 19 | 255, 255, 255, 255, 255, 255>> 20 | 21 | assert ExRLP.encode(@log_entry) == expected_result 22 | end 23 | 24 | test "encodes custom struct in list" do 25 | expected_result = 26 | <<248, 60, 248, 58, 148, 15, 87, 46, 82, 149, 197, 127, 21, 136, 111, 155, 38, 62, 47, 27 | 109, 45, 108, 123, 94, 198, 195, 128, 128, 128, 160, 255, 255, 255, 255, 255, 255, 255, 28 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 29 | 255, 255, 255, 255, 255, 255, 255, 255>> 30 | 31 | assert ExRLP.encode([@log_entry]) == expected_result 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/ex_rlp_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExRLPTest do 2 | use ExUnit.Case, async: true 3 | doctest ExRLP 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/support/log_entry.ex: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.LogEntry do 2 | @moduledoc false 3 | 4 | defstruct address: nil, topics: [], data: nil 5 | 6 | @type t :: %__MODULE__{ 7 | address: EVM.address(), 8 | topics: [integer()], 9 | data: binary() 10 | } 11 | 12 | @spec new(binary, [integer()], binary()) :: t() 13 | def new(address, topics, data) do 14 | %__MODULE__{ 15 | address: address, 16 | topics: topics, 17 | data: data 18 | } 19 | end 20 | 21 | def to_list(log) do 22 | [log.address, log.topics, log.data] 23 | end 24 | end 25 | 26 | defimpl ExRLP.Encode, for: ExRLP.LogEntry do 27 | alias ExRLP.{Encode, LogEntry} 28 | 29 | @spec encode(LogEntry.t(), keyword()) :: binary() 30 | def encode(log, options \\ []) do 31 | log 32 | |> LogEntry.to_list() 33 | |> Encode.encode(options) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/support/test_utils.ex: -------------------------------------------------------------------------------- 1 | defmodule ExRLP.TestUtils do 2 | @moduledoc false 3 | @tests_directory "../../test/support/ethereum_common_tests/RLPTests/" 4 | 5 | def read_json_file(file_name) do 6 | {:ok, body} = File.read(@tests_directory <> file_name) 7 | 8 | Poison.decode!(body) 9 | end 10 | 11 | def normalize_data("#" <> number) do 12 | {num, ""} = Integer.parse(number) 13 | 14 | num 15 | end 16 | 17 | def normalize_data(input), do: input 18 | 19 | def normalize_decoded_data(input, output, acc \\ []) 20 | 21 | def normalize_decoded_data(input, output, _) when is_number(output) do 22 | input |> :binary.decode_unsigned() 23 | end 24 | 25 | def normalize_decoded_data([], [], acc), do: acc 26 | 27 | def normalize_decoded_data([in_head | in_tail], [out_head | out_tail], acc) do 28 | normalized_item = normalize_decoded_data(in_head, out_head) 29 | 30 | normalize_decoded_data(in_tail, out_tail, acc ++ [normalized_item]) 31 | end 32 | 33 | def normalize_decoded_data(input, _output, _acc), do: input 34 | end 35 | -------------------------------------------------------------------------------- /apps/ex_rlp/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/ex_wire/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.2 2 | * Build proper Discovery and Peering model 3 | * Add NAT traversal via UPnP 4 | * Default bind to random port 5 | # 0.1.1 6 | * Add RLPx, DevP2P and Eth Wire Protocol Support 7 | * Add syncing support from remote peers 8 | # 0.1.0 9 | * Add Discovery Protocol Support -------------------------------------------------------------------------------- /apps/ex_wire/README.md: -------------------------------------------------------------------------------- 1 | # ExWire [![CircleCI](https://circleci.com/gh/exthereum/ex_wire.svg?style=svg)](https://circleci.com/gh/exthereum/ex_wire) 2 | 3 | Elixir Client for RLPx, DevP2P and Eth Wire Protocol. 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 8 | by adding `ex_wire` to your list of dependencies in `mix.exs`: 9 | 10 | ```elixir 11 | def deps do 12 | [{:ex_wire, "~> 0.1.2"}] 13 | end 14 | ``` 15 | 16 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 17 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 18 | be found at [https://hexdocs.pm/ex_wire](https://hexdocs.pm/ex_wire). 19 | 20 | ## Usage 21 | 22 | For the time being, this can be used to sync the block chain from a given network (currently defaulted to Ropsten). 23 | 24 | You can run `iex -S mix` and you should see: 25 | 26 | ``` 27 | 12:59:23.818 [debug] [Network] [6ce059...1acd9d] Established outbound connection with 13.84.180.240, sending auth. 28 | 29 | 12:59:23.858 [debug] [Network] Sending EIP8 Handshake to 13.84.180.240 30 | 31 | 12:59:23.884 [debug] [Network] [6ce059...1acd9d] Sending raw data message of length 388 byte(s) to 13.84.180.240 32 | 33 | 12:59:23.886 [debug] [Sync] Requesting block 0 34 | 35 | ... 36 | 37 | 12:59:24.496 [debug] [Packet] Peer sent 1 header(s) 38 | 39 | 12:59:24.540 [debug] [Block Queue] Verified block and added to new block tree 40 | 41 | 12:59:24.540 [debug] [Sync] Requesting block 1 42 | 43 | 12:59:24.541 [info] [Network] [6ce059...1acd9d] Sending packet Elixir.ExWire.Packet.GetBlockHeaders to 13.84.180.240 44 | 45 | 12:59:24.593 [debug] [Network] [6ce059...1acd9d] Got packet Elixir.ExWire.Packet.BlockHeaders from 13.84.180.240 46 | 47 | 12:59:24.593 [debug] [Packet] Peer sent 1 header(s) 48 | 49 | 12:59:24.595 [debug] [Block Queue] Verified block and added to new block tree 50 | ``` 51 | 52 | In the future, we will continue to grow and built out a proper syncing ability. It's likely the proper interface (with CLI tools) will not be this module directly, but instead a general CLI which calls into this module. 53 | -------------------------------------------------------------------------------- /apps/ex_wire/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | config :ex_wire, 6 | p2p_version: 0x04, 7 | protocol_version: 63, 8 | # ropsten 9 | network_id: 3, 10 | caps: [{"eth", 62}, {"eth", 63}], 11 | chain: :ropsten, 12 | # TODO: This should be set and stored in a file 13 | private_key: :random, 14 | bootnodes: :from_chain, 15 | # Number of peer advertisements before we trust a block 16 | commitment_count: 1 17 | 18 | import_config "#{Mix.env()}.exs" 19 | -------------------------------------------------------------------------------- /apps/ex_wire/config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :ex_wire, 4 | network_adapter: ExWire.Adapter.UDP, 5 | sync: true, 6 | use_nat: true, 7 | local_ip: {127, 0, 0, 1}, 8 | # Number of peer advertisements before we trust a block 9 | commitment_count: 2 10 | -------------------------------------------------------------------------------- /apps/ex_wire/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | level: :warn, 5 | # purge logs with lower level than this - removes calls from code 6 | compile_time_purge_level: :info 7 | -------------------------------------------------------------------------------- /apps/ex_wire/config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :ex_wire, 4 | network_adapter: ExWire.Adapter.Test, 5 | private_key: :random 6 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire do 2 | @moduledoc """ 3 | Main application for ExWire. We will begin listening on a port 4 | when this application is started. 5 | """ 6 | 7 | @default_network_adapter Application.get_env(:ex_wire, :network_adapter) 8 | 9 | @type node_id :: binary() 10 | 11 | use Application 12 | alias ExWire.Config 13 | alias ExWire.Discovery 14 | alias ExWire.PeerSupervisor 15 | alias ExWire.Sync 16 | alias ExWire.Network 17 | alias MerklePatriciaTree.Test 18 | 19 | def start(_type, args) do 20 | import Supervisor.Spec 21 | 22 | network_adapter = Keyword.get(args, :network_adapter, @default_network_adapter) 23 | port = Keyword.get(args, :port, Config.listen_port()) 24 | name = Keyword.get(args, :name, ExWire) 25 | 26 | sync_children = 27 | case Config.sync() do 28 | true -> 29 | # TODO: Replace with level db 30 | db = Test.random_ets_db() 31 | 32 | [ 33 | worker(Discovery, [Config.bootnodes()]), 34 | worker(PeerSupervisor, [:ok]), 35 | worker(Sync, [db]) 36 | ] 37 | 38 | _ -> 39 | [] 40 | end 41 | 42 | discovery = 43 | case Config.sync() do 44 | true -> Discovery 45 | _ -> nil 46 | end 47 | 48 | children = 49 | [ 50 | worker(network_adapter, [{Network, [discovery]}, port], 51 | name: Network, 52 | restart: :permanent 53 | ) 54 | ] ++ sync_children 55 | 56 | opts = [strategy: :one_for_one, name: name] 57 | Supervisor.start_link(children, opts) 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/adapter/udp.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Adapter.UDP do 2 | @moduledoc """ 3 | Starts a UDP server to handle incoming and outgoing 4 | peer to peer messages according to RLPx. 5 | """ 6 | use GenServer 7 | use Bitwise 8 | 9 | alias ExWire.Util.Timestamp 10 | 11 | require Logger 12 | 13 | @doc """ 14 | When starting a UDP server, we'll store a network to use for all 15 | message handling. 16 | """ 17 | def start_link({network, network_args}, port, name \\ __MODULE__) do 18 | GenServer.start_link(__MODULE__, %{network: network, network_args: network_args, port: port}, 19 | name: name 20 | ) 21 | end 22 | 23 | @doc """ 24 | Initialize by opening up a `gen_udp` server on a given port. 25 | """ 26 | def init(state = %{port: port}) do 27 | {:ok, socket} = 28 | :gen_udp.open(port, [{:ip, {0, 0, 0, 0}}, {:active, true}, {:reuseaddr, true}, :binary]) 29 | 30 | {:ok, port_num} = :inet.port(socket) 31 | _ = Logger.debug(fn -> "[UDP] Listening on port #{port_num}" end) 32 | 33 | {:ok, Map.put(state, :socket, socket)} 34 | end 35 | 36 | @doc """ 37 | Handle info will handle when we have communucation from a peer node. 38 | 39 | We'll offload the effort to our `ExWire.Network` and `ExWire.Handler` modules. 40 | 41 | Note: all responses will be asynchronous. 42 | """ 43 | def handle_info( 44 | {:udp, _socket, ip, port, data}, 45 | state = %{network: network, network_args: network_args} 46 | ) do 47 | inbound_message = %ExWire.Network.InboundMessage{ 48 | data: data, 49 | server_pid: self(), 50 | remote_host: %ExWire.Struct.Endpoint{ 51 | ip: ip |> Tuple.to_list(), 52 | udp_port: port 53 | }, 54 | timestamp: Timestamp.soon() 55 | } 56 | 57 | apply(network, :receive, [inbound_message | network_args]) 58 | 59 | {:noreply, state} 60 | end 61 | 62 | @doc """ 63 | For cast, we'll respond back to a given peer with a given message package. This represents 64 | all outbound messages we'll ever send. 65 | """ 66 | def handle_cast( 67 | {:send, %{to: %{ip: ip, udp_port: udp_port}, data: data}}, 68 | state = %{socket: socket} 69 | ) 70 | when not is_nil(udp_port) do 71 | # TODO: How should we handle invalid ping or message requests? 72 | _ = :gen_udp.send(socket, ip, udp_port, data) 73 | 74 | {:noreply, state} 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/exth.ex: -------------------------------------------------------------------------------- 1 | defmodule Exth do 2 | @moduledoc """ 3 | General helper functions, like for inspection. 4 | """ 5 | require Logger 6 | 7 | @spec view(any(), String.t() | nil) :: any() 8 | def view(variable, prefix \\ nil) do 9 | args = if prefix, do: [prefix, variable], else: variable 10 | 11 | _ = Logger.debug(fn -> "#{inspect(args)}" end) 12 | 13 | variable 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handler.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler do 2 | @moduledoc """ 3 | Defines a behavior for all message handlers of RLPx messages. 4 | 5 | Message handlers tell us how we should respond to a given incoming transmission, 6 | after it has been decoded. 7 | """ 8 | 9 | alias ExWire.Message 10 | alias ExWire.Crypto 11 | 12 | require Logger 13 | 14 | @handlers %{ 15 | 0x01 => ExWire.Handler.Ping, 16 | 0x02 => ExWire.Handler.Pong, 17 | 0x03 => ExWire.Handler.FindNeighbours, 18 | 0x04 => ExWire.Handler.Neighbours 19 | } 20 | 21 | defmodule Params do 22 | @moduledoc "Struct to store parameters from an incoming message" 23 | 24 | defstruct remote_host: nil, 25 | signature: nil, 26 | recovery_id: nil, 27 | hash: nil, 28 | type: nil, 29 | data: nil, 30 | timestamp: nil, 31 | node_id: nil 32 | 33 | @type t :: %__MODULE__{ 34 | remote_host: Endpoint.t(), 35 | signature: Crpyto.signature(), 36 | recovery_id: Crypto.recovery_id(), 37 | hash: Crypto.hash(), 38 | type: integer(), 39 | data: binary(), 40 | timestamp: integer(), 41 | node_id: ExWire.node_id() 42 | } 43 | end 44 | 45 | @type handler_response :: :not_implented | :no_response | {:respond, Message.t()} 46 | @callback handle(Params.t()) :: handler_response 47 | 48 | @doc """ 49 | Decides which module to route the given message to, 50 | or returns `:not_implemented` if we have no implemented 51 | a handler for the message type. 52 | 53 | ## Examples 54 | 55 | iex> ExWire.Handler.dispatch(0x01, %ExWire.Handler.Params{ 56 | ...> remote_host: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, udp_port: 55}, 57 | ...> signature: 2, 58 | ...> recovery_id: 3, 59 | ...> hash: <<5>>, 60 | ...> data: [1, [<<1,2,3,4>>, <<>>, <<5>>], [<<5,6,7,8>>, <<6>>, <<>>], 4] |> ExRLP.encode(), 61 | ...> timestamp: 123, 62 | ...> }, nil) 63 | {:respond, %ExWire.Message.Pong{ 64 | hash: <<5>>, 65 | timestamp: 123, 66 | to: %ExWire.Struct.Endpoint{ 67 | ip: {1, 2, 3, 4}, 68 | tcp_port: 5, 69 | udp_port: nil 70 | } 71 | }} 72 | 73 | iex> ExWire.Handler.dispatch(0x99, %ExWire.Handler.Params{}, nil) 74 | :not_implemented 75 | 76 | # TODO: Add a `no_response` test case 77 | """ 78 | @spec dispatch(integer(), Params.t(), identifier() | nil) :: handler_response 79 | def dispatch(type, params, discovery) do 80 | case @handlers[type] do 81 | nil -> 82 | _ = Logger.warn("Message code `#{inspect(type, base: :hex)}` not implemented") 83 | :not_implemented 84 | 85 | mod when is_atom(mod) -> 86 | apply(mod, :handle, [params, discovery]) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handler/find_neighbours.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.FindNeighbours do 2 | @moduledoc """ 3 | Not currently implemented. 4 | """ 5 | 6 | alias ExWire.Handler 7 | alias Handler.Params 8 | alias ExWire.Discovery 9 | alias ExWire.Message.Neighbours 10 | alias ExWire.Message.FindNeighbours 11 | 12 | @doc """ 13 | Handler for a FindNeighbors message. 14 | 15 | ## Examples 16 | 17 | iex> ExWire.Handler.FindNeighbours.handle(%ExWire.Handler.Params{ 18 | ...> remote_host: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, udp_port: 55}, 19 | ...> signature: 2, 20 | ...> recovery_id: 3, 21 | ...> hash: <<5>>, 22 | ...> data: <<194, 1, 2>>, 23 | ...> timestamp: 7, 24 | ...> }, nil) 25 | {:respond, %ExWire.Message.Neighbours{ 26 | nodes: [], 27 | timestamp: 7, 28 | }} 29 | """ 30 | @spec handle(Params.t(), identifier | nil) :: Handler.handler_response() 31 | def handle(params, discovery) do 32 | find_neighbours = FindNeighbours.decode(params.data) 33 | 34 | nodes = 35 | if discovery do 36 | Discovery.get_neighbours(discovery, find_neighbours.target) 37 | else 38 | [] 39 | end 40 | 41 | {:respond, 42 | %Neighbours{ 43 | nodes: nodes, 44 | timestamp: params.timestamp 45 | }} 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handler/neighbours.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.Neighbours do 2 | @moduledoc """ 3 | Module to handle a response to a Neighbours message, which 4 | should be to add the neighbors to the correct K-Buckets. 5 | 6 | Jim Nabors is way cool. 7 | """ 8 | 9 | require Logger 10 | 11 | alias ExWire.Handler 12 | alias ExWire.Message.Neighbours 13 | alias Handler.Params 14 | alias ExWire.Discovery 15 | 16 | @doc """ 17 | Handler for a Neighbours message. 18 | 19 | ## Examples 20 | 21 | iex> message = %ExWire.Message.Neighbours{ 22 | ...> nodes: [ 23 | ...> %ExWire.Struct.Neighbour{endpoint: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, tcp_port: 5, udp_port: nil}, node: <<7, 7>>}, 24 | ...> %ExWire.Struct.Neighbour{endpoint: %ExWire.Struct.Endpoint{ip: {5, 6, 7, 8}, tcp_port: nil, udp_port: 6}, node: <<8, 8>>}], 25 | ...> timestamp: 1 26 | ...> } 27 | iex> ExWire.Handler.Neighbours.handle(%ExWire.Handler.Params{ 28 | ...> remote_host: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, udp_port: 55}, 29 | ...> signature: 2, 30 | ...> recovery_id: 3, 31 | ...> hash: <<5>>, 32 | ...> data: message |> ExWire.Message.Neighbours.encode(), 33 | ...> timestamp: 123, 34 | ...> }, nil) 35 | :no_response 36 | """ 37 | @spec handle(Params.t(), identifier() | nil) :: Handler.handler_response() 38 | def handle(params, discovery) do 39 | neighbours = Neighbours.decode(params.data) 40 | 41 | if discovery, do: Discovery.add_neighbours(discovery, neighbours) 42 | 43 | :no_response 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handler/ping.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.Ping do 2 | @moduledoc """ 3 | Module to handle a respond to a Ping message, which generate a Pong response. 4 | """ 5 | 6 | alias ExWire.Handler 7 | alias ExWire.Message.Pong 8 | alias ExWire.Message.Ping 9 | 10 | @doc """ 11 | Handler for a Ping message. 12 | 13 | ## Examples 14 | 15 | iex> ExWire.Handler.Ping.handle(%ExWire.Handler.Params{ 16 | ...> remote_host: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, udp_port: 55}, 17 | ...> signature: 2, 18 | ...> recovery_id: 3, 19 | ...> hash: <<5>>, 20 | ...> data: [1, [<<1,2,3,4>>, <<>>, <<5>>], [<<5,6,7,8>>, <<6>>, <<>>], 4] |> ExRLP.encode(), 21 | ...> timestamp: 123, 22 | ...> }, nil) 23 | {:respond, %ExWire.Message.Pong{ 24 | hash: <<5>>, 25 | timestamp: 123, 26 | to: %ExWire.Struct.Endpoint{ 27 | ip: {1, 2, 3, 4}, 28 | tcp_port: 5, 29 | udp_port: nil 30 | } 31 | }} 32 | """ 33 | @spec handle(Handler.Params.t(), identifier() | nil) :: Handler.handler_response() 34 | def handle(params, _discovery) do 35 | ping = Ping.decode(params.data) 36 | 37 | {:respond, 38 | %Pong{ 39 | to: ping.from, 40 | hash: params.hash, 41 | timestamp: params.timestamp 42 | }} 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handler/pong.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.Pong do 2 | @moduledoc """ 3 | Module to handle a response to a Pong message, which is to do nothing. 4 | """ 5 | 6 | alias ExWire.Handler 7 | alias ExWire.Message.Pong 8 | alias ExWire.Discovery 9 | 10 | @doc """ 11 | Handler for a Pong message. 12 | 13 | ## Examples 14 | 15 | iex> ExWire.Handler.Pong.handle(%ExWire.Handler.Params{ 16 | ...> remote_host: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, udp_port: 55}, 17 | ...> signature: 2, 18 | ...> recovery_id: 3, 19 | ...> hash: <<5>>, 20 | ...> data: [[<<1,2,3,4>>, <<>>, <<5>>], <<2>>, 3] |> ExRLP.encode(), 21 | ...> timestamp: 123, 22 | ...> }, nil) 23 | :no_response 24 | """ 25 | @spec handle(Handler.Params.t(), identifier() | nil) :: Handler.handler_response() 26 | def handle(params, discovery) do 27 | _pong = Pong.decode(params.data) 28 | 29 | if discovery, do: Discovery.pong(discovery, params.node_id) 30 | 31 | :no_response 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handshake/struct/ack_resp_v4.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handshake.Struct.AckRespV4 do 2 | @moduledoc """ 3 | Simple struct to wrap an auth response. 4 | 5 | The RLPx v4 handshake ack is defined in EIP-8. 6 | """ 7 | 8 | defstruct [ 9 | :remote_ephemeral_public_key, 10 | :remote_nonce, 11 | :remote_version 12 | ] 13 | 14 | @type t :: %__MODULE__{ 15 | remote_ephemeral_public_key: Key.public_key(), 16 | remote_nonce: binary(), 17 | remote_version: integer() 18 | } 19 | 20 | @spec serialize(t) :: ExRLP.t() 21 | def serialize(auth_resp) do 22 | [ 23 | auth_resp.remote_ephemeral_public_key, 24 | auth_resp.remote_nonce, 25 | auth_resp.remote_version |> :binary.encode_unsigned() 26 | ] 27 | end 28 | 29 | @spec deserialize(ExRLP.t()) :: t 30 | def deserialize(rlp) do 31 | [ 32 | remote_ephemeral_public_key 33 | | [ 34 | remote_nonce 35 | | [ 36 | remote_version 37 | | _tl 38 | ] 39 | ] 40 | ] = rlp 41 | 42 | %__MODULE__{ 43 | remote_ephemeral_public_key: remote_ephemeral_public_key, 44 | remote_nonce: remote_nonce, 45 | remote_version: 46 | if(is_binary(remote_version), 47 | do: :binary.decode_unsigned(remote_version), 48 | else: remote_version 49 | ) 50 | } 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/handshake/struct/auth_msg_v4.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handshake.Struct.AuthMsgV4 do 2 | @moduledoc """ 3 | Simple struct to wrap an auth msg. 4 | 5 | The RLPx v4 handshake auth is defined in EIP-8. 6 | """ 7 | 8 | alias ExthCrypto.Key 9 | alias ExthCrypto.Signature 10 | alias ExthCrypto.ECIES.ECDH 11 | 12 | defstruct [ 13 | :signature, 14 | :remote_public_key, 15 | :remote_nonce, 16 | :remote_version, 17 | :remote_ephemeral_public_key 18 | ] 19 | 20 | @type t :: %__MODULE__{ 21 | signature: ExthCrypto.signature(), 22 | remote_public_key: Key.public_key(), 23 | remote_nonce: binary(), 24 | remote_version: integer(), 25 | remote_ephemeral_public_key: Key.public_key() 26 | } 27 | 28 | @spec serialize(t) :: ExRLP.t() 29 | def serialize(auth_msg) do 30 | [ 31 | auth_msg.signature, 32 | auth_msg.remote_public_key |> Key.der_to_raw(), 33 | auth_msg.remote_nonce, 34 | auth_msg.remote_version |> :binary.encode_unsigned() 35 | ] 36 | end 37 | 38 | @spec deserialize(ExRLP.t()) :: t 39 | def deserialize(rlp) do 40 | [ 41 | signature 42 | | [ 43 | remote_public_key 44 | | [ 45 | remote_nonce 46 | | [ 47 | remote_version 48 | | _tl 49 | ] 50 | ] 51 | ] 52 | ] = rlp 53 | 54 | %__MODULE__{ 55 | signature: signature, 56 | remote_public_key: remote_public_key |> Key.raw_to_der(), 57 | remote_nonce: remote_nonce, 58 | remote_version: 59 | if(is_binary(remote_version), 60 | do: :binary.decode_unsigned(remote_version), 61 | else: remote_version 62 | ) 63 | } 64 | end 65 | 66 | @doc """ 67 | Sets the remote ephemeral public key for a given auth msg, based on our secret 68 | and the keys passed from remote. 69 | 70 | # TODO: Test 71 | # TODO: Multiple possible values and no recovery key? 72 | """ 73 | @spec set_remote_ephemeral_public_key(t, Key.private_key()) :: t 74 | def set_remote_ephemeral_public_key(auth_msg, my_static_private_key) do 75 | shared_secret = ECDH.generate_shared_secret(my_static_private_key, auth_msg.remote_public_key) 76 | shared_secret_xor_nonce = :crypto.exor(shared_secret, auth_msg.remote_nonce) 77 | 78 | {:ok, remote_ephemeral_public_key} = 79 | Signature.recover(shared_secret_xor_nonce, auth_msg.signature, 0) 80 | 81 | %{auth_msg | remote_ephemeral_public_key: remote_ephemeral_public_key} 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/message/find_neighbours.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.FindNeighbours do 2 | @moduledoc """ 3 | A wrapper for ExWire's `FindNeighbours` message. 4 | 5 | "Id of a node. The responding node will send back nodes closest to the target." 6 | """ 7 | 8 | @behaviour ExWire.Message 9 | @message_id 0x03 10 | 11 | defstruct target: nil, 12 | timestamp: nil 13 | 14 | @type t :: %__MODULE__{ 15 | target: ExWire.node_id(), 16 | timestamp: integer() 17 | } 18 | 19 | @spec message_id() :: ExWire.Message.message_id() 20 | def message_id, do: @message_id 21 | 22 | @doc """ 23 | Decodes a given message binary, which is assumed 24 | to be an RLP encoded list of elements. 25 | 26 | ## Examples 27 | 28 | iex> ExWire.Message.FindNeighbours.decode([<<1>>, 2] |> ExRLP.encode) 29 | %ExWire.Message.FindNeighbours{ 30 | target: <<1>>, 31 | timestamp: 2, 32 | } 33 | 34 | iex> ExWire.Message.FindNeighbours.decode([<<1>>] |> ExRLP.encode) 35 | ** (MatchError) no match of right hand side value: [<<1>>] 36 | """ 37 | @spec decode(binary()) :: t 38 | def decode(data) do 39 | [target, timestamp] = ExRLP.decode(data) 40 | 41 | %__MODULE__{ 42 | target: target, 43 | timestamp: :binary.decode_unsigned(timestamp) 44 | } 45 | end 46 | 47 | @doc """ 48 | Given a FindNeighbours message, encodes it so it can be sent on the wire in RLPx. 49 | 50 | ## Examples 51 | 52 | iex> ExWire.Message.FindNeighbours.encode(%ExWire.Message.FindNeighbours{target: <<1>>, timestamp: 2}) 53 | ...> |> ExRLP.decode() 54 | [<<1>>, <<2>>] 55 | """ 56 | @spec encode(t) :: binary() 57 | def encode(%__MODULE__{target: target, timestamp: timestamp}) do 58 | ExRLP.encode([ 59 | target, 60 | timestamp 61 | ]) 62 | end 63 | 64 | @doc """ 65 | FindNeighbours messages do not specify a destination. 66 | 67 | ## Examples 68 | 69 | iex> ExWire.Message.FindNeighbours.to(%ExWire.Message.FindNeighbours{target: <<1>>, timestamp: 2}) 70 | nil 71 | """ 72 | @spec to(t) :: Endpoint.t() | nil 73 | def to(_message), do: nil 74 | end 75 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/message/pong.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.Pong do 2 | @moduledoc """ 3 | A wrapper for ExWire's `Pong` message. 4 | """ 5 | 6 | alias ExWire.Struct.Endpoint 7 | 8 | @message_id 0x02 9 | 10 | defstruct to: nil, 11 | hash: nil, 12 | timestamp: nil 13 | 14 | @type t :: %__MODULE__{ 15 | to: Endpoint.t(), 16 | hash: binary(), 17 | timestamp: integer() 18 | } 19 | 20 | @spec message_id() :: ExWire.Message.message_id() 21 | def message_id, do: @message_id 22 | 23 | @doc """ 24 | Decodes a given message binary, which is assumed 25 | to be an RLP encoded list of elements. 26 | 27 | ## Examples 28 | 29 | iex> ExWire.Message.Pong.decode([[<<1,2,3,4>>, <<>>, <<0, 5>>], <<2>>, 3] |> ExRLP.encode) 30 | %ExWire.Message.Pong{ 31 | to: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, tcp_port: 5, udp_port: nil}, 32 | hash: <<2>>, 33 | timestamp: 3, 34 | } 35 | 36 | iex> ExWire.Message.Pong.decode([<<1>>] |> ExRLP.encode) 37 | ** (MatchError) no match of right hand side value: [<<1>>] 38 | """ 39 | @spec decode(binary()) :: t 40 | def decode(data) do 41 | [to, hash, timestamp] = ExRLP.decode(data) 42 | 43 | %__MODULE__{ 44 | to: Endpoint.decode(to), 45 | hash: hash, 46 | timestamp: :binary.decode_unsigned(timestamp) 47 | } 48 | end 49 | 50 | @doc """ 51 | Given a Pong message, encodes it so it can be sent on the wire in RLPx. 52 | 53 | ## Examples 54 | 55 | iex> ExWire.Message.Pong.encode(%ExWire.Message.Pong{ 56 | ...> to: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, tcp_port: 5, udp_port: nil}, 57 | ...> hash: <<2>>, 58 | ...> timestamp: 3} 59 | ...> ) |> ExRLP.decode() 60 | [[<<1, 2, 3, 4>>, "", <<0, 5>>], <<2>>, <<3>>] 61 | """ 62 | @spec encode(t) :: binary() 63 | def encode(%__MODULE__{to: to, hash: hash, timestamp: timestamp}) do 64 | ExRLP.encode([ 65 | Endpoint.encode(to), 66 | hash, 67 | timestamp 68 | ]) 69 | end 70 | 71 | @doc """ 72 | Pong messages should be routed to given endpoint. 73 | 74 | ## Examples 75 | 76 | iex> ExWire.Message.Pong.to(%ExWire.Message.Pong{ 77 | ...> to: %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, tcp_port: 5, udp_port: nil}, 78 | ...> hash: <<2>>, 79 | ...> timestamp: 3} 80 | ...> ) 81 | %ExWire.Struct.Endpoint{ip: {1, 2, 3, 4}, tcp_port: 5, udp_port: nil} 82 | """ 83 | @spec to(t) :: Endpoint.t() | nil 84 | def to(message) do 85 | message.to 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/packet/get_block_bodies.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.GetBlockBodies do 2 | @moduledoc """ 3 | Request the bodies for a set of blocks by hash. 4 | 5 | ``` 6 | `GetBlockBodies` [`+0x05`, `hash_0`: `B_32`, `hash_1`: `B_32`, ...] 7 | 8 | Require peer to return a BlockBodies message. Specify the set of blocks that 9 | we're interested in with the hashes. 10 | ``` 11 | """ 12 | 13 | @behaviour ExWire.Packet 14 | 15 | @type t :: %__MODULE__{ 16 | hashes: [binary()] 17 | } 18 | 19 | defstruct hashes: [] 20 | 21 | @doc """ 22 | Given a GetBlockBodies packet, serializes for transport over Eth Wire Protocol. 23 | 24 | ## Examples 25 | 26 | iex> %ExWire.Packet.GetBlockBodies{hashes: [<<5>>, <<6>>]} 27 | ...> |> ExWire.Packet.GetBlockBodies.serialize 28 | [<<5>>, <<6>>] 29 | """ 30 | @spec serialize(t) :: ExRLP.t() 31 | def serialize(packet = %__MODULE__{}) do 32 | packet.hashes 33 | end 34 | 35 | @doc """ 36 | Given an RLP-encoded GetBlockBodies packet from Eth Wire Protocol, 37 | decodes into a GetBlockBodies struct. 38 | 39 | ## Examples 40 | 41 | iex> ExWire.Packet.GetBlockBodies.deserialize([<<5>>, <<6>>]) 42 | %ExWire.Packet.GetBlockBodies{hashes: [<<5>>, <<6>>]} 43 | """ 44 | @spec deserialize(ExRLP.t()) :: t 45 | def deserialize(rlp) do 46 | # verify it's a list 47 | hashes = [_h | _t] = rlp 48 | 49 | %__MODULE__{ 50 | hashes: hashes 51 | } 52 | end 53 | 54 | @doc """ 55 | Handles a GetBlockBodies message. We shoud send the block bodies 56 | to the peer if we have them. For now, we'll do nothing. 57 | 58 | ## Examples 59 | 60 | iex> %ExWire.Packet.GetBlockBodies{hashes: [<<5>>, <<6>>]} 61 | ...> |> ExWire.Packet.GetBlockBodies.handle() 62 | :ok 63 | """ 64 | @spec handle(ExWire.Packet.packet()) :: ExWire.Packet.handle_response() 65 | def handle(_packet = %__MODULE__{}) do 66 | :ok 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/packet/ping.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.Ping do 2 | @moduledoc """ 3 | Ping is used to determine round-trip time of messages to a peer. 4 | 5 | ``` 6 | **Ping** `0x02` [] 7 | 8 | Requests an immediate reply of Pong from the peer. 9 | ``` 10 | """ 11 | 12 | require Logger 13 | 14 | @behaviour ExWire.Packet 15 | 16 | @type t :: %__MODULE__{} 17 | 18 | defstruct [] 19 | 20 | @doc """ 21 | Given a Ping packet, serializes for transport over Eth Wire Protocol. 22 | 23 | ## Examples 24 | 25 | iex> %ExWire.Packet.Ping{} 26 | ...> |> ExWire.Packet.Ping.serialize 27 | [] 28 | """ 29 | @spec serialize(t) :: ExRLP.t() 30 | def serialize(_packet = %__MODULE__{}) do 31 | [] 32 | end 33 | 34 | @doc """ 35 | Given an RLP-encoded Ping packet from Eth Wire Protocol, 36 | decodes into a Ping struct. 37 | 38 | ## Examples 39 | 40 | iex> ExWire.Packet.Ping.deserialize([]) 41 | %ExWire.Packet.Ping{} 42 | """ 43 | @spec deserialize(ExRLP.t()) :: t 44 | def deserialize(rlp) do 45 | [] = rlp 46 | 47 | %__MODULE__{} 48 | end 49 | 50 | @doc """ 51 | Handles a Ping message. We send a Pong back to the peer. 52 | 53 | ## Examples 54 | 55 | iex> ExWire.Packet.Ping.handle(%ExWire.Packet.Ping{}) 56 | {:send, %ExWire.Packet.Pong{}} 57 | """ 58 | @spec handle(ExWire.Packet.packet()) :: ExWire.Packet.handle_response() 59 | def handle(_packet = %__MODULE__{}) do 60 | _ = Logger.debug("[Packet] Received ping, responding pong.") 61 | 62 | {:send, %ExWire.Packet.Pong{}} 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/packet/pong.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.Pong do 2 | @moduledoc """ 3 | Pong is the response to a Ping message. 4 | 5 | ``` 6 | **Pong** `0x03` [] 7 | 8 | Reply to peer's `Ping` packet. 9 | ``` 10 | """ 11 | 12 | @behaviour ExWire.Packet 13 | 14 | @type t :: %__MODULE__{} 15 | 16 | defstruct [] 17 | 18 | @doc """ 19 | Given a Pong packet, serializes for transport over Eth Wire Protocol. 20 | 21 | ## Examples 22 | 23 | iex> %ExWire.Packet.Pong{} 24 | ...> |> ExWire.Packet.Pong.serialize 25 | [] 26 | """ 27 | @spec serialize(t) :: ExRLP.t() 28 | def serialize(_packet = %__MODULE__{}) do 29 | [] 30 | end 31 | 32 | @doc """ 33 | Given an RLP-encoded Pong packet from Eth Wire Protocol, 34 | decodes into a Pong struct. 35 | 36 | ## Examples 37 | 38 | iex> ExWire.Packet.Pong.deserialize([]) 39 | %ExWire.Packet.Pong{} 40 | """ 41 | @spec deserialize(ExRLP.t()) :: t 42 | def deserialize(rlp) do 43 | [] = rlp 44 | 45 | %__MODULE__{} 46 | end 47 | 48 | @doc """ 49 | Handles a Pong message. We should track the round-trip time since the 50 | corresponding Ping was sent to know how fast this peer is. 51 | 52 | ## Examples 53 | 54 | iex> ExWire.Packet.Pong.handle(%ExWire.Packet.Pong{}) 55 | :ok 56 | """ 57 | @spec handle(ExWire.Packet.packet()) :: ExWire.Packet.handle_response() 58 | def handle(_packet = %__MODULE__{}) do 59 | # TODO: Track RTT time 60 | 61 | :ok 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/packet/transactions.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.Transactions do 2 | @moduledoc """ 3 | Eth Wire Packet for communicating new transactions. 4 | 5 | ``` 6 | **Transactions** [`+0x02`: `P`, [`nonce`: `P`, `receivingAddress`: `B_20`, `value`: `P`, ...], ...] 7 | 8 | Specify (a) transaction(s) that the peer should make sure is included on 9 | its transaction queue. The items in the list (following the first item 0x12) 10 | are transactions in the format described in the main Ethereum specification. 11 | Nodes must not resend the same transaction to a peer in the same session. This 12 | packet must contain at least one (new) transaction. 13 | ``` 14 | """ 15 | 16 | require Logger 17 | 18 | @behaviour ExWire.Packet 19 | 20 | @type t :: %__MODULE__{ 21 | transactions: [any()] 22 | } 23 | 24 | defstruct [ 25 | :transactions 26 | ] 27 | 28 | @doc """ 29 | Given a Transactions packet, serializes for transport over Eth Wire Protocol. 30 | 31 | ## Examples 32 | 33 | iex> %ExWire.Packet.Transactions{ 34 | ...> transactions: [ 35 | ...> [1, 2, 3], 36 | ...> [4, 5, 6] 37 | ...> ] 38 | ...> } 39 | ...> |> ExWire.Packet.Transactions.serialize 40 | [ [1, 2, 3], [4, 5, 6] ] 41 | """ 42 | @spec serialize(t) :: ExRLP.t() 43 | def serialize(packet = %__MODULE__{}) do 44 | # TODO: Serialize accurately 45 | packet.transactions 46 | end 47 | 48 | @doc """ 49 | Given an RLP-encoded Transactions packet from Eth Wire Protocol, 50 | decodes into a Tranasctions struct. 51 | 52 | ## Examples 53 | 54 | iex> ExWire.Packet.Transactions.deserialize([ [1, 2, 3], [4, 5, 6] ]) 55 | %ExWire.Packet.Transactions{ 56 | transactions: [ 57 | [1, 2, 3], 58 | [4, 5, 6], 59 | ] 60 | } 61 | """ 62 | @spec deserialize(ExRLP.t()) :: t 63 | def deserialize(rlp) do 64 | # TODO: Deserialize from proper struct 65 | 66 | %__MODULE__{ 67 | transactions: rlp 68 | } 69 | end 70 | 71 | @doc """ 72 | Handles a Transactions message. We should try to add the transaction 73 | to a queue and process it. Or, right now, do nothing. 74 | 75 | ## Examples 76 | 77 | iex> %ExWire.Packet.Transactions{transactions: []} 78 | ...> |> ExWire.Packet.Transactions.handle() 79 | :ok 80 | """ 81 | @spec handle(ExWire.Packet.packet()) :: ExWire.Packet.handle_response() 82 | def handle(packet = %__MODULE__{}) do 83 | # TODO: Do. 84 | _ = 85 | Logger.debug(fn -> 86 | "[Packet] Peer sent #{Enum.count(packet.transactions)} transaction(s)." 87 | end) 88 | 89 | :ok 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/peer_supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.PeerSupervisor do 2 | @moduledoc """ 3 | The Peer Supervisor is responsible for maintaining a set of peer TCP connections. 4 | 5 | We should ask bootnodes for a set of potential peers via the Discovery Protocol, and then 6 | we can connect to those nodes. Currently, we just connect to the Bootnodes themselves. 7 | """ 8 | 9 | # TODO: We need to track and see which of these are up. We need to percolate messages on success. 10 | 11 | use Supervisor 12 | 13 | require Logger 14 | alias ExthCrypto.Math 15 | alias ExWire.Adapter.TCP 16 | alias ExWire.Struct.Endpoint 17 | alias ExWire.Struct.Peer 18 | alias ExWire.Sync 19 | 20 | @name __MODULE__ 21 | 22 | def start_link(_opts) do 23 | Supervisor.start_link(__MODULE__, :ok, name: @name) 24 | end 25 | 26 | def init(_args) do 27 | children = [ 28 | worker(TCP, [], restart: :transient) 29 | ] 30 | 31 | supervise(children, strategy: :simple_one_for_one) 32 | end 33 | 34 | @doc """ 35 | Sends a packet to all active TCP connections. This is useful when we want to, for instance, 36 | ask for a `GetBlockBody` from all peers for a given block hash. 37 | """ 38 | def send_packet(pid, packet) do 39 | # Send to all of the Supervisor's children... 40 | # ... not the best. 41 | 42 | for {_id, child, _type, _modules} <- Supervisor.which_children(pid) do 43 | # Children which are being restarted by not have a child_pid at this time. 44 | if is_pid(child), do: TCP.send_packet(child, packet) 45 | end 46 | end 47 | 48 | @doc """ 49 | Informs our peer supervisor a new neighbour that we should connect to. 50 | """ 51 | def connect(neighbour) do 52 | _ = 53 | Logger.debug(fn -> 54 | "[Peer Supervisor] Starting TCP connection to neighbour #{ 55 | neighbour.endpoint.ip |> Endpoint.ip_to_string() 56 | }:#{neighbour.endpoint.tcp_port} (#{neighbour.node |> Math.bin_to_hex()})" 57 | end) 58 | 59 | peer = Peer.from_neighbour(neighbour) 60 | 61 | Supervisor.start_child(@name, [:outbound, peer, [{:server, Sync}]]) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/struct/block.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.Block do 2 | @moduledoc """ 3 | A struct for storing blocks as they are transported over the Eth Wire Protocol. 4 | """ 5 | alias Blockchain.Transaction 6 | 7 | defstruct [ 8 | :transactions_list, 9 | :transactions, 10 | :ommers 11 | ] 12 | 13 | @type t :: %__MODULE__{ 14 | transactions: [Transaction.t()], 15 | ommers: [binary()] 16 | } 17 | 18 | @doc """ 19 | Given a Block, serializes for transport over Eth Wire Protocol. 20 | 21 | ## Examples 22 | 23 | iex> %ExWire.Struct.Block{transactions_list: [[<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>]], ommers: [<<1::256>>]} 24 | ...> |> ExWire.Struct.Block.serialize 25 | [[[<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>]], [<<1::256>>]] 26 | """ 27 | @spec serialize(t) :: ExRLP.t() 28 | def serialize(struct) do 29 | [ 30 | struct.transactions_list, 31 | struct.ommers 32 | ] 33 | end 34 | 35 | @doc """ 36 | Given an RLP-encoded block from Eth Wire Protocol, decodes into a Block struct. 37 | 38 | ## Examples 39 | 40 | iex> ExWire.Struct.Block.deserialize([[[<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>]], [<<1::256>>]]) 41 | %ExWire.Struct.Block{ 42 | transactions_list: [[<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, "hi", <<27>>, <<9>>, <<10>>]], 43 | transactions: [%Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1::160>>, value: 8, v: 27, r: 9, s: 10, data: "hi"}], 44 | ommers: [<<1::256>>] 45 | } 46 | """ 47 | @spec deserialize(ExRLP.t()) :: t 48 | def deserialize(rlp) do 49 | [ 50 | transactions_list, 51 | ommers 52 | ] = rlp 53 | 54 | %__MODULE__{ 55 | transactions_list: transactions_list, 56 | transactions: 57 | for( 58 | transaction_rlp <- transactions_list, 59 | do: Transaction.deserialize(transaction_rlp) 60 | ), 61 | ommers: ommers 62 | } 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /apps/ex_wire/lib/ex_wire/util/timestamp.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Util.Timestamp do 2 | @moduledoc """ 3 | Helper functions for getting current time. 4 | """ 5 | 6 | # seconds 7 | @expiration 20 8 | 9 | @doc """ 10 | Returns the current time as a unix epoch. 11 | """ 12 | @spec now() :: integer() 13 | def now do 14 | :os.system_time(:seconds) 15 | end 16 | 17 | @doc """ 18 | Returns the current time plus a global expiration. 19 | """ 20 | @spec soon() :: integer() 21 | def soon do 22 | now() + @expiration 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /apps/ex_wire/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ex_wire, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Elixir Client for Ethereum's RLPx, DevP2P and Eth Wire Protocol", 15 | package: [ 16 | maintainers: ["Mason Fischer", "Geoffrey Hayes", "Ayrat Badykov"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | elixirc_paths: elixirc_paths(Mix.env()), 21 | build_embedded: Mix.env() == :prod, 22 | deps: deps(), 23 | dialyzer: [ 24 | flags: [:underspecs, :unknown, :unmatched_returns], 25 | plt_add_apps: [:mix, :iex, :logger], 26 | plt_add_deps: :transitive 27 | ], 28 | preferred_cli_env: [ 29 | coveralls: :test, 30 | "coveralls.detail": :test, 31 | "coveralls.post": :test, 32 | "coveralls.html": :test, 33 | dialyzer: :test 34 | ], 35 | start_permanent: Mix.env() == :prod, 36 | test_coverage: [tool: ExCoveralls] 37 | ] 38 | end 39 | 40 | def application do 41 | [mod: {ExWire, []}, extra_applications: [:logger, :crypto, :inets, :xmerl, :inet_ext]] 42 | end 43 | 44 | defp elixirc_paths(:test), do: ["lib", "test/support"] 45 | defp elixirc_paths(_), do: ["lib"] 46 | 47 | # Run "mix help deps" to learn about dependencies. 48 | defp deps do 49 | [ 50 | # Umbrella 51 | {:blockchain, in_umbrella: true}, 52 | {:evm, in_umbrella: true}, 53 | {:ex_rlp, in_umbrella: true}, 54 | {:exth_crypto, in_umbrella: true}, 55 | 56 | # Libraries 57 | {:nat_upnp, "~> 0.1.0"}, 58 | 59 | # Common 60 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 61 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 62 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 63 | ] 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/adapters/udp_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Adapter.UDPTest do 2 | use ExUnit.Case 3 | doctest ExWire.Adapter.UDP 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/config_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.ConfigTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Config 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/crypto_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.CryptoTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Crypto 4 | alias ExthCrypto.Signature 5 | 6 | test "recover public key from signature" do 7 | {signature, _r, _s, recovery_id} = 8 | Signature.sign_digest("hi mom", ExthCrypto.Test.private_key(:key_a)) 9 | 10 | {:ok, public_key} = Signature.recover("hi mom", signature, recovery_id) 11 | 12 | assert public_key == ExthCrypto.Test.public_key(:key_a) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/framing/frame_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Framing.FrameTest do 2 | use ExUnit.Case, async: true 3 | alias ExWire.Framing.Frame 4 | alias ExthCrypto.AES 5 | alias ExthCrypto.Math 6 | alias ExthCrypto.Hash.Keccak 7 | 8 | test "mac encoder" do 9 | mac_secret = 10 | "2212767d793a7a3d66f869ae324dd11bd17044b82c9f463b8a541a4d089efec5" 11 | |> Math.hex_to_bin() 12 | 13 | input_1 = "12532abaec065082a3cf1da7d0136f15" |> Math.hex_to_bin() 14 | input_2 = "7e99f682356fdfbc6b67a9562787b18a" |> Math.hex_to_bin() 15 | expected_1 = "89464c6b04e7c99e555c81d3f7266a05" 16 | expected_2 = "85c070030589ef9c7a2879b3a8489316" 17 | 18 | mac_encoder = {ExthCrypto.AES, ExthCrypto.AES.block_size(), :ecb} 19 | 20 | assert expected_1 == 21 | ExthCrypto.Cipher.encrypt(input_1, mac_secret, mac_encoder) 22 | |> Binary.take(-16) 23 | |> Math.bin_to_hex() 24 | 25 | assert expected_2 == 26 | ExthCrypto.Cipher.encrypt(input_2, mac_secret, mac_encoder) 27 | |> Binary.take(-16) 28 | |> Math.bin_to_hex() 29 | end 30 | 31 | test "simple frame test read / write" do 32 | hash = 33 | <<1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 34 | 1, 1>> 35 | 36 | symmetric_key = Keccak.kec(<<>>) 37 | mac_secret = Keccak.kec(<<>>) 38 | ingress_mac = ExthCrypto.MAC.init(:fake, [hash]) 39 | egress_mac = ExthCrypto.MAC.init(:fake, [hash]) 40 | 41 | secrets = %ExWire.Framing.Secrets{ 42 | egress_mac: ingress_mac, 43 | ingress_mac: egress_mac, 44 | mac_encoder: {AES, AES.block_size(), :ecb}, 45 | mac_secret: mac_secret, 46 | encoder_stream: AES.stream_init(:ctr, symmetric_key, <<0::size(128)>>), 47 | decoder_stream: AES.stream_init(:ctr, symmetric_key, <<0::size(128)>>) 48 | } 49 | 50 | {frame, _updated_secrets} = Frame.frame(8, [1, 2, 3, 4], secrets) 51 | 52 | assert frame |> Math.bin_to_hex() == 53 | "00828ddae471818bb0bfa6b551d1cb4201010101010101010101010101010101ba628a4ba590cb43f7848f41c438288501010101010101010101010101010101" 54 | 55 | {:ok, packet_type, packet_data, frame_rest, _secrets} = 56 | Frame.unframe(frame <> "hello", secrets) 57 | 58 | assert frame_rest == "hello" 59 | assert packet_type == 8 60 | assert packet_data == [<<1>>, <<2>>, <<3>>, <<4>>] 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/framing/secrets_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Framing.SecretsTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Framing.Secrets 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handler/find_neighbours_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.FindNeighboursTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handler.FindNeighbours 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handler/ping_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handler.PingTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handler.Ping 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handler_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.HandlerTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handler 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handshake/eip_8_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handshake.EIP8Test do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handshake.EIP8 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handshake/struct/ack_resp_v4_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handshake.Struct.AckRespV4Test do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handshake.Struct.AckRespV4 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/handshake/struct/auth_msg_v4_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Handshake.Struct.AuthMsgV4Test do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Handshake.Struct.AuthMsgV4 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/message/find_neighbours_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.FindNeighboursTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Message.FindNeighbours 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/message/neighbours_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.NeighboursTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Message.Neighbours 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/message/ping_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.PingTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Message.Ping 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/message/pong_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Message.PongTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Message.Pong 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/message_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.MessageTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Message 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/network_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.NetworkTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Network 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/block_bodies_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.BlockBodiesTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.BlockBodies 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/block_headers_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.BlockHeadersTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.BlockHeaders 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/disconnect_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.DisconnectTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Disconnect 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/get_block_bodies_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.GetBlockBodiesTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.GetBlockBodies 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/get_block_headers_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.GetBlockHeadersTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.GetBlockHeaders 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/hello_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.HelloTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Hello 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/new_block_hashes_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.NewBlockHashesTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.NewBlockHashes 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/new_block_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.NewBlockTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.NewBlock 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/ping_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.PingTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Ping 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/pong_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.PongTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Pong 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/status_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.StatusTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Status 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet/transactions_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Packet.TransactionsTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet.Transactions 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/packet_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.PacketTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Packet 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/peer_supervisor_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.PeerSupervisorTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.PeerSupervisor 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/protocol_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.ProtocolTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Protocol 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/struct/block_queue_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.BlockQueueTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Struct.BlockQueue 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/struct/block_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.BlockTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Struct.Block 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/struct/endpoint_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.EndpointTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Struct.Endpoint 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/struct/neighbour_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.NeighbourTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Struct.Neighbour 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/struct/peer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Struct.PeerTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Struct.Peer 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/sync_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.SyncTest do 2 | use ExUnit.Case, async: true 3 | doctest ExWire.Sync 4 | end 5 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire/util/timestamp_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Util.TimestampTest do 2 | use ExUnit.Case, async: true 3 | alias ExWire.Util.Timestamp 4 | 5 | test "returns a valid timestamp" do 6 | assert Timestamp.now() > 0 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /apps/ex_wire/test/ex_wire_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExWireTest do 2 | use ExUnit.Case 3 | doctest ExWire 4 | 5 | alias ExWire.Config 6 | alias ExWire.Protocol 7 | alias ExWire.Message.Ping 8 | alias ExWire.Message.Pong 9 | alias ExWire.Message.Neighbours 10 | alias ExWire.Message.FindNeighbours 11 | alias ExWire.Util.Timestamp 12 | 13 | @them %ExWire.Struct.Endpoint{ 14 | ip: {0, 0, 0, 1}, 15 | udp_port: 30303, 16 | tcp_port: nil 17 | } 18 | 19 | @us %ExWire.Struct.Endpoint{ 20 | ip: {0, 0, 0, 2}, 21 | udp_port: 30303, 22 | tcp_port: nil 23 | } 24 | 25 | setup do 26 | Process.register(self(), :test) 27 | 28 | :ok 29 | end 30 | 31 | test "`ping` responds with a `pong`" do 32 | timestamp = Timestamp.now() 33 | 34 | ping = %Ping{ 35 | version: 4, 36 | to: @them, 37 | from: @us, 38 | timestamp: timestamp 39 | } 40 | 41 | hash = fake_send(ping, timestamp + 1) 42 | 43 | assert_receive_message(%Pong{ 44 | to: @us, 45 | hash: hash, 46 | timestamp: timestamp + 1 47 | }) 48 | end 49 | 50 | test "`find_neighbours` responds with `neighbors`" do 51 | timestamp = Timestamp.now() 52 | 53 | find_neighbours = %FindNeighbours{ 54 | target: <<1>>, 55 | timestamp: timestamp 56 | } 57 | 58 | fake_send(find_neighbours, timestamp + 1) 59 | 60 | assert_receive_message(%Neighbours{ 61 | nodes: [], 62 | timestamp: timestamp + 1 63 | }) 64 | end 65 | 66 | def assert_receive_message(message) do 67 | message = message |> Protocol.encode(Config.private_key()) 68 | assert_receive(%{data: ^message, to: @us}) 69 | end 70 | 71 | def fake_send(message, timestamp) do 72 | encoded_message = Protocol.encode(message, Config.private_key()) 73 | 74 | GenServer.cast( 75 | :test_network_adapter, 76 | { 77 | :fake_recieve, 78 | %{ 79 | data: encoded_message, 80 | remote_host: @us, 81 | timestamp: timestamp 82 | } 83 | } 84 | ) 85 | 86 | <> = encoded_message 87 | 88 | hash 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /apps/ex_wire/test/integration/wire_to_wire_test.exs: -------------------------------------------------------------------------------- 1 | defmodule WireToWireTest do 2 | @moduledoc """ 3 | This test starts a server and connects a peer to it. It 4 | checks that a PING / PONG can be successfully communicated. 5 | """ 6 | 7 | use ExUnit.Case, async: true 8 | alias ExWire.Util.Timestamp 9 | 10 | @moduletag integration: true 11 | @localhost {127, 0, 0, 1} 12 | @us_port 8888 13 | @them_port 9999 14 | 15 | setup do 16 | server = 17 | ExWire.start(nil, 18 | network_adapter: ExWire.Adapter.UDP, 19 | port: @them_port, 20 | name: __MODULE__.Server 21 | ) 22 | 23 | remote_host = %ExWire.Struct.Endpoint{ 24 | ip: @localhost, 25 | udp_port: @them_port 26 | } 27 | 28 | {:ok, %{server: server, remote_host: remote_host}} 29 | end 30 | 31 | def receive(inbound_message, pid) do 32 | send(pid, {:inbound_message, inbound_message}) 33 | end 34 | 35 | test "ping / pong", %{remote_host: remote_host} do 36 | {:ok, client_pid} = 37 | ExWire.Adapter.UDP.start_link({__MODULE__, [self()]}, @us_port, __MODULE__.Test) 38 | 39 | timestamp = Timestamp.now() 40 | 41 | ping = %ExWire.Message.Ping{ 42 | version: 1, 43 | from: %ExWire.Struct.Endpoint{ip: @localhost, tcp_port: nil, udp_port: @us_port}, 44 | to: %ExWire.Struct.Endpoint{ip: @localhost, tcp_port: nil, udp_port: @them_port}, 45 | timestamp: timestamp 46 | } 47 | 48 | ExWire.Network.send(ping, client_pid, remote_host) 49 | 50 | receive do 51 | {:inbound_message, inbound_message} -> 52 | message = decode_message(inbound_message) 53 | 54 | assert message.__struct__ == ExWire.Message.Pong 55 | 56 | assert message.to == %ExWire.Struct.Endpoint{ 57 | ip: {127, 0, 0, 1}, 58 | tcp_port: nil, 59 | udp_port: @us_port 60 | } 61 | 62 | assert message.timestamp >= timestamp 63 | after 64 | 2_000 -> 65 | raise "Expected pong, but did not receive before timeout." 66 | end 67 | end 68 | 69 | def decode_message(%ExWire.Network.InboundMessage{ 70 | data: << 71 | _hash::size(256), 72 | _signature::size(512), 73 | _recovery_id::integer-size(8), 74 | type::integer-size(8), 75 | data::bitstring 76 | >> 77 | }) do 78 | ExWire.Message.decode(type, data) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /apps/ex_wire/test/support/ex_wire/adapter/test.ex: -------------------------------------------------------------------------------- 1 | defmodule ExWire.Adapter.Test do 2 | use GenServer 3 | 4 | def start_link({network, network_args}, port) do 5 | GenServer.start_link(__MODULE__, %{network: network, network_args: network_args, port: port}) 6 | end 7 | 8 | def init(state) do 9 | Process.register(self(), :test_network_adapter) 10 | {:ok, state} 11 | end 12 | 13 | def handle_cast({:listen, callback}, state) do 14 | state = Map.put(state, :callback, callback) 15 | {:noreply, state} 16 | end 17 | 18 | def handle_cast({:send, data}, state) do 19 | send(:test, data) 20 | {:noreply, state} 21 | end 22 | 23 | def handle_cast( 24 | {:fake_recieve, 25 | %{ 26 | data: data, 27 | remote_host: remote_host, 28 | timestamp: timestamp 29 | }}, 30 | state = %{network: network} 31 | ) do 32 | network.receive( 33 | %ExWire.Network.InboundMessage{ 34 | data: data, 35 | server_pid: self(), 36 | remote_host: remote_host, 37 | timestamp: timestamp 38 | }, 39 | nil 40 | ) 41 | 42 | {:noreply, state} 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /apps/ex_wire/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/exth_crypto/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | * 0.1.5 2 | * Upgraded libsecp256k1 dependency 3 | * 0.1.4 4 | * Add MAC stream. 5 | * Improve signature recovery algorithms 6 | * Add ECB mode for AES 7 | # 0.1.3 8 | * Adds additional test keys pairs and `key_pair` as a type. 9 | # 0.1.2 10 | * Refactor key functions to new module 11 | # 0.1.1 12 | * Minor updates 13 | # 0.1.0 14 | * First pass, includes ECIES implementation 15 | -------------------------------------------------------------------------------- /apps/exth_crypto/README.md: -------------------------------------------------------------------------------- 1 | # ExthCrypto [![CircleCI](https://circleci.com/gh/exthereum/exth_crypto.svg?style=svg)](https://circleci.com/gh/exthereum/exth_crypto) 2 | 3 | ExthCrypto handles the majority of cryptographic operations for Exthereum. The goal of this project is to give each Exthereum project a common set of cryptographic functions where the backends can be swapped out as need be. Additionally, more complicated protocols (such as ECIES) can be implemented and tested in this project. 4 | 5 | Note: we opt, whenever possible, to use erlang core or open-source implementations for all functions. The goal of this project is to create a consistent API for cryptographic functions that can be referenced from Exthereum projects. The goal of this project is not to re-write such functions in native erlang or Elixir. 6 | 7 | We currently support: 8 | 9 | * AES symmetric encryption in block mode and (simplified) stream mode 10 | * Elliptic Curve Diffie Hellman (ECDH) key exchange 11 | * ECIES perfect-forward secret generation 12 | * SHA1, SHA2 and Keccak one-way cryptographic hash functions 13 | * NIST-SP-800-56 key derivation function 14 | * HMAC with SHA1, SHA2 or Keccak 15 | * Elliptic Curve Digital Signature Algorithm (ECDSA) with public key recovery 16 | 17 | ## Installation 18 | 19 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed 20 | by adding `exth_crypto` to your list of dependencies in `mix.exs`: 21 | 22 | ```elixir 23 | def deps do 24 | [{:exth_crypto, "~> 0.1.6"}] 25 | end 26 | ``` 27 | 28 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 29 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 30 | be found at [https://hexdocs.pm/exth_crypto](https://hexdocs.pm/exth_crypto). 31 | 32 | -------------------------------------------------------------------------------- /apps/exth_crypto/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :exth_crypto, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:exth_crypto, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/ecies/ecdh.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ECDH do 2 | @moduledoc """ 3 | Implements Elliptic Curve Diffie-Hellman, as it pertains to Exthereum. 4 | """ 5 | 6 | @default_curve :secp256k1 7 | 8 | @doc """ 9 | Generates a new key_pair for elliptic curve diffie-hellman. 10 | 11 | These keys should be used as ephemeral keys in the key-exchange protocol. 12 | 13 | ## Examples 14 | 15 | iex> {public_key, private_key} = ExthCrypto.ECIES.ECDH.new_ecdh_key_pair() 16 | iex> byte_size(public_key) 17 | 65 18 | iex> byte_size(private_key) 19 | 32 20 | iex> {public_key, private_key} == :crypto.generate_key(:ecdh, :secp256k1, private_key) 21 | true 22 | """ 23 | @spec new_ecdh_key_pair(ExthCrypto.named_curve()) :: Key.key_pair() 24 | def new_ecdh_key_pair(curve \\ @default_curve) when is_atom(curve) do 25 | :crypto.generate_key(:ecdh, curve) 26 | end 27 | 28 | @doc """ 29 | Generates a static shared secret between two parties according to the 30 | protocol for elliptic curve diffie hellman. 31 | 32 | ## Examples 33 | 34 | iex> ExthCrypto.ECIES.ECDH.generate_shared_secret(ExthCrypto.Test.private_key(:key_b), ExthCrypto.Test.public_key(:key_a), :secp256k1) 35 | <<68, 139, 102, 172, 32, 159, 198, 236, 33, 216, 132, 22, 62, 46, 163, 215, 53, 40, 177, 14, 51, 94, 155, 151, 21, 226, 9, 254, 153, 48, 112, 226>> 36 | 37 | iex> ExthCrypto.ECIES.ECDH.generate_shared_secret(ExthCrypto.Test.private_key(:key_a), ExthCrypto.Test.public_key(:key_b), :secp256k1) 38 | <<68, 139, 102, 172, 32, 159, 198, 236, 33, 216, 132, 22, 62, 46, 163, 215, 53, 40, 177, 14, 51, 94, 155, 151, 21, 226, 9, 254, 153, 48, 112, 226>> 39 | """ 40 | @spec generate_shared_secret( 41 | Key.private_key(), 42 | Key.public_key(), 43 | ExthCrypto.named_curve() 44 | ) :: binary() 45 | def generate_shared_secret(local_private_key, remote_public_key, curve \\ @default_curve) 46 | when is_atom(curve) do 47 | :crypto.compute_key(:ecdh, remote_public_key, local_private_key, curve) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/exth_crypto.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto do 2 | @moduledoc """ 3 | Handles the general crypto stuff. 4 | """ 5 | 6 | @type curve :: nil 7 | @type curve_params :: nil 8 | @type named_curve :: :crypto.ec_named_curve() 9 | end 10 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/hash/fake.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.Fake do 2 | @moduledoc """ 3 | Simple fake hash that basically just returns its own input. 4 | 5 | Gasp, that's reversable! 6 | """ 7 | 8 | @type fake_mac :: {:fake_mac, binary()} 9 | 10 | @doc """ 11 | Initializes a new Fake mac stream. 12 | 13 | ## Examples 14 | 15 | iex> fake_mac = ExthCrypto.Hash.Fake.init_mac("abc") 16 | iex> is_nil(fake_mac) 17 | false 18 | """ 19 | @spec init_mac(binary()) :: fake_mac 20 | def init_mac(initial) do 21 | {:fake_mac, initial} 22 | end 23 | 24 | @doc """ 25 | Updates a given Fake mac stream, which is, do nothing. 26 | 27 | ## Examples 28 | 29 | iex> fake_mac = ExthCrypto.Hash.Fake.init_mac("init") 30 | ...> |> ExthCrypto.Hash.Fake.update_mac("data") 31 | iex> is_nil(fake_mac) 32 | false 33 | """ 34 | @spec update_mac(fake_mac, binary()) :: fake_mac 35 | def update_mac({:fake_mac, mac}, _data) do 36 | {:fake_mac, mac} 37 | end 38 | 39 | @doc """ 40 | Finalizes a given Fake mac stream to produce the current 41 | hash. 42 | 43 | ## Examples 44 | 45 | iex> ExthCrypto.Hash.Fake.init_mac("abc") 46 | ...> |> ExthCrypto.Hash.Fake.update_mac("def") 47 | ...> |> ExthCrypto.Hash.Fake.final_mac() 48 | "abc" 49 | """ 50 | @spec final_mac(fake_mac) :: binary() 51 | def final_mac({:fake_mac, mac}) do 52 | mac 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/hash/hash.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash do 2 | @moduledoc """ 3 | A variety of functions to handle one-way hashing functions as 4 | defined by Ethereum. 5 | """ 6 | 7 | alias ExthCrypto.Hash.Keccak 8 | alias ExthCrypto.Hash.SHA 9 | 10 | @type hash_algorithm :: 11 | :md4 12 | | :md5 13 | | :sha 14 | | :sha224 15 | | :sha256 16 | | :sha384 17 | | :sha3_224 18 | | :sha3_256 19 | | :sha3_384 20 | | :sha3_512 21 | | :sha512 22 | @type hash_algorithms :: [hash_algorithm] 23 | @type hash :: binary() 24 | @type hasher :: (binary() -> binary()) 25 | @type hash_type :: {hasher, integer() | nil, integer()} 26 | 27 | @doc """ 28 | Returns a list of supported hash algorithms. 29 | """ 30 | @hash_algorithms [ 31 | :md4, 32 | :md5, 33 | :sha, 34 | :sha224, 35 | :sha256, 36 | :sha384, 37 | :sha512, 38 | :sha3_224, 39 | :sha3_256, 40 | :sha3_384, 41 | :sha3_512 42 | ] 43 | @spec hash_algorithms() :: nonempty_list(hash_algorithm) 44 | def hash_algorithms, do: @hash_algorithms 45 | 46 | @doc """ 47 | The SHA1 hasher. 48 | """ 49 | @spec sha1() :: hash_type 50 | def sha1, do: {&SHA.sha1/1, nil, 20} 51 | 52 | @doc """ 53 | The KECCAK hasher, as defined by Ethereum. 54 | """ 55 | @spec kec() :: hash_type 56 | def kec, do: {&Keccak.kec/1, nil, 256} 57 | 58 | @doc """ 59 | Runs the specified hash type on the given data. 60 | ## Examples 61 | iex> ExthCrypto.Hash.hash("hello world", ExthCrypto.Hash.kec) |> ExthCrypto.Math.bin_to_hex 62 | "47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" 63 | iex> ExthCrypto.Hash.hash("hello world", ExthCrypto.Hash.sha1) |> ExthCrypto.Math.bin_to_hex 64 | "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed" 65 | """ 66 | @spec hash(iodata(), hash_type) :: hash 67 | def hash(data, {hash_fun, _, _}) do 68 | hash_fun.(data) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/hash/keccak.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.Keccak do 2 | @moduledoc """ 3 | Simple wrapper for Keccak function for Ethereum. 4 | 5 | Note: This module defines KECCAK as defined by Ethereum, which differs slightly 6 | than that assigned as the new SHA-3 variant. For SHA-3, a few constants have 7 | been changed prior to adoption by NIST, but after adoption by Ethereum. 8 | """ 9 | 10 | @type keccak_hash :: ExthCrypto.Hash.hash() 11 | @type keccak_mac :: {atom(), binary()} 12 | 13 | @doc """ 14 | Returns the keccak sha256 of a given input. 15 | 16 | ## Examples 17 | 18 | iex> ExthCrypto.Hash.Keccak.kec("hello world") 19 | <<71, 23, 50, 133, 168, 215, 52, 30, 94, 151, 47, 198, 119, 40, 99, 20 | 132, 248, 2, 248, 239, 66, 165, 236, 95, 3, 187, 250, 37, 76, 176, 21 | 31, 173>> 22 | 23 | iex> ExthCrypto.Hash.Keccak.kec(<<0x01, 0x02, 0x03>>) 24 | <<241, 136, 94, 218, 84, 183, 160, 83, 49, 140, 212, 30, 32, 147, 34, 25 | 13, 171, 21, 214, 83, 129, 177, 21, 122, 54, 51, 168, 59, 253, 92, 26 | 146, 57>> 27 | """ 28 | @spec kec(binary()) :: keccak_hash 29 | def kec(data) do 30 | :keccakf1600.sha3_256(data) 31 | end 32 | 33 | @doc """ 34 | Initializes a new Keccak mac stream. 35 | 36 | ## Examples 37 | 38 | iex> keccak_mac = ExthCrypto.Hash.Keccak.init_mac() 39 | iex> is_nil(keccak_mac) 40 | false 41 | """ 42 | @spec init_mac() :: keccak_mac 43 | def init_mac() do 44 | :keccakf1600.init(:sha3_256) 45 | end 46 | 47 | @doc """ 48 | Updates a given Keccak mac stream with the given 49 | secret and data, returning a new mac stream. 50 | 51 | ## Examples 52 | 53 | iex> keccak_mac = ExthCrypto.Hash.Keccak.init_mac() 54 | ...> |> ExthCrypto.Hash.Keccak.update_mac("data") 55 | iex> is_nil(keccak_mac) 56 | false 57 | """ 58 | @spec update_mac(keccak_mac, binary()) :: keccak_mac 59 | def update_mac(mac, data) do 60 | :keccakf1600.update(mac, data) 61 | end 62 | 63 | @doc """ 64 | Finalizes a given Keccak mac stream to produce the current 65 | hash. 66 | 67 | ## Examples 68 | 69 | iex> ExthCrypto.Hash.Keccak.init_mac() 70 | ...> |> ExthCrypto.Hash.Keccak.update_mac("data") 71 | ...> |> ExthCrypto.Hash.Keccak.final_mac() 72 | ...> |> ExthCrypto.Math.bin_to_hex 73 | "8f54f1c2d0eb5771cd5bf67a6689fcd6eed9444d91a39e5ef32a9b4ae5ca14ff" 74 | """ 75 | @spec final_mac(keccak_mac) :: keccak_hash 76 | def final_mac(mac) do 77 | :keccakf1600.final(mac) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/hash/sha.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.SHA do 2 | @moduledoc """ 3 | Helper functions for running Secure Hash Algorithm (SHA). 4 | """ 5 | 6 | @doc """ 7 | Computes the SHA-1 of a given input. 8 | 9 | ## Examples 10 | 11 | iex> ExthCrypto.Hash.SHA.sha1("The quick brown fox jumps over the lazy dog") |> ExthCrypto.Math.bin_to_hex 12 | "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" 13 | 14 | iex> ExthCrypto.Hash.SHA.sha1("") |> ExthCrypto.Math.bin_to_hex 15 | "da39a3ee5e6b4b0d3255bfef95601890afd80709" 16 | """ 17 | @spec sha1(binary()) :: <<_::160>> 18 | def sha1(data) do 19 | :crypto.hash(:sha, data) 20 | end 21 | 22 | @doc """ 23 | Computes the SHA-2 of a given input outputting 256 bits. 24 | 25 | ## Examples 26 | 27 | iex> ExthCrypto.Hash.SHA.sha256("") |> ExthCrypto.Math.bin_to_hex 28 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 29 | """ 30 | @spec sha256(binary()) :: <<_::256>> 31 | def sha256(data) do 32 | :crypto.hash(:sha256, data) 33 | end 34 | 35 | @doc """ 36 | Computes the SHA-2 of a given input outputting 384 bits. 37 | 38 | ## Examples 39 | 40 | iex> ExthCrypto.Hash.SHA.sha384("") |> ExthCrypto.Math.bin_to_hex 41 | "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" 42 | """ 43 | @spec sha384(binary()) :: <<_::384>> 44 | def sha384(data) do 45 | :crypto.hash(:sha384, data) 46 | end 47 | 48 | @doc """ 49 | Computes the SHA-2 of a given input outputting 512 bits. 50 | 51 | ## Examples 52 | 53 | iex> ExthCrypto.Hash.SHA.sha512("") |> ExthCrypto.Math.bin_to_hex 54 | "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" 55 | """ 56 | @spec sha512(binary()) :: <<_::512>> 57 | def sha512(data) do 58 | :crypto.hash(:sha512, data) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/key/key.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Key do 2 | @moduledoc """ 3 | Simple functions to interact with keys. 4 | """ 5 | 6 | @type symmetric_key :: binary() 7 | @type public_key_der :: binary() 8 | @type public_key :: <<_::8, _::_*8>> 9 | @type private_key_der :: binary() 10 | @type private_key :: binary() 11 | @type key_pair :: {public_key, private_key} 12 | 13 | @doc """ 14 | Converts a key from der to raw format. 15 | 16 | ## Examples 17 | 18 | iex> ExthCrypto.Key.der_to_raw(<<0x04, 0x01>>) 19 | <<0x01>> 20 | """ 21 | @spec der_to_raw(public_key_der) :: public_key 22 | def der_to_raw(public_key_der) do 23 | <<0x04, public_key::binary()>> = public_key_der 24 | 25 | public_key 26 | end 27 | 28 | @doc """ 29 | Converts a key from raw to der format. 30 | 31 | ## Examples 32 | 33 | iex> ExthCrypto.Key.der_to_raw(<<0x04, 0x01>>) 34 | <<0x01>> 35 | """ 36 | @spec raw_to_der(public_key) :: public_key_der 37 | def raw_to_der(public_key) do 38 | <<0x04>> <> public_key 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /apps/exth_crypto/lib/mac/mac.ex: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MAC do 2 | @moduledoc """ 3 | Wrapper for erlang's built-in HMAC (Hash-based Message Authentication Code) 4 | and CMAC (Cipher-based Message Authentication Code) routines, to be used for Exthereum. 5 | """ 6 | 7 | alias ExthCrypto.Hash 8 | 9 | @type mac :: binary() 10 | @type mac_type :: :kec | :fake 11 | @type mac_inst :: {mac_type, any()} 12 | 13 | defp mac_module(:kec), do: ExthCrypto.Hash.Keccak 14 | defp mac_module(:fake), do: ExthCrypto.Hash.Fake 15 | 16 | @doc """ 17 | Calcluates the MAC of a given set of input. 18 | 19 | ## Examples 20 | 21 | iex> ExthCrypto.MAC.mac("The quick brown fox jumps over the lazy dog", "key", :sha256) |> ExthCrypto.Math.bin_to_hex 22 | "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8" 23 | 24 | iex> ExthCrypto.MAC.mac("The quick brown fox jumps over the lazy dog", "key", :sha256, 8) 25 | <<247, 188, 131, 244, 48, 83, 132, 36>> 26 | """ 27 | @spec mac(iodata(), iodata(), Hash.hash_algorithm(), integer() | nil) :: mac 28 | def mac(data, key, hash_algorithm, length \\ nil) when is_atom(hash_algorithm) do 29 | case length do 30 | nil -> :crypto.hmac(hash_algorithm, key, data) 31 | _ -> :crypto.hmac(hash_algorithm, key, data, length) 32 | end 33 | 34 | # TODO: Implement CMAC 35 | end 36 | 37 | @doc """ 38 | Initializes a new mac of given type with given args. 39 | """ 40 | @spec init(mac_type) :: mac_inst 41 | def init(mac_type, args \\ []) do 42 | {mac_type, apply(mac_module(mac_type), :init_mac, args)} 43 | end 44 | 45 | @doc """ 46 | Updates a given mac stream with the given secret and data, returning a new mac stream. 47 | 48 | ## Examples 49 | 50 | iex> mac = ExthCrypto.MAC.init(:kec) 51 | ...> |> ExthCrypto.MAC.update("data") 52 | iex> is_nil(mac) 53 | false 54 | """ 55 | @spec update(mac_inst, binary()) :: mac_inst 56 | def update({mac_type, mac}, data) do 57 | {mac_type, mac_module(mac_type).update_mac(mac, data)} 58 | end 59 | 60 | @doc """ 61 | Finalizes a given mac stream to produce the current hash. 62 | 63 | ## Examples 64 | 65 | iex> ExthCrypto.MAC.init(:kec) 66 | ...> |> ExthCrypto.MAC.update("data") 67 | ...> |> ExthCrypto.MAC.final() 68 | ...> |> ExthCrypto.Math.bin_to_hex 69 | "8f54f1c2d0eb5771cd5bf67a6689fcd6eed9444d91a39e5ef32a9b4ae5ca14ff" 70 | 71 | iex> ExthCrypto.MAC.init(:fake, ["jedi"]) 72 | ...> |> ExthCrypto.MAC.update(" ") 73 | ...> |> ExthCrypto.MAC.update("knight") 74 | ...> |> ExthCrypto.MAC.final() 75 | "jedi" 76 | """ 77 | @spec final(mac_inst) :: binary() 78 | def final({mac_type, mac}) do 79 | mac_module(mac_type).final_mac(mac) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /apps/exth_crypto/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :exth_crypto, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Exthereum's Crypto Suite.", 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Mason Fischer"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | preferred_cli_env: [ 23 | coveralls: :test, 24 | "coveralls.detail": :test, 25 | "coveralls.post": :test, 26 | "coveralls.html": :test, 27 | dialyzer: :test 28 | ], 29 | start_permanent: Mix.env() == :prod, 30 | test_coverage: [tool: ExCoveralls] 31 | ] 32 | end 33 | 34 | # Configuration for the OTP application 35 | # 36 | # Type "mix help compile.app" for more information 37 | def application do 38 | # Specify extra applications you'll use from Erlang/Elixir 39 | [extra_applications: [:logger]] 40 | end 41 | 42 | # Run "mix help deps" to learn about dependencies. 43 | defp deps do 44 | [ 45 | # Umbrella 46 | 47 | # Libraries 48 | {:binary, "~> 0.0.4"}, 49 | {:keccakf1600, "~> 2.0.0", hex: :keccakf1600_orig}, 50 | {:libsecp256k1, "~> 0.1.9"}, 51 | 52 | # Common 53 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 54 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 55 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 56 | ] 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/aes/aes_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.AESTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.AES 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/cipher_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.CipherTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Cipher 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/ecies/ecdh_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ECDHTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES.ECDH 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/ecies/ecies_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIESTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/ecies/parameters_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.ECIES.ParametersTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.ECIES.Parameters 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/exth_crypto_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCryptoTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto 4 | 5 | test "the truth" do 6 | assert 1 + 1 == 2 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/hash/fake_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.FakeTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.Fake 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/hash/keccak_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.KeccakTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.Keccak 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/hash/sha_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.Hash.SHATest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash.SHA 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/hash_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.HashTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Hash 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/kdf/nist_sp_800_65_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.KDF.NistSp80056Test do 2 | use ExUnit.Case 3 | doctest ExthCrypto.KDF.NistSp80056 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/mac/mac_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MACTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.MAC 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/math/math_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.MathTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Math 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/signature/signature_test.exs: -------------------------------------------------------------------------------- 1 | defmodule ExthCrypto.SignatureTest do 2 | use ExUnit.Case 3 | doctest ExthCrypto.Signature 4 | end 5 | -------------------------------------------------------------------------------- /apps/exth_crypto/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/hex_prefix/README.md: -------------------------------------------------------------------------------- 1 | # HexPrefix [![CircleCI](https://circleci.com/gh/exthereum/hex_prefix.svg?style=svg)](https://circleci.com/gh/exthereum/hex_prefix) 2 | 3 | Elixir implementation of Ethereum's Hex Prefix encoding 4 | 5 | The encoding's specification can be found in [the yellow paper](http://yellowpaper.io/) or in the [ethereum wiki](https://github.com/ethereum/wiki/wiki/RLP) under Appendix C. 6 | 7 | ## Installation 8 | 9 | The easiest way to add HexPrefix to your project is by [using Mix](http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). 10 | 11 | Add `:hex_prefix` as a dependency to your project's `mix.exs`: 12 | 13 | ```elixir 14 | defp deps do 15 | [ 16 | {:hex_prefix, "~> 0.1.0"} 17 | ] 18 | end 19 | ``` 20 | 21 | And run: 22 | 23 | $ mix deps.get 24 | 25 | ## Basic Usage 26 | 27 | Use `HexPrefix.encode/1` to encode a list of nibbles using hex-prefix notation. 28 | 29 | ```elixir 30 | ## Examples 31 | 32 | iex> HexPrefix.encode({[0xa, 0xb, 0xc, 0xd], false}) 33 | <<0, 171, 205>> 34 | 35 | iex> HexPrefix.encode({[0xa, 0xb, 0xc, 0xd], true}) 36 | <<32, 171, 205>> 37 | 38 | iex> HexPrefix.encode({[0x09, 0xa, 0xb, 0xc, 0xd], false}) 39 | <<25, 171, 205>> 40 | ``` 41 | 42 | Use `HexPrefix.decode/1` to decode a binary encoded via hex-prefix notation. 43 | 44 | ```elixir 45 | ## Examples 46 | 47 | iex> HexPrefix.decode(<<0, 171, 205>>) 48 | {[0xa, 0xb, 0xc, 0xd], false} 49 | 50 | iex> HexPrefix.decode(<<32, 171, 205>>) 51 | {[0xa, 0xb, 0xc, 0xd], true} 52 | 53 | iex> HexPrefix.decode(<<25, 171, 205>>) 54 | {[0x09, 0xa, 0xb, 0xc, 0xd], false} 55 | ``` 56 | 57 | ## Contributing 58 | 59 | 1. [Fork it!](https://github.com/exthereum/hex_prefix/fork) 60 | 2. Create your feature branch (`git checkout -b my-new-feature`) 61 | 3. Commit your changes (`git commit -am 'Add some feature'`) 62 | 4. Push to the branch (`git push origin my-new-feature`) 63 | 5. Create new Pull Request 64 | 65 | ## Author 66 | 67 | Geoffrey Hayes (@hayesgm) 68 | Ayrat Badykov (@ayrat555) 69 | 70 | ## License 71 | 72 | HexPrefix is released under the MIT License. See the LICENSE file for further details. 73 | -------------------------------------------------------------------------------- /apps/hex_prefix/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /apps/hex_prefix/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule HexPrefix.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :hex_prefix, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Ethereum's Hex Prefix encoding", 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Ayrat Badykov"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | preferred_cli_env: [ 23 | coveralls: :test, 24 | "coveralls.detail": :test, 25 | "coveralls.post": :test, 26 | "coveralls.html": :test, 27 | dialyzer: :test 28 | ], 29 | start_permanent: Mix.env() == :prod, 30 | test_coverage: [tool: ExCoveralls] 31 | ] 32 | end 33 | 34 | # Configuration for the OTP application 35 | # 36 | # Type `mix help compile.app` for more information 37 | def application do 38 | [applications: [:logger]] 39 | end 40 | 41 | # Run "mix help deps" to learn about dependencies. 42 | defp deps do 43 | [ 44 | # Umbrella 45 | 46 | # Libaries 47 | 48 | # Common 49 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 50 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 51 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 52 | ] 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /apps/hex_prefix/test/hex_prefix_test.exs: -------------------------------------------------------------------------------- 1 | defmodule HexPrefixTest do 2 | use ExUnit.Case, async: true 3 | doctest HexPrefix 4 | 5 | test "encode then decode - no terminator" do 6 | assert {[0x01, 0x02], false} |> HexPrefix.encode() |> HexPrefix.decode() == {[1, 2], false} 7 | end 8 | 9 | test "encode then decode - terminator" do 10 | assert {[0x01, 0x02], true} |> HexPrefix.encode() |> HexPrefix.decode() == {[1, 2], true} 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /apps/hex_prefix/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.2.7 2 | * Update ex_rlp to 0.3.0 3 | # 0.2.6 4 | * Don't raise on key not found 5 | # 0.2.5 6 | * Add back empty trie function :( 7 | # 0.2.4 8 | * Remove empty trie function 9 | # 0.2.3 10 | * Add key deletion and tie out rest of tests 11 | # 0.2.2 12 | * Add typespec for an empty trie. 13 | # 0.2.1 14 | * Expand key typespec to allow any size binary keys. 15 | # 0.2.0 16 | * Breaking change to upgrade to different implementation. Please read docs for full set of exposed APIs. 17 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/README.md: -------------------------------------------------------------------------------- 1 | # MerklePatriciaTree [![CircleCI](https://circleci.com/gh/exthereum/merkle_patricia_tree.svg?style=svg)](https://circleci.com/gh/exthereum/merkle_patricia_tree) 2 | 3 | Elixir implementation of Ethereum's Merkle Patricia Tries. 4 | 5 | The encoding's specification can be found in [the yellow paper](http://yellowpaper.io/) or in the [ethereum wiki](https://github.com/ethereum/wiki/wiki/RLP) under Appendix D. 6 | 7 | The modified patricia merkle trie allows arbitrary storage of key, value pairs with the benefits of a merkle trie in O(n*log(n)) time for insert, lookup and delete. 8 | 9 | [This diagram](https://i.stack.imgur.com/YZGxe.png) is also very helpful in understanding these tries. 10 | 11 | ## Installation 12 | 13 | The easiest way to add MerklePatriciaTree to your project is by [using Mix](http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). 14 | 15 | Add `:merkle_patricia_tree` as a dependency to your project's `mix.exs`: 16 | 17 | ```elixir 18 | defp deps do 19 | [ 20 | {:merkle_patricia_tree, "~> 0.2.7"} 21 | ] 22 | end 23 | ``` 24 | 25 | And run: 26 | 27 | $ mix deps.get 28 | 29 | ## Basic Usage 30 | 31 | Use the `MerklePatriciaTree` module to create and build merkle patricia tries. You will be required to choose 32 | a storage database, and we currently support `:ets` and `:leveldb`. The follow example illustrates how to 33 | create an update a trie. 34 | 35 | ```elixir 36 | ## Examples 37 | 38 | iex> trie = 39 | ...> MerklePatriciaTree.Test.random_ets_db() 40 | ...> |> MerklePatriciaTree.Trie.new() 41 | ...> |> MerklePatriciaTree.Trie.update(<<0x01::4, 0x02::4>>, "wee") 42 | ...> |> MerklePatriciaTree.Trie.update(<<0x01::4, 0x02::4, 0x03::4>>, "cool") 43 | iex> trie_2 = MerklePatriciaTree.Trie.update(trie, <<0x01::4, 0x02::4, 0x03::4>>, "cooler") 44 | iex> MerklePatriciaTree.Trie.get(trie, <<0x01::4, 0x02::4, 0x03::4>>) 45 | "cool" 46 | iex> MerklePatriciaTree.Trie.get(trie_2, <<0x01::4>>) 47 | nil 48 | iex> MerklePatriciaTree.Trie.get(trie_2, <<0x01::4, 0x02::4>>) 49 | "wee" 50 | iex> MerklePatriciaTree.Trie.get(trie_2, <<0x01::4, 0x02::4, 0x03::4>>) 51 | "cooler" 52 | iex> MerklePatriciaTree.Trie.get(trie_2, <<0x01::4, 0x02::4, 0x03::4, 0x04::4>>) 53 | nil 54 | ``` 55 | 56 | ## Contributing 57 | 58 | 1. [Fork it!](https://github.com/exthereum/merkle_patricia_trie/fork) 59 | 2. Create your feature branch (`git checkout -b my-new-feature`) 60 | 3. Commit your changes (`git commit -am 'Add some feature'`) 61 | 4. Push to the branch (`git push origin my-new-feature`) 62 | 5. Create new Pull Request 63 | 64 | ## Author 65 | 66 | Geoffrey Hayes (@hayesgm) 67 | Ayrat Badykov (@ayrat555) 68 | 69 | ## License 70 | 71 | MerklePatriciaTree is released under the MIT License. See the LICENSE file for further details. 72 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/db.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DB do 2 | @moduledoc """ 3 | Defines a general key-value storage to back and persist 4 | out Merkle Patricia Trie. This is generally LevelDB in the 5 | community, but for testing, we'll generally use `:ets`. 6 | 7 | We define a callback that can be implemented by a number 8 | of potential backends. 9 | """ 10 | defmodule KeyNotFoundError do 11 | defexception [:message] 12 | end 13 | 14 | @type t :: module() 15 | @type db_name :: any() 16 | @type db_ref :: any() 17 | @type db :: {t, db_ref} 18 | @type value :: binary() 19 | 20 | @callback init(db_name) :: db 21 | @callback get(db_ref, MerklePatriciaTree.Trie.key()) :: {:ok, value} | :not_found 22 | @callback put!(db_ref, MerklePatriciaTree.Trie.key(), value) :: :ok 23 | 24 | @doc """ 25 | Retrieves a key from the database. 26 | 27 | ## Examples 28 | 29 | iex> db = MerklePatriciaTree.Test.random_ets_db() 30 | iex> MerklePatriciaTree.DB.get(db, "name") 31 | :not_found 32 | 33 | iex> db = MerklePatriciaTree.Test.random_ets_db() 34 | iex> MerklePatriciaTree.DB.put!(db, "name", "bob") 35 | iex> MerklePatriciaTree.DB.get(db, "name") 36 | {:ok, "bob"} 37 | """ 38 | @spec get(db, MerklePatriciaTree.Trie.key()) :: {:ok, value} | :not_found 39 | def get(_db = {db_mod, db_ref}, key) do 40 | db_mod.get(db_ref, key) 41 | end 42 | 43 | @doc """ 44 | Retrieves a key from the database, but raises if that key does not exist. 45 | 46 | ## Examples 47 | 48 | iex> db = MerklePatriciaTree.Test.random_ets_db() 49 | iex> MerklePatriciaTree.DB.get!(db, "name") 50 | ** (MerklePatriciaTree.DB.KeyNotFoundError) cannot find key `name` 51 | 52 | iex> db = MerklePatriciaTree.Test.random_ets_db() 53 | iex> MerklePatriciaTree.DB.put!(db, "name", "bob") 54 | iex> MerklePatriciaTree.DB.get!(db, "name") 55 | "bob" 56 | """ 57 | @spec get!(db, MerklePatriciaTree.Trie.key()) :: value 58 | def get!(db, key) do 59 | case get(db, key) do 60 | {:ok, value} -> value 61 | :not_found -> raise KeyNotFoundError, message: "cannot find key `#{key}`" 62 | end 63 | end 64 | 65 | @doc """ 66 | Stores a key in the database. 67 | 68 | ## Examples 69 | 70 | ## Examples 71 | 72 | iex> db = MerklePatriciaTree.Test.random_ets_db() 73 | iex> MerklePatriciaTree.DB.put!(db, "name", "bob") 74 | iex> MerklePatriciaTree.DB.get(db, "name") 75 | {:ok, "bob"} 76 | iex> MerklePatriciaTree.DB.put!(db, "name", "tom") 77 | iex> MerklePatriciaTree.DB.get(db, "name") 78 | {:ok, "tom"} 79 | """ 80 | @spec put!(db, MerklePatriciaTree.Trie.key(), value) :: :ok 81 | def put!(_db = {db_mod, db_ref}, key, value) do 82 | db_mod.put!(db_ref, key, value) 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/db/ets.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DB.ETS do 2 | @moduledoc """ 3 | Implementation of `MerklePatriciaTree.DB` which 4 | is backed by :ets. 5 | """ 6 | 7 | alias MerklePatriciaTree.DB 8 | alias MerklePatriciaTree.Trie 9 | 10 | @behaviour MerklePatriciaTree.DB 11 | 12 | @doc """ 13 | Performs initialization for this db. 14 | """ 15 | @spec init(DB.db_name()) :: DB.db() 16 | def init(db_name) do 17 | ^db_name = :ets.new(db_name, [:set, :public, :named_table]) 18 | 19 | {__MODULE__, db_name} 20 | end 21 | 22 | @doc """ 23 | Retrieves a key from the database. 24 | """ 25 | @spec get(DB.db_ref(), Trie.key()) :: {:ok, DB.value()} | :not_found 26 | def get(db_ref, key) do 27 | case :ets.lookup(db_ref, key) do 28 | [{^key, v} | _rest] -> {:ok, v} 29 | _ -> :not_found 30 | end 31 | end 32 | 33 | @doc """ 34 | Stores a key in the database. 35 | """ 36 | @spec put!(DB.db_ref(), Trie.key(), DB.value()) :: :ok 37 | def put!(db_ref, key, value) do 38 | case :ets.insert(db_ref, {key, value}) do 39 | true -> :ok 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/db/leveldb.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DB.LevelDB do 2 | @moduledoc """ 3 | Implementation of MerklePatriciaTree.DB which 4 | is backed by leveldb. 5 | """ 6 | 7 | alias MerklePatriciaTree.Trie 8 | alias MerklePatriciaTree.DB 9 | 10 | @behaviour MerklePatriciaTree.DB 11 | 12 | @doc """ 13 | Performs initialization for this db. 14 | """ 15 | @spec init(DB.db_name()) :: DB.db() 16 | def init(db_name) do 17 | {:ok, db_ref} = Exleveldb.open(db_name, create_if_missing: true) 18 | 19 | {__MODULE__, db_ref} 20 | end 21 | 22 | @doc """ 23 | Retrieves a key from the database. 24 | """ 25 | @spec get(DB.db_ref(), Trie.key()) :: {:ok, DB.value()} | :not_found 26 | def get(db_ref, key) do 27 | case Exleveldb.get(db_ref, key) do 28 | {:ok, v} -> {:ok, v} 29 | :not_found -> :not_found 30 | end 31 | end 32 | 33 | @doc """ 34 | Stores a key in the database. 35 | """ 36 | @spec put!(DB.db_ref(), Trie.key(), DB.value()) :: :ok 37 | def put!(db_ref, key, value) do 38 | case Exleveldb.put(db_ref, key, value) do 39 | :ok -> :ok 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/list_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.ListHelper do 2 | @moduledoc """ 3 | Helpers for navigating lists, specifically for 4 | finding shared prefixes and postfixes. 5 | """ 6 | 7 | @doc """ 8 | Returns the post of list A if starts with list B, otherwise nil 9 | 10 | ## Examples 11 | 12 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2,3], [1,2]) 13 | [3] 14 | 15 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2,3,4], [1,2]) 16 | [3,4] 17 | 18 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2,3,4], [1]) 19 | [2,3,4] 20 | 21 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2,3,4], [0,1]) 22 | nil 23 | 24 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2,3,4], []) 25 | [1,2,3,4] 26 | 27 | iex> MerklePatriciaTree.ListHelper.get_postfix([1,2], [1,2,3]) 28 | nil 29 | 30 | iex> MerklePatriciaTree.ListHelper.get_postfix([], []) 31 | [] 32 | """ 33 | @spec get_postfix([integer()], [integer()]) :: [integer()] | nil 34 | def get_postfix([h0 | t0], [h1 | t1]) do 35 | if h0 == h1 do 36 | get_postfix(t0, t1) 37 | else 38 | nil 39 | end 40 | end 41 | 42 | def get_postfix(l, []), do: l 43 | def get_postfix([], [_ | _]), do: nil 44 | 45 | @doc """ 46 | Returns the overlap of two lists in terms of a shared prefix, then the relative postfixes 47 | 48 | ## Examples 49 | 50 | iex> MerklePatriciaTree.ListHelper.overlap([1,2,3], [1,2]) 51 | {[1,2],[3],[]} 52 | 53 | iex> MerklePatriciaTree.ListHelper.overlap([1,2,3], [1,2,3,4]) 54 | {[1,2,3],[],[4]} 55 | 56 | iex> MerklePatriciaTree.ListHelper.overlap([1,2,3], [2,3,4]) 57 | {[],[1,2,3],[2,3,4]} 58 | 59 | iex> MerklePatriciaTree.ListHelper.overlap([], [2,3,4]) 60 | {[],[],[2,3,4]} 61 | 62 | iex> MerklePatriciaTree.ListHelper.overlap([1,2,3], []) 63 | {[],[1,2,3],[]} 64 | 65 | iex> MerklePatriciaTree.ListHelper.overlap([15, 10, 5, 11], [15, 11, 1, 14]) 66 | {[15], [10, 5, 11], [11, 1, 14]} 67 | """ 68 | @spec overlap([integer()], [integer()]) :: {[integer()], [integer()], [integer()]} 69 | def overlap([], b = [_ | _]), do: {[], [], b} 70 | def overlap(a = [_ | _], []), do: {[], a, []} 71 | def overlap([], []), do: {[], [], []} 72 | 73 | def overlap([a0 | a], [b0 | b]) when a0 == b0 do 74 | {o1, a1, b1} = overlap(a, b) 75 | {[a0 | o1], a1, b1} 76 | end 77 | 78 | def overlap(a, b), do: {[], a, b} 79 | end 80 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/merkle_patricia_tree.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree do 2 | @moduledoc false 3 | end 4 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/test.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Test do 2 | @moduledoc """ 3 | Helper functions related to creating a MerklePatriciaTree 4 | database in :ets or :leveldb. This is to be used when 5 | you need to have a persisted table for a test case, which 6 | is basically any test that involves these tables. The tables 7 | generated by this module are supposed to be exceedingly temporary. 8 | """ 9 | alias MerklePatriciaTree.Test 10 | alias MerklePatriciaTree.DB.ETS, as: MerklePatriciaTreeEts 11 | 12 | @doc """ 13 | Returns a random :ets database suitable for testing 14 | 15 | ## Examples 16 | 17 | iex> {MerklePatriciaTree.DB.ETS, db_ref} = MerklePatriciaTree.Test.random_ets_db() 18 | iex> :ets.info(db_ref)[:type] 19 | :set 20 | 21 | iex> {MerklePatriciaTree.DB.ETS, db_ref} = MerklePatriciaTree.Test.random_ets_db(:test1) 22 | iex> :ets.info(db_ref)[:name] 23 | :test1 24 | """ 25 | def random_ets_db(name \\ nil) do 26 | MerklePatriciaTreeEts.init(name || Test.random_atom(20)) 27 | end 28 | 29 | @doc """ 30 | Returns a semi-random string of length `length` that 31 | can be represented by alphanumeric characters. 32 | 33 | Adopted from https://stackoverflow.com/a/32002566. 34 | 35 | ## Examples 36 | 37 | iex> MerklePatriciaTree.Test.random_string(20) |> is_binary 38 | true 39 | 40 | iex> String.length(MerklePatriciaTree.Test.random_string(20)) 41 | 20 42 | 43 | iex> MerklePatriciaTree.Test.random_string(20) == MerklePatriciaTree.Test.random_string(20) 44 | false 45 | """ 46 | def random_string(length) do 47 | length 48 | |> :crypto.strong_rand_bytes() 49 | |> Base.url_encode64() 50 | |> binary_part(0, length) 51 | end 52 | 53 | @doc """ 54 | Returns a semi-random atom, similar to `random_string/1`, but 55 | is an atom. This is obviously not to be used in production since 56 | atoms are not garbage collected. 57 | 58 | ## Examples 59 | 60 | iex> MerklePatriciaTree.Test.random_atom(20) |> is_atom 61 | true 62 | 63 | iex> MerklePatriciaTree.Test.random_atom(20) |> Atom.to_string |> String.length 64 | 20 65 | 66 | iex> MerklePatriciaTree.Test.random_atom(20) == MerklePatriciaTree.Test.random_atom(20) 67 | false 68 | """ 69 | def random_atom(length) do 70 | length |> random_string |> String.to_atom() 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/lib/trie/helper.ex: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Trie.Helper do 2 | @moduledoc """ 3 | Functions to help with manipulating or working 4 | with tries. 5 | """ 6 | require Logger 7 | 8 | @doc """ 9 | Returns the nibbles of a given binary as a list 10 | 11 | ## Examples 12 | 13 | iex> MerklePatriciaTree.Trie.Helper.get_nibbles(<<0x1e, 0x2f>>) 14 | [0x01, 0x0e, 0x02, 0x0f] 15 | 16 | iex> MerklePatriciaTree.Trie.Helper.get_nibbles(<<0x1::4, 0x02::4, 0x03::4>>) 17 | [1, 2, 3] 18 | 19 | iex> MerklePatriciaTree.Trie.Helper.get_nibbles(<<0x01, 0x02, 0x03>>) 20 | [0, 1, 0, 2, 0, 3] 21 | """ 22 | @spec get_nibbles(binary()) :: [integer()] 23 | def get_nibbles(k), do: for(<>, do: nibble) 24 | 25 | @doc """ 26 | Returns the binary of a given a list of nibbles 27 | 28 | ## Examples 29 | 30 | iex> MerklePatriciaTree.Trie.Helper.get_binary([0x01, 0x0e, 0x02, 0x0f]) 31 | <<0x1e, 0x2f>> 32 | 33 | iex> MerklePatriciaTree.Trie.Helper.get_binary([1, 2, 3]) 34 | <<0x1::4, 0x02::4, 0x03::4>> 35 | 36 | iex> MerklePatriciaTree.Trie.Helper.get_binary([0, 1, 0, 2, 0, 3]) 37 | <<0x01, 0x02, 0x03>> 38 | """ 39 | @spec get_binary([integer()]) :: binary() 40 | def get_binary(l) do 41 | for x <- l, into: <<0::size(0)>>, do: <> 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :merkle_patricia_tree, 7 | version: "0.2.0", 8 | build_path: "../../_build", 9 | config_path: "../../config/config.exs", 10 | deps_path: "../../deps", 11 | lockfile: "../../mix.lock", 12 | elixir: "~> 1.7", 13 | elixirc_options: [warnings_as_errors: true], 14 | description: "Ethereum's Merkle Patricia Trie data structure", 15 | package: [ 16 | maintainers: ["Geoffrey Hayes", "Ayrat Badykov", "Mason Forest"], 17 | licenses: ["MIT"], 18 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 19 | ], 20 | build_embedded: Mix.env() == :prod, 21 | deps: deps(), 22 | dialyzer: [ignore_warnings: "../../.dialyzer.ignore-warnings"], 23 | preferred_cli_env: [ 24 | coveralls: :test, 25 | "coveralls.detail": :test, 26 | "coveralls.post": :test, 27 | "coveralls.html": :test, 28 | dialyzer: :test 29 | ], 30 | start_permanent: Mix.env() == :prod, 31 | test_coverage: [tool: ExCoveralls] 32 | ] 33 | end 34 | 35 | # Configuration for the OTP application 36 | # 37 | # Type `mix help compile.app` for more information 38 | def application do 39 | [extra_applications: [:logger]] 40 | end 41 | 42 | # Run "mix help deps" to learn about dependencies. 43 | defp deps do 44 | [ 45 | # Umbrella 46 | {:hex_prefix, in_umbrella: true}, 47 | {:ex_rlp, in_umbrella: true}, 48 | 49 | # Libaries 50 | {:exleveldb, "~> 0.13"}, 51 | {:keccakf1600, "~> 2.0.0", hex: :keccakf1600_orig}, 52 | {:poison, "~> 4.0.1", only: [:dev, :test], runtime: false}, 53 | 54 | # Common 55 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 56 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev], runtime: false}, 57 | {:ex_doc, "~> 0.19.1", only: :dev, runtime: false} 58 | ] 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/list_helper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.ListHelperTest do 2 | use ExUnit.Case, async: true 3 | doctest MerklePatriciaTree.ListHelper 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/db/ets_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DB.ETSTest do 2 | use ExUnit.Case, async: false 3 | alias MerklePatriciaTree.DB 4 | alias MerklePatriciaTree.DB.ETS 5 | alias MerklePatriciaTree.Test 6 | 7 | test "init creates an ets table" do 8 | {_, db_ref} = ETS.init(Test.random_atom(20)) 9 | 10 | :ets.insert(db_ref, {"key", "value"}) 11 | assert :ets.lookup(db_ref, "key") == [{"key", "value"}] 12 | end 13 | 14 | test "get/1" do 15 | {_, db_ref} = ETS.init(Test.random_atom(20)) 16 | 17 | :ets.insert(db_ref, {"key", "value"}) 18 | assert ETS.get(db_ref, "key") == {:ok, "value"} 19 | assert ETS.get(db_ref, "key2") == :not_found 20 | end 21 | 22 | test "get!/1" do 23 | db = {_, db_ref} = ETS.init(Test.random_atom(20)) 24 | 25 | :ets.insert(db_ref, {"key", "value"}) 26 | assert DB.get!(db, "key") == "value" 27 | 28 | assert_raise MerklePatriciaTree.DB.KeyNotFoundError, "cannot find key `key2`", fn -> 29 | DB.get!(db, "key2") 30 | end 31 | end 32 | 33 | test "put!/2" do 34 | {_, db_ref} = ETS.init(Test.random_atom(20)) 35 | 36 | assert ETS.put!(db_ref, "key", "value") == :ok 37 | assert :ets.lookup(db_ref, "key") == [{"key", "value"}] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/db/leveldb_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DB.LevelDBTest do 2 | use ExUnit.Case, async: false 3 | alias MerklePatriciaTree.DB 4 | alias MerklePatriciaTree.DB.LevelDB 5 | alias MerklePatriciaTree.Test 6 | 7 | test "init creates an leveldb table" do 8 | db_name = "/tmp/db#{Test.random_string(20)}" 9 | 10 | {_, db_ref} = LevelDB.init(db_name) 11 | Exleveldb.close(db_ref) 12 | {:ok, _db} = Exleveldb.open(db_name, create_if_missing: false) 13 | end 14 | 15 | test "get/1" do 16 | {_, db_ref} = LevelDB.init("/tmp/db#{Test.random_string(20)}") 17 | 18 | Exleveldb.put(db_ref, "key", "value") 19 | assert LevelDB.get(db_ref, "key") == {:ok, "value"} 20 | assert LevelDB.get(db_ref, "key2") == :not_found 21 | end 22 | 23 | test "get!/1" do 24 | db = {_, db_ref} = LevelDB.init("/tmp/db#{Test.random_string(20)}") 25 | 26 | Exleveldb.put(db_ref, "key", "value") 27 | assert DB.get!(db, "key") == "value" 28 | 29 | assert_raise MerklePatriciaTree.DB.KeyNotFoundError, "cannot find key `key2`", fn -> 30 | DB.get!(db, "key2") 31 | end 32 | end 33 | 34 | test "put!/2" do 35 | {_, db_ref} = LevelDB.init("/tmp/db#{Test.random_string(20)}") 36 | 37 | assert LevelDB.put!(db_ref, "key", "value") == :ok 38 | assert Exleveldb.get(db_ref, "key") == {:ok, "value"} 39 | end 40 | 41 | test "simple init, put, get" do 42 | db = {_, db_ref} = LevelDB.init("/tmp/db#{Test.random_string(20)}") 43 | 44 | assert LevelDB.put!(db_ref, "name", "bob") == :ok 45 | assert DB.get!(db, "name") == "bob" 46 | assert LevelDB.get(db_ref, "age") == :not_found 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/db_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.DBTest do 2 | use ExUnit.Case 3 | doctest MerklePatriciaTree.DB 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/merkle_patricia_tree_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTreeTest do 2 | use ExUnit.Case 3 | 4 | alias MerklePatriciaTree.Trie 5 | alias MerklePatriciaTree.Test 6 | 7 | @passing_tests %{ 8 | anyorder: :all, 9 | test: :all 10 | } 11 | 12 | test "Ethereum Common Tests" do 13 | for {test_type, test_group} <- @passing_tests do 14 | for {test_name, test} <- read_test_file(test_type), 15 | test_group == :all or Enum.member?(test_group, String.to_atom(test_name)) do 16 | db = Test.random_ets_db() 17 | test_in = test["in"] 18 | 19 | input = 20 | if is_map(test_in) do 21 | test_in 22 | |> Enum.into([]) 23 | |> Enum.map(fn {a, b} -> [a, b] end) 24 | |> Enum.shuffle() 25 | else 26 | test_in 27 | end 28 | 29 | trie = 30 | Enum.reduce(input, Trie.new(db), fn [k, v], trie -> 31 | Trie.update(trie, k |> maybe_hex, v |> maybe_hex) 32 | end) 33 | 34 | # MerklePatriciaTree.Trie.Inspector.inspect_trie(trie) 35 | 36 | assert trie.root_hash == test["root"] |> hex_to_binary 37 | end 38 | end 39 | end 40 | 41 | def read_test_file(type) do 42 | {:ok, body} = File.read(test_file_name(type)) 43 | Poison.decode!(body) 44 | end 45 | 46 | def test_file_name(type) do 47 | "../../test/support/ethereum_common_tests/TrieTests/trie#{Atom.to_string(type)}.json" 48 | end 49 | 50 | def maybe_hex(hex_string = "0x" <> _str), do: hex_to_binary(hex_string) 51 | def maybe_hex(x), do: x 52 | 53 | def hex_to_binary(string) do 54 | string 55 | |> String.slice(2..-1) 56 | |> Base.decode16!(case: :mixed) 57 | end 58 | 59 | def hex_to_int(string) do 60 | string 61 | |> hex_to_binary() 62 | |> :binary.decode_unsigned() 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/test_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.TestTest do 2 | use ExUnit.Case 3 | doctest MerklePatriciaTree.Test 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/trie/helper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Trie.HelperTest do 2 | use ExUnit.Case, async: true 3 | doctest MerklePatriciaTree.Trie.Helper 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/trie/inspector_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Trie.InspectorTest do 2 | use ExUnit.Case, async: true 3 | doctest MerklePatriciaTree.Trie.Inspector 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/trie/node_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Trie.NodeTest do 2 | use ExUnit.Case 3 | doctest MerklePatriciaTree.Trie.Node 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/merkle_patricia_tree/trie/storage_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MerklePatriciaTree.Trie.StorageTest do 2 | use ExUnit.Case, async: true 3 | doctest MerklePatriciaTree.Trie.Storage 4 | end 5 | -------------------------------------------------------------------------------- /apps/merkle_patricia_tree/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # By default, the umbrella project as well as each child 6 | # application will require this configuration file, ensuring 7 | # they all use the same configuration. While one could 8 | # configure all applications here, we prefer to delegate 9 | # back to each application for organization purposes. 10 | import_config "../apps/*/config/config.exs" 11 | import_config "#{Mix.env()}.exs" 12 | # Sample configuration (overrides the imported configuration above): 13 | # 14 | # config :logger, :console, 15 | # level: :info, 16 | # format: "$date $time [$level] $metadata$message\n", 17 | # metadata: [:user_id], 18 | # compile_time_purge_level: :warn 19 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | level: :debug 5 | -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :logger, 4 | level: :info, 5 | # purge logs with lower level than this - removes calls from code 6 | compile_time_purge_level: :info 7 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | -------------------------------------------------------------------------------- /coveralls.json: -------------------------------------------------------------------------------- 1 | { 2 | "default_stop_words": [ 3 | "defmodule", 4 | "defrecord", 5 | "defimpl", 6 | "defexception", 7 | "defprotocol", 8 | "defstruct", 9 | "def.+(.+\\\\.+).+do", 10 | "^\\s+use\\s+" 11 | ], 12 | 13 | "custom_stop_words": [ 14 | ], 15 | 16 | "coverage_options": { 17 | "treat_no_relevant_lines_as_covered": true, 18 | "minimum_coverage": 70 19 | }, 20 | 21 | "terminal_options": { 22 | "file_column_width": 120 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Ethereum.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :ethereum, 7 | version: "0.2.0", 8 | elixir: "~> 1.7", 9 | description: "Exthereum - The Elixir Ethereum Client", 10 | elixirc_options: [warnings_as_errors: true], 11 | package: [ 12 | maintainers: [ 13 | "Geoffrey Hayes", 14 | "Ayrat Badykov", 15 | "Mason Fischer", 16 | "Antoine Toulme", 17 | "Ino Murko" 18 | ], 19 | licenses: ["MIT"], 20 | links: %{"GitHub" => "https://github.com/exthereum/ethereum"} 21 | ], 22 | apps_path: "apps", 23 | start_permanent: Mix.env() == :prod, 24 | deps: deps(), 25 | test_coverage: [tool: ExCoveralls], 26 | preferred_cli_env: [ 27 | coveralls: :test, 28 | "coveralls.detail": :test, 29 | "coveralls.post": :test, 30 | "coveralls.html": :test, 31 | "coveralls.json": :test, 32 | "coveralls.circle": :test, 33 | dialyzer: :test 34 | ], 35 | dialyzer: [ 36 | flags: [:underspecs, :unknown, :unmatched_returns], 37 | plt_add_apps: [:mix, :iex, :logger], 38 | plt_add_deps: :transitive 39 | ] 40 | ] 41 | end 42 | 43 | # Dependencies listed here are available only for this 44 | # project and cannot be accessed from applications inside 45 | # the apps folder. 46 | # 47 | # Run "mix help deps" for examples and options. 48 | defp deps do 49 | [ 50 | {:credo, "~> 0.10.2", only: [:dev, :test], runtime: false}, 51 | {:dialyxir, "~> 1.0.0-rc.3", only: [:test, :dev], runtime: false}, 52 | {:excoveralls, "~> 0.10.0", only: [:test]}, 53 | {:distillery, "~> 2.0"} 54 | ] 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /rel/config.exs: -------------------------------------------------------------------------------- 1 | # Import all plugins from `rel/plugins` 2 | # They can then be used by adding `plugin MyPlugin` to 3 | # either an environment, or release definition, where 4 | # `MyPlugin` is the name of the plugin module. 5 | ~w(rel plugins *.exs) 6 | |> Path.join() 7 | |> Path.wildcard() 8 | |> Enum.map(&Code.eval_file(&1)) 9 | 10 | use Mix.Releases.Config, 11 | # This sets the default release built by `mix release` 12 | default_release: :default, 13 | # This sets the default environment used by `mix release` 14 | default_environment: Mix.env() 15 | 16 | # For a full list of config options for both releases 17 | # and environments, visit https://hexdocs.pm/distillery/config/distillery.html 18 | 19 | 20 | # You may define one or more environments in this file, 21 | # an environment's settings will override those of a release 22 | # when building in that environment, this combination of release 23 | # and environment configuration is called a profile 24 | 25 | environment :dev do 26 | # If you are running Phoenix, you should make sure that 27 | # server: true is set and the code reloader is disabled, 28 | # even in dev mode. 29 | # It is recommended that you build with MIX_ENV=prod and pass 30 | # the --env flag to Distillery explicitly if you want to use 31 | # dev mode. 32 | set output_dir: "_build/" 33 | set dev_mode: false 34 | set include_erts: true 35 | set cookie: :"`ybR3Te$f;/5OwNL^B/x,47Ik)SvZa81Q[&8WukUZ~XvHT)Eg%?&w1^yY`|Er3.2" 36 | end 37 | 38 | environment :prod do 39 | set output_dir: "_build/" 40 | set include_erts: true 41 | set include_src: false 42 | set cookie: :"f<,RX%KGkI@n6%=iE)6bcKh," 43 | end 44 | 45 | # You may define one or more releases in this file. 46 | # If you have not set a default release, or selected one 47 | # when running `mix release`, the first release in the file 48 | # will be used by default 49 | 50 | release :ethereum do 51 | set version: "#{System.get_env("RELEASE_VERSION")}" 52 | set applications: [ 53 | :runtime_tools, 54 | abi: :permanent, 55 | blockchain: :permanent, 56 | evm: :permanent, 57 | ex_rlp: :permanent, 58 | ex_wire: :permanent, 59 | exth_crypto: :permanent, 60 | hex_prefix: :permanent, 61 | merkle_patricia_tree: :permanent 62 | ] 63 | end 64 | 65 | -------------------------------------------------------------------------------- /rel/plugins/.gitignore: -------------------------------------------------------------------------------- 1 | *.* 2 | !*.exs 3 | !.gitignore --------------------------------------------------------------------------------