defmodule EthWallet.Transaction do
@moduledoc """
Tx about Ethereum.
"""
alias EthWallet.Utils.{Crypto, TypeTranslator}
alias EthWallet.Transaction
defstruct nonce: 0,
gas_price: 0,
gas_limit: 0,
to: <<>>,
value: 0,
v: nil,
r: nil,
s: nil,
init: <<>>,
data: <<>>
@type t :: %__MODULE__{
nonce: integer(),
gas_price: integer(),
gas_limit: integer(),
to: <<_::160>> | <<_::0>>,
value: integer(),
v: integer(),
r: integer(),
s: integer(),
# contract machine code
init: binary(),
data: binary()
}
def build_tx(to_str, value, data, nonce, gas_price, gas_limit) do
to_bin =
TypeTranslator.addr_to_bin(to_str)
%Transaction{
nonce: nonce,
gas_price: gas_price,
gas_limit: gas_limit,
to: to_bin,
value: value,
init: <<>>,
data: data
}
end
@spec signed_tx_to_raw_tx(Transaction.t()) :: String.t()
def signed_tx_to_raw_tx(signed_tx) do
raw_tx =
signed_tx
|> serialize()
|> ExRLP.encode(encoding: :hex)
"0x" <> raw_tx
end
@doc """
> https://b10c.me/blog/006-evolution-of-the-bitcoin-signature-length/
> https://bitcoin.stackexchange.com/questions/12554/why-the-signature-is-always-65-13232-bytes-long
Bitcoin: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
Attetion: SigHash Flag should be append to the last in finally signature.
Ethereum: R <> S <> V (V = recid + chainID * 2 + 35)
"""
@spec sign_tx(Transaction.t(), binary(), integer() | nil) :: Transaction.t()
def sign_tx(tx, private_key, chain_id \\ nil) do
%{v: v, r: r, s: s} =
tx
|> hash_for_signing(chain_id)
|> Crypto.sign_compact(private_key, chain_id)
%{tx | v: v, r: r, s: s}
end
@spec hash_for_signing(Transaction.t(), integer() | nil) :: binary()
def hash_for_signing(tx, chain_id \\ nil) do
# See EIP-155
tx
|> serialize(false)
|> Kernel.++(if chain_id, do: [:binary.encode_unsigned(chain_id), <<>>, <<>>], else: [])
|> ExRLP.encode(encoding: :binary)
|> Crypto.kec()
end
@spec serialize(Transaction.t(), boolean()) :: ExRLP.t()
def serialize(tx, include_vrs \\ true) do
base = [
encode_unsigned(tx.nonce),
encode_unsigned(tx.gas_price),
encode_unsigned(tx.gas_limit),
tx.to,
encode_unsigned(tx.value),
if(tx.to == <<>>, do: tx.init, else: tx.data)
]
if include_vrs do
base ++
[
encode_unsigned(tx.v),
encode_unsigned(tx.r),
encode_unsigned(tx.s)
]
else
base
end
end
@doc """
iex(80)> :binary.encode_unsigned(12345)
"09"
iex(81)> "09" <> <<0>>
<<48, 57, 0>>
iex(83)> 48 * 256 + 57
12345
"""
@spec encode_unsigned(number()) :: binary()
def encode_unsigned(0), do: <<>>
def encode_unsigned(n), do: :binary.encode_unsigned(n)
end