Skip to content

Commit

Permalink
TRANSACTION SIGNING WORKS!
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Sep 5, 2017
1 parent 35a1845 commit 5f95dce
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 104 deletions.
209 changes: 107 additions & 102 deletions lib/eth/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -96,39 +108,38 @@ 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

{:ok, public_key} = :libsecp256k1.ecdsa_recover_compact(message_hash, signature, :uncompressed, recovery_id)
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,
Expand Down Expand Up @@ -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(<<recovery + 27>>)})
|> 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(<<recovery + 27>>)})
# |> 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)
Expand All @@ -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_v>>, 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
Expand All @@ -239,43 +269,18 @@ defmodule ETH.Transaction do
end

# defp buffer_to_int(""), do: 0
defp buffer_to_int(data) do
def buffer_to_int(data) do
<<number>> = to_buffer(data)
number
end

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
Expand Down
18 changes: 16 additions & 2 deletions test/eth/transaction_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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) ->
Expand All @@ -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)
Expand Down

0 comments on commit 5f95dce

Please # to comment.