Skip to content

Commit

Permalink
ongoing changes
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Sep 4, 2017
1 parent 2e55c1c commit 20cf997
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 92 deletions.
225 changes: 143 additions & 82 deletions lib/eth/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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(<<transaction_rlp>>), 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, "")
Expand All @@ -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)
Expand All @@ -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(<<chain_id>>), 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 ->
Expand All @@ -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
Expand All @@ -119,14 +150,18 @@ defmodule ETH.Transaction do
|> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<<recovery + 27>>)})
|> 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)
Expand All @@ -136,82 +171,108 @@ 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
{:ok, decoded_binary} -> decoded_binary
_ -> 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
4 changes: 4 additions & 0 deletions lib/eth/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion test/eth/transaction_test.exs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require IEx
defmodule ETH.TransactionTest do
use ExUnit.Case
import ETH.Utils
Expand Down Expand Up @@ -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")
Expand All @@ -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

Expand Down
Loading

0 comments on commit 20cf997

Please # to comment.