From 20cf997a6b2cb0e1c3f8d70505e7f91b007a0863 Mon Sep 17 00:00:00 2001 From: Izel Nakri Date: Tue, 5 Sep 2017 01:47:29 +0200 Subject: [PATCH] ongoing changes --- lib/eth/transaction.ex | 225 +++++++++++++++++++++------------ lib/eth/utils.ex | 4 + test/eth/transaction_test.exs | 5 +- test/eth/transactions_test.exs | 29 +++-- 4 files changed, 171 insertions(+), 92 deletions(-) diff --git a/lib/eth/transaction.ex b/lib/eth/transaction.ex index 16de60d..395971c 100644 --- a/lib/eth/transaction.ex +++ b/lib/eth/transaction.ex @@ -5,6 +5,54 @@ defmodule ETH.Transaction do alias ETH.Query + def parse("0x" <> encoded_transaction_rlp) do + encoded_transaction_rlp |> Base.decode16!(case: :mixed) |> ExRLP.decode + end + def parse(<>), do: transaction_rlp |> ExRLP.decode + def parse([nonce, gas_price, gas_limit, to, value, data]) do + %{ + nonce: to_buffer(nonce), gas_price: to_buffer(gas_price), gas_limit: to_buffer(gas_limit), + to: to_buffer(to), value: to_buffer(to), data: to_buffer(data) + } + end + def parse([nonce, gas_price, gas_limit, to, value, data, v, r, s]) do + %{ + nonce: to_buffer(nonce), gas_price: to_buffer(gas_price), gas_limit: to_buffer(gas_limit), + to: to_buffer(to), value: to_buffer(to), data: to_buffer(data), v: to_buffer(v), + r: to_buffer(r), s: to_buffer(s) + } + end + def parse(%{ + nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: to, data: data + }) do + %{ + nonce: to_buffer(nonce), gas_price: to_buffer(gas_price), gas_limit: to_buffer(gas_limit), + to: to_buffer(to), value: to_buffer(to), data: to_buffer(data) + } + end + def parse(%{ + nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: to, data: data, + v: v, r: r, s: s + }) do + %{ + nonce: to_buffer(nonce), gas_price: to_buffer(gas_price), gas_limit: to_buffer(gas_limit), + to: to_buffer(to), value: to_buffer(to), data: to_buffer(data), v: to_buffer(v), + r: to_buffer(r), s: to_buffer(s) + } + end + + def buffer_to_map(_transaction_buffer = [nonce, gas_price, gas_limit, to, value, data, v, r, s]) do + %{ + nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data, + v: v, r: r, s: s + } + 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, "") @@ -17,20 +65,20 @@ defmodule ETH.Transaction do [ to: to, value: value, data: data, gas_price: gas_price, gas_limit: gas_limit, nonce: nonce ] - # |> Enum.map(fn(x) -> - # {key, value} = x - # {key, Base.encode16(value, case: :lower)} - # end) - |> Enum.into(%{chain_id: chain_id}) # NOTE: this is probably wrong + |> Enum.map(fn(x) -> + {key, value} = x + {key, to_buffer(value)} + end) + |> Enum.into(%{}) end def sign(transaction = %{ to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, - nonce: _nonce, chain_id: _chain_id + nonce: _nonce }, << 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 + nonce: _nonce }, << encoded_private_key :: binary-size(64) >>) do decoded_private_key = Base.decode16!(encoded_private_key, case: :mixed) sign_transaction(transaction, decoded_private_key) @@ -54,16 +102,20 @@ defmodule ETH.Transaction do def hash_transaction(transaction = %{ to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, - nonce: _nonce, chain_id: chain_id + nonce: _nonce }) do + chain_id = Map.get(transaction, :chain_id, 0) transaction |> Map.merge(%{v: encode16(<>), r: <<>>, s: <<>>}) |> to_list - |> Enum.map(fn(x) -> Base.decode16!(x, case: :mixed) end) + |> Enum.map(fn(x) -> + # IEx.pry + Base.decode16!(x, case: :mixed) + end) |> hash end - def hash(transaction_list, include_signature \\ true) do + def hash(transaction_list, include_signature \\ true) do # NOTE: use internally target_list = case include_signature do true -> transaction_list false -> @@ -74,43 +126,22 @@ defmodule ETH.Transaction do list = transaction_list |> Enum.take(6) v = transaction_list |> Enum.at(6) chain_id = get_chain_id(v) - if chain_id > 0, do: list ++ ["0x#{chain_id}", "0x", "0x"], else: list # NOTE: this part is dangerous: in JS we change state(v: chainId, r: 0, s: 0) + 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) end - IO.puts("target_list is:") - IO.inspect(target_list) target_list - |> Enum.map(fn(value) -> # NOTE: maybe move this should be moved somewhere else (probably .set() sets these or transaction list) - cond do - is_number(value) -> - IO.puts("value is") - IO.puts(value) - string_value = to_string(value) - result = add_0_for_uneven_encoding(string_value) - IO.puts("result is:") - IO.puts(result) - result - String.slice(value, 0..1) == "0x" -> - "0x" <> stripped_value = value - encoded_value = add_0_for_uneven_encoding(stripped_value) - Base.decode16!(encoded_value, case: :mixed) - true -> value - end - # NOTE: else if (v === null || v === undefined) { v = Buffer.allocUnsafe(0) } - end) + |> Enum.map(fn(value) -> to_buffer(value) end) |> ExRLP.encode |> keccak256 end - def verify_signature(transaction) do - - 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 + nonce: _nonce }, << private_key :: binary-size(32) >>) do hash = hash_transaction(transaction) + IO.puts("hash is") + # IO.puts(hash) [signature: signature, recovery: recovery] = secp256k1_signature(hash, private_key) << r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature @@ -119,14 +150,18 @@ defmodule ETH.Transaction do |> 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: :mixed) end) + |> Enum.map(fn(x) -> + # IEx.pry + Base.decode16!(x, case: :mixed) + end) |> ExRLP.encode end defp adjust_v_for_chain_id(transaction = %{ # NOTE: this is probably not correct to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, - nonce: _nonce, chain_id: chain_id, v: v, r: r, s: s + nonce: _nonce, v: v, r: r, s: s }) do + chain_id = Map.get(transaction, :chain_id, 0) if chain_id > 0 do current_v_bytes = Base.decode16!(v, case: :mixed) |> :binary.decode_unsigned target_v_bytes = current_v_bytes + (chain_id * 2 + 8) @@ -136,68 +171,73 @@ defmodule ETH.Transaction do end end - defp to_list(transaction = %{ + 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)) + v = Map.get(transaction, :v, Base.encode16(<<28>>, case: :lower)) # this probably needs to change 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 + |> Enum.map(fn(value) -> to_buffer(value) end) end - def decode_transaction_list(transaction_list=[]) do - # encoded_list = transaction_list # NOTE: remove this line - # encoded_list = transaction_list |> Enum.map(fn(value) -> buffer_decoder(value) end) - - params = %{ - nonce: Enum.at(encoded_list, 0), - gas_price: Enum.at(encoded_list, 1), - gas_limit: Enum.at(encoded_list, 2), - to: Enum.at(encoded_list, 3), - value: Enum.at(encoded_list, 4), - data: Enum.at(encoded_list, 5) - } - - if transaction_list |> length > 6 do - params |> Map.merge(%{ - v: Enum.at(encoded_list, 6), - r: Enum.at(encoded_list, 7), - s: Enum.at(encoded_list, 8) - }) - else - params - end - end + # def decode_transaction_list(transaction_list) when is_list(transaction_list) do + # encoded_list = transaction_list |> Enum.map(fn(value) -> Base.encode16(buffer_decoder(value)) end) + # + # params = %{ + # nonce: Enum.at(encoded_list, 0), + # gas_price: Enum.at(encoded_list, 1), + # gas_limit: Enum.at(encoded_list, 2), + # to: Enum.at(encoded_list, 3), + # value: Enum.at(encoded_list, 4), + # data: Enum.at(encoded_list, 5) + # } + # + # if transaction_list |> length > 6 do + # params |> Map.merge(%{ + # v: Enum.at(encoded_list, 6), + # r: Enum.at(encoded_list, 7), + # s: Enum.at(encoded_list, 8) + # }) + # else + # params + # end + # end def to_hex(value), do: HexPrefix.encode(value) + # def to_json() # transaction_map or transaction_list - defp add_0_for_uneven_encoding(value) do - case rem(String.length(value), 2) == 1 do - true -> "0" <> value - false -> value - end + defp get_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 - "0x" <> v_string = v - {sig_v, _} = Integer.parse(v_string, 16) + 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 buffer_to_int(""), do: 0 defp buffer_to_int(data) do - "0x" <> v_string = v - {number, _} = Integer.parse(v_string, 16) + {number, _} = Integer.parse(data, 16) number end - defp buffer_encode(data) do + # defp buffer_decoder("0x"), do: "" + # defp buffer_decoder("0x" <> data), do: Base.decode16!(data, case: :mixed) + + defp buffer_to_json_value(data) do "0x" <> Base.encode16(data, case: :mixed) end defp to_buffer(nil), do: "" - defp to_buffer(data) when is_number(data), do: Hexate.encode(data) + defp to_buffer(data) when is_number(data) do + IO.puts("NUMBER CALLED") + IEx.pry + pad_to_even(Hexate.encode(data)) |> Base.decode16!(case: :mixed) + end defp to_buffer("0x" <> data) do padded_data = pad_to_even(data) case Base.decode16(padded_data, case: :mixed) do @@ -205,13 +245,34 @@ defmodule ETH.Transaction do _ -> data end end - defp to_buffer(data), do: data + defp to_buffer(data), do: data # NOTE: to_buffer else if (v === null || v === undefined) { v = Buffer.allocUnsafe(0) } - defp pad_to_even(data) do - if rem(String.length(data), 2) == 1 do - "0#{data}" - else - data - end + def pad_to_even(data) do + if rem(String.length(data), 2) == 1, do: "0#{data}", else: data end + + # def verify_signature(transaction) do + # end end + + + + + +# def get_sender_address(signature) do +# transaction_list = signature +# |> ExRLP.decode +# |> Enum.map(fn(value) -> "0x" <> Base.encode16(value) end) +# +# v = transaction_list |> Enum.at(6) |> String.slice(2..-1) |> Hexate.to_integer +# r = transaction_list |> Enum.at(7) |> String.slice(2..-1) +# s = transaction_list |> Enum.at(8) |> String.slice(2..-1) +# +# message_hash = hash(transaction_list, false) +# signature = r <> s +# recovery_id = v - 27 +# +# {:ok, public_key} = :libsecp256k1.ecdsa_recover_compact(message_hash, signature, :uncompressed, recovery_id) +# +# get_address(public_key) +# end diff --git a/lib/eth/utils.ex b/lib/eth/utils.ex index 147fe97..375f0c6 100644 --- a/lib/eth/utils.ex +++ b/lib/eth/utils.ex @@ -57,6 +57,10 @@ defmodule ETH.Utils do [signature: signature, recovery: recovery] end + def secp256k1_recover(signature) do + + end + def keccak256(data), do: :keccakf1600.hash(:sha3_256, data) def encode16(value), do: Base.encode16(value, case: :lower) def decode16(value), do: Base.decode16!(value, case: :mixed) diff --git a/test/eth/transaction_test.exs b/test/eth/transaction_test.exs index d0eb5e8..289df8b 100644 --- a/test/eth/transaction_test.exs +++ b/test/eth/transaction_test.exs @@ -1,3 +1,4 @@ +require IEx defmodule ETH.TransactionTest do use ExUnit.Case import ETH.Utils @@ -31,12 +32,14 @@ defmodule ETH.TransactionTest do first_transaction_list = @transactions |> Enum.at(2) |> Map.get("raw") second_transaction_list = @transactions |> Enum.at(3) |> Map.get("raw") + # |> ETH.Transaction.parse |> ETH.Transaction.to_list IO.inspect(first_transaction_list) assert ETH.Transaction.hash(first_transaction_list) == decode16("375a8983c9fc56d7cfd118254a80a8d7403d590a6c9e105532b67aca1efb97aa") assert ETH.Transaction.hash(first_transaction_list, false) == decode16("61e1ec33764304dddb55348e7883d4437426f44ab3ef65e6da1e025734c03ff0") assert ETH.Transaction.hash(first_transaction_list, true) == decode16("375a8983c9fc56d7cfd118254a80a8d7403d590a6c9e105532b67aca1efb97aa") + IEx.pry assert ETH.Transaction.hash(second_transaction_list) == decode16("0f09dc98ea85b7872f4409131a790b91e7540953992886fc268b7ba5c96820e4") assert ETH.Transaction.hash(second_transaction_list, true) == decode16("0f09dc98ea85b7872f4409131a790b91e7540953992886fc268b7ba5c96820e4") assert ETH.Transaction.hash(second_transaction_list, false) == decode16("f97c73fdca079da7652dbc61a46cd5aeef804008e057be3e712c43eac389aaf0") @@ -62,7 +65,7 @@ defmodule ETH.TransactionTest do test "sign/2 works" do signature = ETH.Transaction.sign(@first_example_transaction, @first_example_wallet.private_key) - |> Base.encode16(case: :lower) + |> Base.encode16(case: :lower) assert signature == "f889808609184e72a00082271094000000000000000000000000000000000000000080a47f746573743200000000000000000000000000000000000000000000000000000060005729a0f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95aa05b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e" end diff --git a/test/eth/transactions_test.exs b/test/eth/transactions_test.exs index e32687d..2b517a9 100644 --- a/test/eth/transactions_test.exs +++ b/test/eth/transactions_test.exs @@ -1,3 +1,4 @@ +require IEx defmodule ETH.TransactionsTest do use ExUnit.Case @@ -19,15 +20,23 @@ defmodule ETH.TransactionsTest do # # end - test "can get transactions sender address after signing it" do - @transactions |> Enum.each(fn(transaction) -> - transaction_params = ETH.Transaction.decode_transaction_list(transaction.raw) - signature = ETH.Transaction.sign_transaction(transaction_params, transaction.privateKey) - assert ETH.Transaction.get_sender_address(signature) == transaction.sendersAddress - end) - end + # test "can get transactions sender address after signing it" do + # @transactions |> Enum.each(fn(transaction) -> + # private_key = Map.get(transaction, "privateKey") + # senders_address = Map.get(transaction, "sendersAddress") + # if private_key != nil do + # decoded_private_key = Base.decode16!(private_key, case: :mixed) + # # IO.inspect(decoded_private_key) + # transaction_params = ETH.Transaction.decode_transaction_list(Map.get(transaction, "raw")) + # IEx.pry + # signature = ETH.Transaction.sign_transaction(transaction_params, decoded_private_key) + # IEx.pry + # assert ETH.Transaction.get_sender_address(signature) == senders_address + # end + # end) + # end - # test "can get transactions sender public kye after signing it" do + # test "can get transactions sender public key after signing it" do # # end @@ -36,7 +45,9 @@ defmodule ETH.TransactionsTest do # there are upfront gas/costs tests that we probably dont need # test "verify EIP155 Signature based on Vitalik\'s tests" do - # + # @eip155_transactions |> Enum.each(fn(transaction) -> + # IEx.pry + # end) # end end