From 5f95dce387b89727cd788d079c4f985f19178ea7 Mon Sep 17 00:00:00 2001 From: Izel Nakri Date: Wed, 6 Sep 2017 01:38:54 +0200 Subject: [PATCH] TRANSACTION SIGNING WORKS! --- lib/eth/transaction.ex | 209 +++++++++++++++++----------------- test/eth/transaction_test.exs | 18 ++- 2 files changed, 123 insertions(+), 104 deletions(-) diff --git a/lib/eth/transaction.ex b/lib/eth/transaction.ex index 3a25724..0a061ca 100644 --- a/lib/eth/transaction.ex +++ b/lib/eth/transaction.ex @@ -71,23 +71,35 @@ defmodule ETH.Transaction do } 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, "") - nonce = Keyword.get(params, :nonce, ETH.Query.get_transaction_count(from)) - chain_id = Keyword.get(params, :chain_id, 3) - gas_limit = Keyword.get(params, :gas_limit, ETH.Query.estimate_gas(%{ - to: to, value: value, data: data, nonce: nonce, chain_id: chain_id - })) - - [ - to: to, value: value, data: data, gas_price: gas_price, gas_limit: gas_limit, nonce: nonce - ] - |> Enum.map(fn(x) -> - {key, value} = x - {key, to_buffer(value)} - end) - |> Enum.into(%{}) + # 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, "") + # nonce = Keyword.get(params, :nonce, ETH.Query.get_transaction_count(from)) + # chain_id = Keyword.get(params, :chain_id, 3) + # gas_limit = Keyword.get(params, :gas_limit, ETH.Query.estimate_gas(%{ + # to: to, value: value, data: data, nonce: nonce, chain_id: chain_id + # })) + # + # [ + # to: to, value: value, data: data, gas_price: gas_price, gas_limit: gas_limit, nonce: nonce + # ] + # |> Enum.map(fn(x) -> + # {key, value} = x + # {key, to_buffer(value)} + # end) + # |> Enum.into(%{}) + # end + + def sign(transaction_list = [ + nonce, gas_price, gas_limit, to, value, data, v, r, s + ], << private_key :: binary-size(32) >>) do + to_signed_transaction_list(transaction_list, private_key) + end + def sign(transaction_list = [ + nonce, gas_price, gas_limit, to, value, data, v, r, s + ], << encoded_private_key :: binary-size(64) >>) do + decoded_private_key = Base.decode16!(encoded_private_key, case: :mixed) + to_signed_transaction_list(transaction_list, decoded_private_key) end def get_sender_address(transaction_list = [nonce, gas_price, gas_limit, to, value, data, v, r, s]) do @@ -96,7 +108,6 @@ defmodule ETH.Transaction do v_int = buffer_to_int(v) target_v = if chain_id > 0, do: v_int - (chain_id * 2 + 8), else: v_int - # NOTE: check if the below is correct: signature = r <> s recovery_id = target_v - 27 @@ -104,31 +115,31 @@ defmodule ETH.Transaction do get_address(public_key) end - def sign(transaction = %{ - to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, - 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 - }, << encoded_private_key :: binary-size(64) >>) do - decoded_private_key = Base.decode16!(encoded_private_key, case: :mixed) - sign_transaction(transaction, decoded_private_key) - end + # def sign(transaction = %{ + # to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + # 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 + # }, << encoded_private_key :: binary-size(64) >>) do + # decoded_private_key = Base.decode16!(encoded_private_key, case: :mixed) + # sign_transaction(transaction, decoded_private_key) + # end def send(signature), do: Ethereumex.HttpClient.eth_send_raw_transaction([signature]) - def send_transaction(params = [from: _from, to: _to, value: _value], private_key) do - set(params) - |> sign(private_key) - |> send - end - def send_transaction(params = %{from: _from, to: _to, value: _value}, private_key) do - Map.to_list(params) - |> set - |> sign(private_key) - |> send - end + # def send_transaction(params = [from: _from, to: _to, value: _value], private_key) do + # set(params) + # |> sign(private_key) + # |> send + # end + # def send_transaction(params = %{from: _from, to: _to, value: _value}, private_key) do + # Map.to_list(params) + # |> set + # |> sign(private_key) + # |> send + # end def hash_transaction(transaction = %{ to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, @@ -161,42 +172,28 @@ defmodule ETH.Transaction do |> keccak256 end - defp sign_transaction(transaction = %{ - to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, - 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 - - # this will change v, r, s - transaction - # |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) - |> adjust_v_for_chain_id - |> to_list - |> 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, 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) - transaction |> Map.merge(%{v: encode16(<< target_v_bytes >>)}) - else - transaction - end - end + # defp sign_transaction(transaction = %{ + # to: _to, value: _value, data: _data, gas_price: _gas_price, gas_limit: _gas_limit, + # 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 + # + # # this will change v, r, s + # transaction + # # |> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<>)}) + # |> adjust_v_for_chain_id + # |> to_list + # |> Enum.map(fn(x) -> + # # IEx.pry + # Base.decode16!(x, case: :mixed) + # end) + # |> ExRLP.encode + # 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) @@ -223,6 +220,39 @@ defmodule ETH.Transaction do # def to_json() # transaction_map or transaction_list + defp to_signed_transaction_list(transaction_list = [ + nonce, gas_price, gas_limit, to, value, data, v, r, s + ], << private_key :: binary-size(32) >>) do + chain_id = get_chain_id(v, Enum.at(transaction_list, 9)) + message_hash = hash(transaction_list, false) + + [signature: signature, recovery: recovery] = secp256k1_signature(message_hash, private_key) + + << sig_r :: binary-size(32) >> <> << sig_s :: binary-size(32) >> = signature + initial_v = recovery + 27 + + sig_v = if chain_id > 0, do: initial_v + (chain_id * 2 + 8), else: initial_v + + [nonce, gas_price, gas_limit, to, value, data, <>, sig_r, sig_s] + end + + def to_buffer(nil), do: "" + def to_buffer(data) when is_number(data), do: pad_to_even(Integer.to_string(data, 16)) + 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 + {:ok, decoded_binary} -> decoded_binary + _ -> data + end + end + def to_buffer(data), do: data + # NOTE: to_buffer else if (v === null || v === undefined) { v = Buffer.allocUnsafe(0) } + + def pad_to_even(data) do + if rem(String.length(data), 2) == 1, do: "0#{data}", else: data + end + 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 @@ -239,7 +269,7 @@ defmodule ETH.Transaction do end # defp buffer_to_int(""), do: 0 - defp buffer_to_int(data) do + def buffer_to_int(data) do <> = to_buffer(data) number end @@ -247,35 +277,10 @@ defmodule ETH.Transaction do defp buffer_to_json_value(data) do "0x" <> Base.encode16(data, case: :mixed) end - - def to_buffer(nil), do: "" - def to_buffer(data) when is_number(data) do - 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 - {:ok, decoded_binary} -> decoded_binary - _ -> data - end - end - def to_buffer(data), do: data # NOTE: to_buffer else if (v === null || v === undefined) { v = Buffer.allocUnsafe(0) } - - 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 diff --git a/test/eth/transaction_test.exs b/test/eth/transaction_test.exs index 15117ef..b5b442a 100644 --- a/test/eth/transaction_test.exs +++ b/test/eth/transaction_test.exs @@ -19,7 +19,7 @@ defmodule ETH.TransactionTest do @transactions File.read!("test/fixtures/transactions.json") |> Poison.decode! @eip155_transactions File.read!("test/fixtures/eip155_vitalik_tests.json") |> Poison.decode! - test "parse/1 and list/1 works for 0x hexed transactions" do + test "parse/1 and to_list/1 works for 0x hexed transactions" do @transactions |> Enum.slice(0..3) |> Enum.map(fn(transaction) -> transaction["raw"] end) @@ -84,7 +84,7 @@ defmodule ETH.TransactionTest do assert result == "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113" end - test "get_sender_address works" do + test "get_sender_address/1 works" do transactons = @transactions |> Enum.slice(0..2) |> Enum.each(fn(transaction) -> @@ -95,6 +95,20 @@ defmodule ETH.TransactionTest do end) end + test "sign/2 works" do + transactons = @transactions + |> Enum.slice(0..2) + |> Enum.each(fn(transaction) -> + signed_transaction_list = transaction + |> Map.get("raw") + |> ETH.Transaction.parse + |> ETH.Transaction.to_list + |> ETH.Transaction.sign(transaction["privateKey"]) + + result = ETH.Transaction.get_sender_address(signed_transaction_list) + assert result == "0x" <> String.upcase(transaction["sendersAddress"]) + end) + end # test "sign/2 works" do # signature = ETH.Transaction.sign(@first_example_transaction, @first_example_wallet.private_key)