Skip to content

Commit

Permalink
major progress on transaction signing but not finished
Browse files Browse the repository at this point in the history
  • Loading branch information
izelnakri committed Aug 27, 2017
0 parents commit 32624b2
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Eth

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `eth` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[
{:eth, "~> 0.1.0"}
]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/eth](https://hexdocs.pm/eth).

30 changes: 30 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure your application as:
#
# config :eth, key: :value
#
# and access this configuration in your application as:
#
# Application.get_env(:eth, :key)
#
# You can also configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
129 changes: 129 additions & 0 deletions lib/eth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
require IEx
# elliptic curve cryptography library for Ethereum
defmodule ETH do
@moduledoc """
Documentation for Eth.
"""

@doc """
Hello world.
## Examples
iex> Eth.hello
:world
"""

def private_key_to_address(<< private_key :: binary-size(32) >>) do
private_key
|> private_key_to_public_key()
|> public_key_to_address()
end

def private_key_to_public_key(<< private_key :: binary-size(32) >>) do
{public_key, ^private_key} = :crypto.generate_key(:ecdh, :secp256k1, private_key)
public_key
end

def public_key_to_address(<< 4 :: size(8), key :: binary-size(64) >>) do
<< _ :: binary-size(12), address :: binary-size(20) >> = keccak256(key)
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
hash = hash_transaction(transaction)
[signature: signature, recovery_id: recovery_id] = secp256k1_signature(hash, private_key)

# IEx.pry

<< 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 })
|> 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' ]
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) })
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]
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!
|> transaction_list
|> hash
end

def hash(transaction_list) do # NOTE: expects all values in base16
# IEx.pry
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]
%{
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)
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)}"
end
30 changes: 30 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule Eth.Mixfile do
use Mix.Project

def project do
[
app: :eth,
version: "0.1.0",
elixir: "~> 1.5",
start_permanent: Mix.env == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:libsecp256k1, [github: "mbrix/libsecp256k1", manager: :rebar]},
{:keccakf1600, git: "https://github.com/jur0/erlang-keccakf1600", branch: "original-keccak"},
{:ex_rlp, "~> 0.2.1"},
{:hexate, "~> 0.6.1"}
]
end
end
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%{"ex_rlp": {:hex, :ex_rlp, "0.2.1", "bd320900d6316cdfe01d365d4bda22eb2f39b359798daeeffd3bd1ca7ba958ec", [:mix], []},
"hexate": {:hex, :hexate, "0.6.1", "1cea42e462c1daa32223127d4752e71016c3d933d492b9bb7fa4709a4a0fd50d", [:mix], []},
"keccakf1600": {:git, "https://github.com/jur0/erlang-keccakf1600", "a87d31d32250091262e0da5ee2c2a4535bc50945", [branch: "original-keccak"]},
"libsecp256k1": {:git, "https://github.com/mbrix/libsecp256k1.git", "671be513a6c19db47fbeea0ceefbf61421d196cc", []}}
45 changes: 45 additions & 0 deletions test/eth_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule ETHTest do
use ExUnit.Case

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

test "hash/1 works" do
target_hash = "5C207A650B59A8C2D1271F5CBDA78A658CB411A87271D68062E61AB1A3F85CF9"
assert ETH.hash(@first_transaction_list) |> Base.encode16 == target_hash
end

test "secp256k1_signature/2 works" do
hash = "5c207a650b59a8c2d1271f5cbda78a658cb411a87271d68062e61ab1a3f85cf9"
|> Base.decode16!(case: :mixed)
private_key = "e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109"
|> Base.decode16!(case: :mixed)

target_signature = "c2a738b1eb84280399115f4bec9e52b8de494a3ea7d9f069277119a02de4a49876f3168913e968e9484e2e0e447cd7adc56505e25cbc372330793a31f0bf7195"

assert ETH.secp256k1_signature(hash, private_key)[:signature] |> Base.encode16(case: :lower) == target_signature
end

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

assert signature == "f889808609184e72a00082271094000000000000000000000000000000000000000080a47f746573743200000000000000000000000000000000000000000000000000000060005729a0f2d54d3399c9bcd3ac3482a5ffaeddfe68e9a805375f626b4f2f8cf530c2d95aa05b3bb54e6e8db52083a9b674e578c843a87c292f0383ddba168573808d36dc8e"
end
end
1 change: 1 addition & 0 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ExUnit.start()

0 comments on commit 32624b2

Please # to comment.