diff --git a/lib/eth.ex b/lib/eth.ex index 1aab7b2..b084268 100644 --- a/lib/eth.ex +++ b/lib/eth.ex @@ -1,5 +1,5 @@ require IEx -# elliptic curve cryptography library for Ethereum +# elliptic curve cryptography library for signing transactions in Ethereum defmodule ETH do @moduledoc """ Documentation for Eth. @@ -15,13 +15,17 @@ defmodule ETH do """ + def get_private_key do + :crypto.strong_rand_bytes(32) + end + def private_key_to_address(<< private_key :: binary-size(32) >>) do private_key - |> private_key_to_public_key() + |> get_public_key() |> public_key_to_address() end - def private_key_to_public_key(<< private_key :: binary-size(32) >>) do + def get_public_key(<< private_key :: binary-size(32) >>) do {public_key, ^private_key} = :crypto.generate_key(:ecdh, :secp256k1, private_key) public_key end @@ -31,6 +35,24 @@ defmodule ETH do address end + def sign_transaction( + source_wallet, value, target_wallet, options \\ [gas_price: 100, gas_limit: 1000, data: "", chain_id: 3] + ) do + gas_price = options.gas_price |> Hexate.encode + gas_limit = options.gas_limit |> Hexate.encode + data = data |> Hexate.encode + + Ethereumex.HttpClient.eth_get_transaction_count([first[:eth_address]]) |> elem(1) |> Map.get("result") + + # NOTE: calc nonce + %{ + to: target_wallet[:eth_address], value: Hexate.encode(value), gas_price: gas_price, + gas_limit: gas_limit, data: data, chain_id: 3 + } + # get nonce and make a transaction map -> sign_transaction -> send it to client + end + + def sign_transaction(transaction, private_key) do # must have chain_id hash = hash_transaction(transaction) decoded_private_key = Base.decode16!(private_key, case: :lower) @@ -49,7 +71,8 @@ defmodule ETH do def adjust_v_for_chain_id(transaction) do if transaction.chain_id > 0 do current_v_bytes = Base.decode16!(transaction.v, case: :lower) |> :binary.decode_unsigned - transaction |> Map.merge(%{v: encode16(<< current_v_bytes + (transaction.chain_id * 2 + 8) >>) }) + target_v_bytes = current_v_bytes + (transaction.chain_id * 2 + 8) + transaction |> Map.merge(%{v: encode16(<< target_v_bytes >>) }) else transaction end @@ -62,6 +85,7 @@ defmodule ETH do # must have [nonce, gasPrice, gasLimit, to, value, data] # and chainId inside the transaction? def hash_transaction(transaction) do + # NOTE: if transaction is decoded no need to encode # EIP155 spec: # when computing the hash of a transaction for purposes of signing or recovering, # instead of hashing only the first six elements (ie. nonce, gasprice, startgas, to, value, data), diff --git a/lib/eth/wallet.ex b/lib/eth/wallet.ex new file mode 100644 index 0000000..020acd5 --- /dev/null +++ b/lib/eth/wallet.ex @@ -0,0 +1,32 @@ +defmodule ETH.Wallet do + def create(private_key \\ :crypto.strong_rand_bytes(32)) do # pattern match on the base16 version as well + {public_key, _} = :crypto.generate_key(:ecdh, :secp256k1, private_key) + << 4 :: size(8), key :: binary-size(64) >> = public_key + << _ :: binary-size(12), address :: binary-size(20) >> = keccak256(key) + + %{private_key: Base.encode16(private_key), public_key: Base.encode16(public_key), + eth_address: "0x#{Base.encode16(address)}"} + end + + # def get_public_key(<< private_key :: binary-size(32) >>) do + # {public_key, ^private_key} = :crypto.generate_key(:ecdh, :secp256k1, private_key) + # public_key + # end + # + # def private_key_to_address(<< private_key :: binary-size(32) >>) do + # private_key + # |> get_public_key() + # |> public_key_to_address() + # end + # + # def public_key_to_address(<< 4 :: size(8), key :: binary-size(64) >>) do + # << _ :: binary-size(12), address :: binary-size(20) >> = keccak256(key) + # address + # end + + def balance do + # Ethereumex + end + + defp keccak256(data), do: :keccakf1600.hash(:sha3_256, data) +end diff --git a/mix.exs b/mix.exs index cf7098b..3bd1867 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,8 @@ defmodule Eth.Mixfile do version: "0.1.0", elixir: "~> 1.5", start_permanent: Mix.env == :prod, - deps: deps() + deps: deps(), + package: package ] end @@ -24,7 +25,27 @@ defmodule Eth.Mixfile do {:libsecp256k1, [github: "mbrix/libsecp256k1", manager: :rebar]}, {:keccakf1600, git: "https://github.com/jur0/erlang-keccakf1600", branch: "original-keccak"}, {:ex_rlp, "~> 0.2.1"}, - {:hexate, "~> 0.6.1"} + {:hexate, "~> 0.6.1"}, + {:ethereumex, "~> 0.1.0"} + ] + end + + defp description do + """ + Ethereum utilities for Elixir. + """ + end + + def package do + [ + name: :eth, + files: ["lib", "mix.exs", "README*", "LICENSE*"], + maintainers: ["Izel Nakri"], + licenses: ["MIT License"], + links: %{ + "GitHub" => "https://github.com/izelnakri/eth", + "Docs" => "https://hexdocs.pm/eth/ETH.html" + } ] end end diff --git a/test/eth_test.exs b/test/eth_test.exs index f3cc0d9..2121a12 100644 --- a/test/eth_test.exs +++ b/test/eth_test.exs @@ -37,7 +37,7 @@ defmodule ETHTest do assert ETH.secp256k1_signature(hash, private_key)[:signature] |> Base.encode16(case: :lower) == target_signature end - test "hash_transaction\2 works" do + test "hash_transaction/2 works" do result = ETH.hash_transaction(@first_example_transaction) |> Base.encode16(case: :lower) target_digest = "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" assert result == target_digest