Skip to content

Commit

Permalink
COMPLETE WORKING VERSION v1
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Aug 28, 2017
1 parent 32624b2 commit c34c67e
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 67 deletions.
86 changes: 26 additions & 60 deletions lib/eth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,99 +31,65 @@ defmodule ETH do
address
end

def get_number(result, denomination \\ :ether) do
result |> elem(1) |> Map.get("result") |> String.slice(2..-1) |> Hexate.to_integer
end

def sign_transaction(transaction, private_key) do
def sign_transaction(transaction, private_key) do # must have chain_id
hash = hash_transaction(transaction)
[signature: signature, recovery_id: recovery_id] = secp256k1_signature(hash, private_key)

# IEx.pry
decoded_private_key = Base.decode16!(private_key, case: :lower)
[signature: signature, recovery: recovery] = secp256k1_signature(hash, decoded_private_key)

<< r :: binary-size(32) >> <> << s :: binary-size(32) >> = signature

IEx.pry

# IO.puts("r hex is:")
# IO.puts(r)
# IO.puts("s hex is:")
# IO.puts(s)

transaction
|> Map.merge(%{r: r, s: s, v: recovery_id + 27 })
|> Map.merge(%{r: encode16(r), s: encode16(s), v: encode16(<<recovery + 27>>)})
|> adjust_v_for_chain_id
|> ExRLP.encode(encoding: :hex)

# const ret = {}
# ret.r = sig.signature.slice(0, 32)
# ret.s = sig.signature.slice(32, 64)
# ret.v = sig.recovery + 27
# return re # append to this.raw

# then

# if (this._chainId > 0) { # chainId 0 by default
# sig.v += this._chainId * 2 + 8
# }


# v '1c',
# r '2b40675300e8c453ecda0e71af527b2f238bef018372f6b0d194bc5307e5ba05',
# s '5872815809435f8e977bbd0d8ac6a65e96803a177506dd0dcfeefd17cb998da6' ]
|> transaction_list
|> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end)
|> ExRLP.encode
end

def adjust_v_for_chain_id(transaction) do
if transaction.chain_id > 0 do
transaction |> Map.merge(%{ v: transaction.v + (transaction.chain_id * 2 + 8) })
current_v_bytes = Base.decode16!(transaction.v, case: :lower) |> :binary.decode_unsigned
transaction |> Map.merge(%{v: encode16(<< current_v_bytes + (transaction.chain_id * 2 + 8) >>) })
else
transaction
end
end

def secp256k1_signature(hash, private_key) do
{:ok, signature, recovery_id} = :libsecp256k1.ecdsa_sign_compact(hash, private_key, :default, <<>>)
[signature: signature, recovery_id: recovery_id]
{:ok, signature, recovery} = :libsecp256k1.ecdsa_sign_compact(hash, private_key, :default, <<>>)
[signature: signature, recovery: recovery]
end

# must have [nonce, gasPrice, gasLimit, to, value, data] # and chainId inside the transaction?
def hash_transaction(transaction) do
transaction |> Map.merge(%{v: transaction.chain_id, r: 0, s: 0}) # 0s here are problematic!
# EIP155 spec:
# 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
transaction |> Map.merge(%{v: encode16(<<transaction.chain_id>>), r: <<>>, s: <<>> })
|> transaction_list
|> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end)
|> hash
end

def hash(transaction_list) do # NOTE: expects all values in base16
# IEx.pry
def hash(transaction_list) do
transaction_list
|> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end) # this could be complicated for empty values
|> ExRLP.encode
|> keccak256
end

def keccak256(data), do: :keccakf1600.hash(:sha3_256, data)

def transaction_list(transaction \\ %{}) do # [nonce, gasPrice, gasLimit, to, value, data, v(1c), r, s]
def transaction_list(transaction \\ %{}) do
%{
nonce: nonce, gas_price: gas_price, gas_limit: gas_limit, to: to, value: value, data: data
} = transaction

[nonce, gas_price, gas_limit, to, value, data] |> append_signature(transaction)
end

defp append_signature(transaction_list, transaction) do
append_to_list_if_exists(transaction_list, transaction, :v)
|> append_to_list_if_exists(transaction, :r)
|> append_to_list_if_exists(transaction, :s)
v = if Map.get(transaction, :v), do: transaction.v, else: Base.encode16(<<28>>, case: :lower)
r = default_is_empty(transaction, :r)
s = default_is_empty(transaction, :s)
[nonce, gas_price, gas_limit, to, value, data, v, r, s]
end

defp append_to_list_if_exists(transaction_list, transaction, key) do
case Map.fetch(transaction, key) do
:error -> transaction_list
{:ok, nil} -> transaction_list
{:ok, value} -> transaction_list ++ [value]
end
end

# def hexate_encode(value), do: "0x#{Hexate.encode(value)}"
def keccak256(data), do: :keccakf1600.hash(:sha3_256, data)
defp encode16(value), do: Base.encode16(value, case: :lower)
defp default_is_empty(map, key), do: if Map.get(map, key), do: Map.get(map, key), else: ""
end
21 changes: 14 additions & 7 deletions test/eth_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ defmodule ETHTest do
"7f7465737432000000000000000000000000000000000000000000000000000000600057", "29",
"f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95a",
"5b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e"
]
] |> Enum.map(fn(x) -> Base.decode16!(x, case: :lower) end)

@first_example_wallet %{
private_key: "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109"
}
@first_example_transaction %{
nonce: "0x00",
gas_price: "0x09184e72a000",
gas_limit: "0x2710",
to: "0x0000000000000000000000000000000000000000",
value: "0x00",
data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057",
nonce: "",
gas_price: "09184e72a000",
gas_limit: "2710",
to: "0000000000000000000000000000000000000000",
value: "",
data: "7f7465737432000000000000000000000000000000000000000000000000000000600057",
chain_id: 3 # EIP 155 chainId - mainnet: 1, ropsten: 3
}

Expand All @@ -36,6 +37,12 @@ defmodule ETHTest do
assert ETH.secp256k1_signature(hash, private_key)[:signature] |> Base.encode16(case: :lower) == target_signature
end

test "hash_transaction\2 works" do
result = ETH.hash_transaction(@first_example_transaction) |> Base.encode16(case: :lower)
target_digest = "df2a7cb6d05278504959987a144c116dbd11cbdc50d6482c5bae84a7f41e2113"
assert result == target_digest
end

test "sign_transaction/2 works" do
signature = ETH.sign_transaction(@first_example_transaction, @first_example_wallet.private_key)
|> Base.encode16(case: :lower)
Expand Down

0 comments on commit c34c67e

Please # to comment.