diff --git a/lib/eth/transaction.ex b/lib/eth/transaction.ex index 38ca012..2f0c6cd 100644 --- a/lib/eth/transaction.ex +++ b/lib/eth/transaction.ex @@ -40,6 +40,9 @@ defmodule ETH.Transaction do r: to_buffer(r), s: to_buffer(s) } end + # def parse(params) do + # # add default values + # end def buffer_to_map(_transaction_buffer = [nonce, gas_price, gas_limit, to, value, data, v, r, s]) do %{ @@ -48,11 +51,6 @@ defmodule ETH.Transaction do } end - # def parse(params) do - # # add default values - # end - - def set(params = [from: from, to: to, value: value]) do gas_price = Keyword.get(params, :gas_price, ETH.Query.gas_price()) data = Keyword.get(params, :data, "") @@ -103,16 +101,14 @@ defmodule ETH.Transaction do def hash_transaction(transaction = %{ to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, nonce: _nonce - }) do - chain_id = Map.get(transaction, :chain_id, 0) + }, include_signature \\ true) do + chain_id = get_chain_id(Map.get(transaction, :v, <<28>>), Map.get(transaction, :chain_id)) + transaction - |> Map.merge(%{v: encode16(<>), r: <<>>, s: <<>>}) + # |> Map.merge(%{v: Map.get(transaction, :v, <<28>>)}) |> to_list - |> Enum.map(fn(x) -> - # IEx.pry - Base.decode16!(x, case: :mixed) - end) - |> hash + |> List.insert_at(-1, chain_id) + |> hash(include_signature) end def hash(transaction_list, include_signature \\ true) do # NOTE: use internally @@ -123,14 +119,13 @@ defmodule ETH.Transaction do # 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 - list = transaction_list |> Enum.take(6) - v = transaction_list |> Enum.at(6) - chain_id = get_chain_id(v) - if chain_id > 0, do: list ++ [chain_id, "", ""], else: list # NOTE: this part is dangerous: in JS we change state(v: chainId, r: 0, s: 0) + list = Enum.take(transaction_list, 6) + v = Enum.at(transaction_list, 6) || <<28>> + chain_id = get_chain_id(v, Enum.at(transaction_list, 9)) + if chain_id > 0, do: list ++ [chain_id, 0, 0], else: list end target_list - |> Enum.map(fn(value) -> to_buffer(value) end) |> ExRLP.encode |> keccak256 end @@ -141,13 +136,14 @@ defmodule ETH.Transaction do }, << private_key :: binary-size(32) >>) do hash = hash_transaction(transaction) IO.puts("hash is") - # IO.puts(hash) + IO.puts(hash) [signature: signature, recovery: recovery] = secp256k1_signature(hash, private_key) << r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature + # this will change v, r, s transaction - |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) + # |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) |> adjust_v_for_chain_id |> to_list |> Enum.map(fn(x) -> @@ -171,14 +167,21 @@ defmodule ETH.Transaction do end end + def to_list(transaction = %{ + nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data, + v: v, r: r, s: s + }) do + [nonce, gas_price, gas_limit, to, value, data, v, r, s] + |> Enum.map(fn(value) -> to_buffer(value) end) + end def to_list(transaction = %{ nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data }) do - v = Map.get(transaction, :v, Base.encode16(<<28>>, case: :lower)) # this probably needs to change + v = Map.get(transaction, :v, <<28>>) r = Map.get(transaction, :r, "") s = Map.get(transaction, :s, "") - [nonce, gas_price, gas_limit, to, value, data, v, r, s] # NOTE: maybe this should turn things toBuffer + [nonce, gas_price, gas_limit, to, value, data, v, r, s] |> Enum.map(fn(value) -> to_buffer(value) end) end @@ -208,12 +211,18 @@ defmodule ETH.Transaction do def to_hex(value), do: HexPrefix.encode(value) # def to_json() # transaction_map or transaction_list - defp get_chain_id("0x" <> v) do + + + def get_chain_id(v, chain_id \\ nil) do + computed_chain_id = compute_chain_id(v) + if computed_chain_id == 0, do: (chain_id || 0), else: computed_chain_id + end + defp compute_chain_id("0x" <> v) do sig_v = buffer_to_int(v) chain_id = Float.floor((sig_v - 35) / 2) if chain_id < 0, do: 0, else: Kernel.trunc(chain_id) end - defp get_chain_id(v) do + defp compute_chain_id(v) do sig_v = buffer_to_int(v) chain_id = Float.floor((sig_v - 35) / 2) if chain_id < 0, do: 0, else: Kernel.trunc(chain_id) @@ -234,8 +243,11 @@ defmodule ETH.Transaction do def to_buffer(nil), do: "" def to_buffer(data) when is_number(data) do - pad_to_even(Integer.to_string(data, 16)) |> Base.decode16!(case: :mixed) + padded_data = pad_to_even(Integer.to_string(data, 16)) + # IEx.pry + padded_data end + def to_buffer("0x00"), do: "" def to_buffer("0x" <> data) do padded_data = pad_to_even(data) case Base.decode16(padded_data, case: :mixed) do diff --git a/test/eth/transaction_test.exs b/test/eth/transaction_test.exs index 5d49833..f7205a4 100644 --- a/test/eth/transaction_test.exs +++ b/test/eth/transaction_test.exs @@ -4,31 +4,28 @@ defmodule ETH.TransactionTest do import ETH.Utils # TODO: "it should decode transactions" - @first_transaction_list [ - "", "09184e72a000", "2710", "0000000000000000000000000000000000000000", "", - "7f7465737432000000000000000000000000000000000000000000000000000000600057", "29", - "f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95a", - "5b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" - ] |> Enum.map(fn(x) -> decode16(x) end) - @first_example_wallet %{ private_key: "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109" } @first_example_transaction %{ - nonce: "", - gas_price: "09184e72a000", - gas_limit: "2710", - to: "0000000000000000000000000000000000000000", - value: "", - data: "7f7465737432000000000000000000000000000000000000000000000000000000600057", + nonce: "0x00", + gas_price: "0x09184e72a000", + gas_limit: "0x2710", + to: "0x0000000000000000000000000000000000000000", + value: "0x00", + data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057", chain_id: 3 # EIP 155 chainId - mainnet: 1, ropsten: 3 } @transactions File.read!("test/fixtures/transactions.json") |> Poison.decode! @eip155_transactions File.read!("test/fixtures/eip155_vitalik_tests.json") |> Poison.decode! test "hash/1 works" do - target_hash = "5C207A650B59A8C2D1271F5CBDA78A658CB411A87271D68062E61AB1A3F85CF9" - assert ETH.Transaction.hash(@first_transaction_list) |> Base.encode16 == target_hash + target_hash = "DF2A7CB6D05278504959987A144C116DBD11CBDC50D6482C5BAE84A7F41E2113" + assert @first_example_transaction + |> ETH.Transaction.to_list + |> List.insert_at(-1, @first_example_transaction.chain_id) + |> ETH.Transaction.hash(false) + |> Base.encode16 == target_hash first_transaction_list = @transactions |> Enum.at(2) @@ -64,9 +61,11 @@ defmodule ETH.TransactionTest do 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 + result = @first_example_transaction + |> ETH.Transaction.hash_transaction(false) + |> Base.encode16(case: :lower) + + assert result == "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" end test "sign/2 works" do