diff --git a/lib/eth.ex b/lib/eth.ex index 05aa015..956c0b5 100644 --- a/lib/eth.ex +++ b/lib/eth.ex @@ -15,103 +15,85 @@ defmodule ETH do """ - 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 = options.data |> Hexate.encode - - Ethereumex.HttpClient.eth_get_transaction_count([source_wallet[: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) - [signature: signature, recovery: recovery] = secp256k1_signature(hash, decoded_private_key) - - << r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature - - transaction - |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) - |> adjust_v_for_chain_id - |> transaction_list - |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) - |> ExRLP.encode - end - - 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 - target_v_bytes = current_v_bytes + (transaction.chain_id * 2 + 8) - transaction |> Map.merge(%{v: encode16(<< target_v_bytes >>) }) - else - transaction - end - end - - def secp256k1_signature(hash, private_key) do - {:ok, signature, recovery} = :libsecp256k1.ecdsa_sign_compact(hash, private_key, :default, <<>>) - [signature: signature, recovery: recovery] - end - - # 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), - # hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0 - transaction |> Map.merge(%{v: encode16(<>), r: <<>>, s: <<>> }) - |> transaction_list - |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) - |> hash - end - - def hash(transaction_list) do - transaction_list - |> ExRLP.encode - |> keccak256 - end - - def transaction_list(transaction \\ %{}) do - %{ - nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data - } = transaction - - v = if Map.get(transaction, :v), do: transaction.v, else: Base.encode16(<<28>>, case: :lower) - r = default_is_empty(transaction, :r) - s = default_is_empty(transaction, :s) - [nonce, gas_price, gas_limit, to, value, data, v, r, s] - end - - def convert(number, magnitute \\ :ether) do - denomination = [ - wei: 1, - kwei: 1000, - mwei: 1000000, - gwei: 1000000000, - shannon: 1000000000, - nano: 1000000000, - szabo: 1000000000000, - micro: 1000000000000, - finney: 1000000000000000, - milli: 1000000000000000, - ether: 1000000000000000000, - ] |> List.keyfind(magnitute, 0) |> elem(1) - - number / denomination - end - - def keccak256(data), do: :keccakf1600.hash(:sha3_256, data) - defp encode16(value), do: Base.encode16(value, case: :lower) - defp default_is_empty(map, key), do: if Map.get(map, key), do: Map.get(map, key), else: "" + # def sign_transaction( + # source_eth_address, value, target_eth_address, + # 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 = options[:data] |> Hexate.encode + # + # nonce = case options[:nonce] do + # nil -> ETH.Query.get_transaction_count(source_eth_address) + # _ -> options[:nonce] + # end + # + # # NOTE: calc nonce + # %{ + # to: target_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) + # [signature: signature, recovery: recovery] = secp256k1_signature(hash, decoded_private_key) + # + # << r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature + # + # transaction + # |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) + # |> adjust_v_for_chain_id + # |> transaction_list + # |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + # |> ExRLP.encode + # end + # + # 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 + # target_v_bytes = current_v_bytes + (transaction.chain_id * 2 + 8) + # transaction |> Map.merge(%{v: encode16(<< target_v_bytes >>) }) + # else + # transaction + # end + # end + # + # def secp256k1_signature(hash, private_key) do + # {:ok, signature, recovery} = :libsecp256k1.ecdsa_sign_compact(hash, private_key, :default, <<>>) + # [signature: signature, recovery: recovery] + # end + # + # # 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), + # # hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0 + # transaction |> Map.merge(%{v: encode16(<>), r: <<>>, s: <<>> }) + # |> transaction_list + # |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + # |> hash + # end + # + # def hash(transaction_list) do + # transaction_list + # |> ExRLP.encode + # |> keccak256 + # end + # + # def transaction_list(transaction \\ %{}) do + # %{ + # nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data + # } = transaction + # + # v = if Map.get(transaction, :v), do: transaction.v, else: Base.encode16(<<28>>, case: :lower) + # r = Map.get(transaction, :r, "") + # s = Map.get(transaction, :s, "") + # [nonce, gas_price, gas_limit, to, value, data, v, r, s] + # end end diff --git a/lib/eth/query.ex b/lib/eth/query.ex index 25bedcb..5b64125 100644 --- a/lib/eth/query.ex +++ b/lib/eth/query.ex @@ -1,18 +1,97 @@ defmodule ETH.Query do + import ETH.Utils + + def block_number do + Ethereumex.HttpClient.eth_block_number + |> get_number_result + end + + def syncing do + Ethereumex.HttpClient.eth_syncing + |> get_result + end + def get_accounts do Ethereumex.HttpClient.eth_accounts - |> elem(1) - |> Map.get("result") + |> get_result end def get_balance(eth_address, denomination \\ :ether) do - result = Ethereumex.HttpClient.eth_get_balance([eth_address]) - |> elem(1) - |> Map.get("result") + Ethereumex.HttpClient.eth_get_balance([eth_address]) + |> get_number_result + |> convert(denomination) + end + + def get_transaction(transaction_hash) do + Ethereumex.HttpClient.eth_get_transaction_by_hash([transaction_hash]) + |> get_result + |> Enum.reduce(%{}, fn(tuple, acc) -> + {key, value} = tuple + + case key do + "blockHash" -> Map.put(acc, :block_hash, value) + "blockNumber" -> Map.put(acc, :block_number, convert_to_number(value)) + "gas" -> Map.put(acc, :gas, convert_to_number(value)) + "gasPrice" -> Map.put(acc, :gas_price, convert_to_number(value)) + "input" -> # NOTE: this could be wrong + input = value |> String.slice(2..-1) |> ExRLP.decode + Map.put(acc, :input, input) + "nonce" -> Map.put(acc, :nonce, convert_to_number(value)) + "transactionIndex" -> Map.put(acc, :transaction_index, convert_to_number(value)) + "value" -> Map.put(acc, :value, convert_to_number(value)) + _ -> Map.put(acc, String.to_atom(key), value) + end + end) + end + + def get_transaction_receipt(transaction_hash) do + Ethereumex.HttpClient.eth_get_transaction_receipt([transaction_hash]) + |> get_result + |> Enum.reduce(%{}, fn(tuple, acc) -> + {key, value} = tuple + + case key do + "transactionHash" -> Map.put(acc, :transaction_hash, value) + "transactionIndex" -> Map.put(acc, :transaction_index, convert_to_number(value)) + "blockHash" -> Map.put(acc, :block_hash, value) + "blockNumber" -> Map.put(acc, :block_number, convert_to_number(value)) + "gasUsed" -> Map.put(acc, :gas_used, convert_to_number(value)) + "cumulativeGasUsed" -> Map.put(acc, :cumulative_gas_used, convert_to_number(value)) + "contractAddress" -> Map.put(acc, :contract_address, value) + "logs" -> Map.put(acc, :logs, value) + end + end) + end + + def get_transaction_count(eth_address) do + Ethereumex.HttpClient.eth_get_transaction_count([eth_address]) + |> get_number_result + end + def gas_price do + Ethereumex.HttpClient.eth_gas_price() + |> get_number_result + end + + def estimate_gas(transaction \\ %{data: ""}) + def estimate_gas(transaction = %{to: to, data: data}) do + Ethereumex.HttpClient.eth_estimate_gas([transaction]) + |> get_number_result + end + + defp get_result(eth_result), do: eth_result |> elem(1) |> Map.get("result") + + defp get_number_result(eth_result) do + get_result(eth_result) |> convert_to_number + end + + defp convert_to_number(result) do result - |> String.slice(2..String.length(result)) + |> String.slice(2..-1) |> Hexate.to_integer - |> ETH.convert(denomination) end end + +# TODO: +# get_block(hash or block_number), get_block_transaction_count(hash or block_number), +# get_transaction_from_block, call, perhaps get_block_transactions diff --git a/lib/eth/transaction.ex b/lib/eth/transaction.ex index 5406a61..1c7dbe1 100644 --- a/lib/eth/transaction.ex +++ b/lib/eth/transaction.ex @@ -1,9 +1,144 @@ +require IEx defmodule ETH.Transaction do - # def set([]) do # maybe better name + import ETH.Utils + + alias ETH.Query + + def set(params = [from: from, to: to, value: value]) do + gas_price = Keyword.get(params, :gas_price, ETH.Query.gas_price()) + gas_limit = Keyword.get(params, :gas_limit, ETH.Query.gas_limit()) + data = Keyword.get(params, :data, "") + nonce = Keyword.get(params, :nonce, ETH.Query.transaction_count(from)) + chain_id = Keyword.get(params, :chain_id, 3) + + %{ + from: from, to: to, value: value, gas_price: gas_price, gas_limit: gas_limit, + data: data, nonce: nonce, chain_id: chain_id + } + end + + def sign(transaction = %{ + to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + nonce: _nonce, chain_id: _chain_id + }, << private_key :: binary-size(32) >>), do: sign_transaction(transaction, private_key) + def sign(transaction = %{ + to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + nonce: _nonce, chain_id: _chain_id + }, << encoded_private_key :: binary-size(64) >>) do + decoded_private_key = Base.decode16!(encoded_private_key, case: :lower) + sign_transaction(transaction, decoded_private_key) + end + + def send(signature) do + # NOTE: make it strict + Ethereumex.HttpClient.eth_send_raw_transaction([signature]) + end + + # def send(transaction \\ %{data: "", chain_id: 3, gas_price: 1}) + # NOTE: send_transaction\1 + # if there is a gas price dont use it + # def send(transaction = %{from: from, to: to, value: value}) do + # transaction_params = case Map.get(transaction, :data) do + # nil -> transaction |> Map.merge(%{data: ""}) + # _ -> transaction + # end + # + # gas_limit = Map.get(transaction, :gas_limit, Query.estimate_gas(transaction_params)) # NOTE: maybe do it slightly more + # + # IEx.pry # + # transaction_options = [ + # data: Map.get(transaction, :data, ""), chain_id: Map.get(transaction, :chain_id, 3), + # gas_price: Map.get(transaction, :gas_price, 1), gas_limit: gas_limit + # ] + # # NOTE: get nonce + # + # IEx.pry + # signature = ETH.sign_transaction(from, value, to, transaction_options) + # IEx.pry + # Ethereumex.HttpClient.eth_send_raw_transaction([signature]) # end - # def send(transaction) do + # set -> sign -> sign_transaction + + # def sign_transaction( + # source_eth_address, value, target_eth_address, + # 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 = options[:data] |> Hexate.encode # + # nonce = case options[:nonce] do + # nil -> ETH.Query.get_transaction_count(source_eth_address) + # _ -> options[:nonce] + # end + # + # # NOTE: calc nonce + # %{ + # to: target_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 hash_transaction(transaction = %{ + to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + nonce: _nonce, chain_id: _chain_id + }) 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), + # hash nine elements, with v replaced by CHAIN_ID, r = 0 and s = 0 + transaction + |> Map.merge(%{v: encode16(<>), r: <<>>, s: <<>>}) + |> to_list + |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + |> hash + end + + def hash(transaction_list) do + transaction_list + |> ExRLP.encode + |> keccak256 + end + + defp 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 + target_v_bytes = current_v_bytes + (transaction.chain_id * 2 + 8) + transaction |> Map.merge(%{v: encode16(<< target_v_bytes >>) }) + else + transaction + end + end + + defp to_list(transaction) do + %{ + nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data + } = transaction + + v = Map.get(transaction, :v, Base.encode16(<<28>>, case: :lower)) + r = Map.get(transaction, :r, "") + s = Map.get(transaction, :s, "") + [nonce, gas_price, gas_limit, to, value, data, v, r, s] + end + + defp sign_transaction(transaction = %{ + to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + nonce: _nonce, chain_id: _chain_id + }, << private_key :: binary-size(32) >>) do + hash = hash_transaction(transaction) + [signature: signature, recovery: recovery] = secp256k1_signature(hash, private_key) + + << r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature + + transaction + |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) + |> adjust_v_for_chain_id + |> to_list + |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + |> ExRLP.encode + end end diff --git a/lib/eth/utils.ex b/lib/eth/utils.ex index 0a2a1df..688e1ef 100644 --- a/lib/eth/utils.ex +++ b/lib/eth/utils.ex @@ -1,8 +1,6 @@ defmodule ETH.Utils do - def get_private_key do - :crypto.strong_rand_bytes(32) - end + def get_private_key, do: :crypto.strong_rand_bytes(32) def get_public_key(<< private_key :: binary-size(32) >>) do {:ok, public_key} = :libsecp256k1.ec_pubkey_create(private_key, :uncompressed) @@ -20,7 +18,8 @@ defmodule ETH.Utils do "0x#{Base.encode16(eth_address)}" end def get_address(<< encoded_private_key :: binary-size(64) >>) do - << 4 :: size(8), key :: binary-size(64) >> = Base.decode16!(encoded_private_key) |> get_public_key() + public_key = Base.decode16!(encoded_private_key) |> get_public_key() + << 4 :: size(8), key :: binary-size(64) >> = public_key << _ :: binary-size(12), eth_address :: binary-size(20) >> = keccak256(key) "0x#{Base.encode16(eth_address)}" end @@ -34,8 +33,32 @@ defmodule ETH.Utils do "0x#{Base.encode16(eth_address)}" end + # NOTE: not tested area: + def convert(number, magnitute \\ :ether) do + denomination = [ + wei: 1, + kwei: 1000, + mwei: 1000000, + gwei: 1000000000, + shannon: 1000000000, + nano: 1000000000, + szabo: 1000000000000, + micro: 1000000000000, + finney: 1000000000000000, + milli: 1000000000000000, + ether: 1000000000000000000, + ] |> List.keyfind(magnitute, 0) |> elem(1) + + number / denomination + end + + def secp256k1_signature(hash, private_key) do + {:ok, signature, recovery} = :libsecp256k1.ecdsa_sign_compact(hash, private_key, :default, <<>>) + [signature: signature, recovery: recovery] + end + def keccak256(data), do: :keccakf1600.hash(:sha3_256, data) - defp encode16(value), do: Base.encode16(value, case: :lower) + def encode16(value), do: Base.encode16(value, case: :lower) end # NOTE: old version that is error-prone: diff --git a/test/eth/query_test.exs b/test/eth/query_test.exs index 88e8d88..c170d69 100644 --- a/test/eth/query_test.exs +++ b/test/eth/query_test.exs @@ -1,6 +1,17 @@ +# TODO: write tests for get_transaction\1 and get_transaction_receipt\1 defmodule ETH.QueryTest do use ExUnit.Case + test "block_number/0 works" do + block_number = ETH.Query.block_number + + assert is_integer(block_number) + end + + test "syncing/0 works" do + assert ETH.Query.syncing == false + end + test "get_accounts/0 works" do accounts = ETH.Query.get_accounts @@ -27,4 +38,33 @@ defmodule ETH.QueryTest do assert ETH.Query.get_balance(address_with_balance, :wei) == 1.0e20 assert ETH.Query.get_balance(address_with_no_balance, :wei) == 0.0 end + + test "transaction_count/1 works" do + address_with_balance = ETH.Query.get_accounts |> List.first + + assert ETH.Query.get_transaction_count(address_with_balance) == 0 + end + + test "estimate_gas/2 works with default wei denomination" do + address_with_balance = ETH.Query.get_accounts |> List.first + + assert ETH.Query.estimate_gas(%{to: address_with_balance, data: ""}) == 2.1e4 + assert ETH.Query.estimate_gas(%{to: address_with_balance, data: "asd"}) == 21340 + end + + # test "estimate_gas/2 works with different denomination" do + # address = ETH.Query.get_accounts |> List.first + # first_gas_in_ether = ETH.Query.estimate_gas(%{to: address, data: ""}) + # second_gas_in_ether = ETH.Query.estimate_gas(%{to: address, data: "asd"}) + # + # first_gas_in_wei = ETH.Query.estimate_gas(%{to: address, data: ""}, :wei) + # second_gas_in_wei = ETH.Query.estimate_gas(%{to: address, data: "asd"}, :wei) + # + # assert first_gas_in_wei == 21000 + # assert second_gas_in_wei == 21340 + # + # first_difference = first_gas_in_ether / second_gas_in_ether + # second_difference = first_gas_in_wei / second_gas_in_wei + # assert Float.floor(first_difference, 15) == Float.floor(second_difference, 15) + # end end diff --git a/test/eth/transaction_test.exs b/test/eth/transaction_test.exs new file mode 100644 index 0000000..899efc6 --- /dev/null +++ b/test/eth/transaction_test.exs @@ -0,0 +1,54 @@ +defmodule ETH.TransactionTest do + use ExUnit.Case + + @first_transaction_list [ + "", "09184e72a000", "2710", "0000000000000000000000000000000000000000", "", + "7f7465737432000000000000000000000000000000000000000000000000000000600057", "29", + "f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95a", + "5b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" + ] |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + + @first_example_wallet %{ + private_key: "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" + } + @first_example_transaction %{ + nonce: "", + gas_price: "09184e72a000", + gas_limit: "2710", + to: "0000000000000000000000000000000000000000", + value: "", + data: "7f7465737432000000000000000000000000000000000000000000000000000000600057", + chain_id: 3 # EIP 155 chainId - mainnet: 1, ropsten: 3 + } + + + test "hash/1 works" do + target_hash = "5C207A650B59A8C2D1271F5CBDA78A658CB411A87271D68062E61AB1A3F85CF9" + assert ETH.Transaction.hash(@first_transaction_list) |> Base.encode16 == target_hash + end + + test "secp256k1_signature/2 works" do + hash = "5c207a650b59a8c2d1271f5cbda78a658cb411a87271d68062e61ab1a3f85cf9" + |> Base.decode16!(case: :mixed) + private_key = "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" + |> Base.decode16!(case: :mixed) + + target_signature = "c2a738b1eb84280399115f4bec9e52b8de494a3ea7d9f069277119a02de4a49876f3168913e968e9484e2e0e447cd7adc56505e25cbc372330793a31f0bf7195" + secp256k1_signature = ETH.Utils.secp256k1_signature(hash, private_key) + + assert secp256k1_signature[:signature] |> Base.encode16(case: :lower) == target_signature + end + + test "hash_transaction/2 works" do + result = ETH.Transaction.hash_transaction(@first_example_transaction) |> Base.encode16(case: :lower) + target_digest = "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" + assert result == target_digest + end + + test "sign/2 works" do + signature = ETH.Transaction.sign(@first_example_transaction, @first_example_wallet.private_key) + |> Base.encode16(case: :lower) + + assert signature == "f889808609184e72a00082271094000000000000000000000000000000000000000080a47f746573743200000000000000000000000000000000000000000000000000000060005729a0f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95aa05b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" + end +end diff --git a/test/eth/utils_test.exs b/test/eth/utils_test.exs index a1a1872..7cb0d23 100644 --- a/test/eth/utils_test.exs +++ b/test/eth/utils_test.exs @@ -15,7 +15,7 @@ defmodule ETH.UtilsTest do assert public_key == ETH.Utils.get_public_key(private_key) assert public_key != ETH.Utils.get_public_key(another_private_key) - 1..1000 |> Enum.each(fn(x) -> + 1..1000 |> Enum.each(fn(_) -> private_key = :crypto.strong_rand_bytes(32) ETH.Utils.get_public_key(private_key) end) diff --git a/test/eth_test.exs b/test/eth_test.exs index fcde58f..fcf6ddc 100644 --- a/test/eth_test.exs +++ b/test/eth_test.exs @@ -1,53 +1,54 @@ defmodule ETHTest do use ExUnit.Case - @first_transaction_list [ - "", "09184e72a000", "2710", "0000000000000000000000000000000000000000", "", - "7f7465737432000000000000000000000000000000000000000000000000000000600057", "29", - "f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95a", - "5b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" - ] |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) - - @first_example_wallet %{ - private_key: "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" - } - @first_example_transaction %{ - nonce: "", - gas_price: "09184e72a000", - gas_limit: "2710", - to: "0000000000000000000000000000000000000000", - value: "", - data: "7f7465737432000000000000000000000000000000000000000000000000000000600057", - chain_id: 3 # EIP 155 chainId - mainnet: 1, ropsten: 3 - } - - - test "hash/1 works" do - target_hash = "5C207A650B59A8C2D1271F5CBDA78A658CB411A87271D68062E61AB1A3F85CF9" - assert ETH.hash(@first_transaction_list) |> Base.encode16 == target_hash - end - - test "secp256k1_signature/2 works" do - hash = "5c207a650b59a8c2d1271f5cbda78a658cb411a87271d68062e61ab1a3f85cf9" - |> Base.decode16!(case: :mixed) - private_key = "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" - |> Base.decode16!(case: :mixed) - - target_signature = "c2a738b1eb84280399115f4bec9e52b8de494a3ea7d9f069277119a02de4a49876f3168913e968e9484e2e0e447cd7adc56505e25cbc372330793a31f0bf7195" - - assert ETH.secp256k1_signature(hash, private_key)[:signature] |> Base.encode16(case: :lower) == target_signature - end - - test "hash_transaction/2 works" do - result = ETH.hash_transaction(@first_example_transaction) |> Base.encode16(case: :lower) - target_digest = "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" - assert result == target_digest - end - - test "sign_transaction/2 works" do - signature = ETH.sign_transaction(@first_example_transaction, @first_example_wallet.private_key) - |> Base.encode16(case: :lower) - - assert signature == "f889808609184e72a00082271094000000000000000000000000000000000000000080a47f746573743200000000000000000000000000000000000000000000000000000060005729a0f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95aa05b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" - end + # @first_transaction_list [ + # "", "09184e72a000", "2710", "0000000000000000000000000000000000000000", "", + # "7f7465737432000000000000000000000000000000000000000000000000000000600057", "29", + # "f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95a", + # "5b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" + # ] |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) + # + # @first_example_wallet %{ + # private_key: "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" + # } + # @first_example_transaction %{ + # nonce: "", + # gas_price: "09184e72a000", + # gas_limit: "2710", + # to: "0000000000000000000000000000000000000000", + # value: "", + # data: "7f7465737432000000000000000000000000000000000000000000000000000000600057", + # chain_id: 3 # EIP 155 chainId - mainnet: 1, ropsten: 3 + # } + # + # + # test "hash/1 works" do + # target_hash = "5C207A650B59A8C2D1271F5CBDA78A658CB411A87271D68062E61AB1A3F85CF9" + # assert ETH.Transaction.hash(@first_transaction_list) |> Base.encode16 == target_hash + # end + # + # test "secp256k1_signature/2 works" do + # hash = "5c207a650b59a8c2d1271f5cbda78a658cb411a87271d68062e61ab1a3f85cf9" + # |> Base.decode16!(case: :mixed) + # private_key = "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" + # |> Base.decode16!(case: :mixed) + # + # target_signature = "c2a738b1eb84280399115f4bec9e52b8de494a3ea7d9f069277119a02de4a49876f3168913e968e9484e2e0e447cd7adc56505e25cbc372330793a31f0bf7195" + # secp256k1_signature = ETH.Utils.secp256k1_signature(hash, private_key) + # + # assert secp256k1_signature[:signature] |> Base.encode16(case: :lower) == target_signature + # end + # + # test "hash_transaction/2 works" do + # result = ETH.Transaction.hash_transaction(@first_example_transaction) |> Base.encode16(case: :lower) + # target_digest = "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" + # assert result == target_digest + # end + # + # test "sign_transaction/2 works" do + # signature = ETH.Transaction.sign_transaction(@first_example_transaction, @first_example_wallet.private_key) + # |> Base.encode16(case: :lower) + # + # assert signature == "f889808609184e72a00082271094000000000000000000000000000000000000000080a47f746573743200000000000000000000000000000000000000000000000000000060005729a0f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95aa05b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" + # end end