Skip to content

Commit

Permalink
Refactors transaction types to use new wallet handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesduncombe committed Jan 31, 2024
1 parent 099b1d2 commit 4399665
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 79 deletions.
4 changes: 2 additions & 2 deletions lib/tt_eth/behaviours/transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ defmodule TTEth.Behaviours.Transaction do
@type opts :: Keyword.t()

@type transaction :: struct()
@type private_key :: binary()
@type wallet :: TTEth.Wallet.t()

@callback new(to_address, abi_data, nonce, opts) :: transaction

@callback build(transaction, private_key) :: binary()
@callback build(transaction, wallet) :: binary()
end
67 changes: 32 additions & 35 deletions lib/tt_eth/transactions/eip1559_transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ defmodule TTEth.Transactions.EIP1559Transaction do
SEE: https://eips.ethereum.org/EIPS/eip-1559
"""
alias TTEth.{BitHelper, Secp256k1}
alias TTEth.{BitHelper, Wallet}
alias TTEth.Behaviours.Transaction
alias TTEth.Type.Signature, as: EthSignature
import BitHelper, only: [encode_unsigned: 1]

@behaviour Transaction
Expand All @@ -30,6 +31,8 @@ defmodule TTEth.Transactions.EIP1559Transaction do
# SEE: https://eips.ethereum.org/EIPS/eip-1559
@transaction_type 2

@base_recovery_id 27

@doc """
Creates a new type 2 transaction that can be signed.
"""
Expand All @@ -53,24 +56,22 @@ defmodule TTEth.Transactions.EIP1559Transaction do
This will return a binary which can then be base16 encoded etc.
"""
@impl Transaction
def build(%__MODULE__{} = trx, private_key),
def build(%__MODULE__{} = trx, %Wallet{} = wallet),
do:
trx
|> sign_transaction(private_key)
|> sign_transaction(wallet)
|> serialize(_include_signature = true)
|> rlp_encode()
|> put_transaction_envelope(trx)

@doc """
Delegate to ExRLP to RLP encode values.
"""
def rlp_encode(data),
## Private.

# Delegate to ExRLP to RLP encode values.
defp rlp_encode(data),
do: data |> ExRLP.encode()

@doc """
Encodes a transaction such that it can be RLP-encoded.
"""
def serialize(%__MODULE__{} = trx, include_vrs \\ true),
# Encodes a transaction such that it can be RLP-encoded.
defp serialize(%__MODULE__{} = trx, include_vrs \\ true),
do:
[
trx.chain_id |> encode_unsigned(),
Expand All @@ -85,47 +86,38 @@ defmodule TTEth.Transactions.EIP1559Transaction do
]
|> maybe_add_yrs(trx, include_vrs)

@doc """
Returns a ECDSA signature (v,r,s) for a given hashed value.
"""
def sign_hash(hash, private_key) do
{:ok, {<<r::size(256), s::size(256)>>, v}} = Secp256k1.ecdsa_sign_compact(hash, private_key)

{v, r, s}
end
# Returns a ECDSA signature (v,r,s) for a given hashed value.
defp sign_hash(hash, %Wallet{} = wallet),
do:
wallet
|> Wallet.sign(hash)
|> EthSignature.compact_to_components!()
|> recovery_id_to_parity!()

@doc """
Returns a hash of a given transaction.
"""
def transaction_hash(%__MODULE__{} = trx),
# Returns a hash of a given transaction.
defp transaction_hash(%__MODULE__{} = trx),
do:
trx
|> serialize(_include_signature = false)
|> rlp_encode()
|> put_transaction_envelope(trx)
|> TTEth.keccak()

@doc """
Takes a given transaction and returns a version signed with the given private key.
"""
def sign_transaction(%__MODULE__{} = trx, private_key) when is_binary(private_key) do
# Takes a given transaction and returns a version signed with the given private key.
defp sign_transaction(%__MODULE__{} = trx, %Wallet{} = wallet) do
{y_parity, r, s} =
trx
|> transaction_hash()
|> sign_hash(private_key)
|> sign_hash(wallet)

%{trx | y_parity: y_parity, r: r, s: s}
end

@doc """
Wraps the RLP encoded transaction in a transaction envelope.
SEE: https://eips.ethereum.org/EIPS/eip-2718
"""
def put_transaction_envelope(encoded, %__MODULE__{} = trx) when is_binary(encoded),
# Wraps the RLP encoded transaction in a transaction envelope.
# SEE: https://eips.ethereum.org/EIPS/eip-2718
defp put_transaction_envelope(encoded, %__MODULE__{} = trx) when is_binary(encoded),
do: <<trx.type>> <> encoded

## Private.

# Optionally add the YRS values.
defp maybe_add_yrs(base, %__MODULE__{} = trx, _include_vrs = true),
do:
Expand All @@ -137,4 +129,9 @@ defmodule TTEth.Transactions.EIP1559Transaction do
]

defp maybe_add_yrs(base, %__MODULE__{}, _dont_include_vrs), do: base

# Switch the recovery ID to the Y parity.
# SEE: https://eips.ethereum.org/EIPS/eip-1559#specification
defp recovery_id_to_parity!({v, r, s}) when v in [27, 28],
do: {v - @base_recovery_id, r, s}
end
66 changes: 31 additions & 35 deletions lib/tt_eth/transactions/legacy_transaction.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ defmodule TTEth.Transactions.LegacyTransaction do
@moduledoc """
Ported from [`Blockchain`](https://hex.pm/packages/blockchain).
"""
alias TTEth.{BitHelper, Secp256k1}
alias TTEth.{BitHelper, Wallet}
alias TTEth.Behaviours.Transaction
alias TTEth.Type.Signature, as: EthSignature
import BitHelper, only: [encode_unsigned: 1]

@behaviour Transaction

@type private_key :: <<_::256>>

@type hash_v :: integer()
@type hash_r :: integer()
@type hash_s :: integer()
Expand Down Expand Up @@ -60,10 +60,10 @@ defmodule TTEth.Transactions.LegacyTransaction do
}

@impl Transaction
def build(%__MODULE__{} = trx, private_key),
def build(%__MODULE__{} = trx, %Wallet{} = wallet),
do:
trx
|> sign_transaction(private_key)
|> sign_transaction(wallet)
|> serialize(_include_signature = true)
|> rlp_encode()

Expand Down Expand Up @@ -95,20 +95,20 @@ defmodule TTEth.Transactions.LegacyTransaction do
@spec serialize(t) :: ExRLP.t()
def serialize(trx, include_vrs \\ true) do
base = [
trx.nonce |> BitHelper.encode_unsigned(),
trx.gas_price |> BitHelper.encode_unsigned(),
trx.gas_limit |> BitHelper.encode_unsigned(),
trx.nonce |> encode_unsigned(),
trx.gas_price |> encode_unsigned(),
trx.gas_limit |> encode_unsigned(),
trx.to,
trx.value |> BitHelper.encode_unsigned(),
trx.value |> encode_unsigned(),
if(trx.to == <<>>, do: trx.init, else: trx.data)
]

if include_vrs do
base ++
[
trx.v |> BitHelper.encode_unsigned(),
trx.r |> BitHelper.encode_unsigned(),
trx.s |> BitHelper.encode_unsigned()
trx.v |> encode_unsigned(),
trx.r |> encode_unsigned(),
trx.s |> encode_unsigned()
]
else
base
Expand All @@ -122,39 +122,33 @@ defmodule TTEth.Transactions.LegacyTransaction do
## Examples
iex> LegacyTransaction.sign_hash(<<2::256>>, <<1::256>>)
iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>)
iex> LegacyTransaction.sign_hash(<<2::256>>, wallet)
{28,
38938543279057362855969661240129897219713373336787331739561340553100525404231,
23772455091703794797226342343520955590158385983376086035257995824653222457926}
iex> LegacyTransaction.sign_hash(<<5::256>>, <<1::256>>)
iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>)
iex> LegacyTransaction.sign_hash(<<5::256>>, wallet)
{27,
74927840775756275467012999236208995857356645681540064312847180029125478834483,
56037731387691402801139111075060162264934372456622294904359821823785637523849}
iex> data = "ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080" |> TTEth.BitHelper.from_hex
iex> hash = data |> TTEth.keccak()
iex> private_key = "4646464646464646464646464646464646464646464646464646464646464646" |> TTEth.BitHelper.from_hex
iex> LegacyTransaction.sign_hash(hash, private_key, 1)
iex> wallet = TTEth.Wallet.from_private_key(private_key)
iex> LegacyTransaction.sign_hash(hash, wallet, 1)
{ 37, 18515461264373351373200002665853028612451056578545711640558177340181847433846, 46948507304638947509940763649030358759909902576025900602547168820602576006531 }
"""
@spec sign_hash(BitHelper.keccak_hash(), private_key, integer() | nil) ::
@spec sign_hash(BitHelper.keccak_hash(), Wallet.t(), integer() | nil) ::
{hash_v, hash_r, hash_s}
def sign_hash(hash, private_key, chain_id \\ nil) do
{:ok, {<<r::size(256), s::size(256)>>, recovery_id}} =
Secp256k1.ecdsa_sign_compact(hash, private_key)

# Fork Ψ EIP-155
recovery_id =
if chain_id do
chain_id * 2 + @base_recovery_id_eip_155 + recovery_id
else
@base_recovery_id + recovery_id
end

{recovery_id, r, s}
end
def sign_hash(hash, %Wallet{} = wallet, chain_id \\ nil),
do:
wallet
|> Wallet.sign(hash)
|> EthSignature.compact_to_components!(chain_id)

@doc """
Returns a hash of a given transaction according to the
Expand Down Expand Up @@ -192,19 +186,21 @@ defmodule TTEth.Transactions.LegacyTransaction do
## Examples
iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>)
iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>)
iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, wallet)
%LegacyTransaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 97037709922803580267279977200525583527127616719646548867384185721164615918250, s: 31446571475787755537574189222065166628755695553801403547291726929250860527755, to: "", v: 27, value: 5}
iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>, 1)
iex> wallet = TTEth.Wallet.from_private_key(_private_key = <<1::256>>)
iex> LegacyTransaction.sign_transaction(%LegacyTransaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, wallet, 1)
%LegacyTransaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 25739987953128435966549144317523422635562973654702886626580606913510283002553, s: 41423569377768420285000144846773344478964141018753766296386430811329935846420, to: "", v: 38, value: 5}
"""
@spec sign_transaction(__MODULE__.t(), private_key, integer() | nil) :: __MODULE__.t()
def sign_transaction(trx, private_key, chain_id \\ nil) do
@spec sign_transaction(__MODULE__.t(), Wallet.t(), integer() | nil) :: __MODULE__.t()
def sign_transaction(trx, %Wallet{} = wallet, chain_id \\ nil) do
{v, r, s} =
trx
|> transaction_hash(chain_id)
|> sign_hash(private_key, chain_id)
|> sign_hash(wallet, chain_id)

%{trx | v: v, r: r, s: s}
end
Expand Down
22 changes: 15 additions & 7 deletions test/tt_eth/transactions/eip1559_transaction_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do
use TTEth.Case
alias TTEth.Transactions.EIP1559Transaction
alias TTEth.Type.{Address, PrivateKey, PublicKey}
alias TTEth.Wallet

# Polygon Mumbai.
@chain_id 80001

@private_key_human "0x62aa6ec41b56439d2c5df352c45a00389cef262b3761e13c6481e35ab027d262"
@private_key @private_key_human |> PrivateKey.from_human!()
@to_address_human "0x38f153fdd399ff2cf64704c6a4b16d3fd9ddcd69"
@to_address @to_address_human |> Address.from_human!()
# transfer(address,uint256)
Expand Down Expand Up @@ -51,27 +51,32 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do
end

describe "build/2" do
setup :build_trx
setup [
:build_trx,
:build_wallet
]

test "builds a signed transaction", %{trx: trx} do
test "builds a signed transaction", %{trx: trx, wallet: wallet} do
trx
|> EIP1559Transaction.build(@private_key)
|> EIP1559Transaction.build(wallet)
|> encode_and_pad()
|> assert_match(@valid_transaction_data)
end

test "from address is correct when checking signature", %{trx: trx} do
test "from address is correct when checking signature", %{trx: trx, wallet: wallet} do
# Build the trx_data but randomize the nonce.
built_trx_data =
%{trx | nonce: Enum.random(10..100)}
|> EIP1559Transaction.build(@private_key)
|> EIP1559Transaction.build(wallet)
|> encode_and_pad()

# Decode the transaction data.
decoded = built_trx_data |> fully_decode_trx_data()

# Get signature params.
[y_parity, r, s] = decoded |> Enum.take(_signature_params = -3)
[y_parity, r, s] =
decoded
|> Enum.take(_signature_params = -3)

# Get the raw public key from the signature.
{:ok, public_raw} =
Expand Down Expand Up @@ -117,6 +122,9 @@ defmodule TTEth.Transactions.EIP1559TransactionTest do
}
}

defp build_wallet(_),
do: %{wallet: @private_key_human |> Wallet.from_private_key()}

## Helpers.

defp encode_and_pad(bin),
Expand Down

0 comments on commit 4399665

Please sign in to comment.