defmodule EIP712.Util do
defdelegate keccak(value), to: EIP712.Hash
@doc ~S"""
Decodes a hex string, specifically requiring that the string begins
with `0x` and allows mixed-case typing.
## Examples
iex> EIP712.Util.decode_hex("0x1122")
{:ok, <<0x11, 0x22>>}
iex> EIP712.Util.decode_hex("0x1")
{:ok, <<0x1>>}
iex> EIP712.Util.decode_hex("0xGG")
:error
"""
def decode_hex("0x" <> hex) do
hex_padded =
if rem(byte_size(hex), 2) == 1 do
"0" <> hex
else
hex
end
Base.decode16(hex_padded, case: :mixed)
end
def decode_hex!(hex) do
{:ok, result} = decode_hex(hex)
result
end
@doc ~S"""
Decodes hex, allowing it to either by "0x..." or <<1::160>>.
Note: a hex-printed string, in this case, must start with 0x,
otherwise it will be interpreted as its ASCII values.
## Examples
iex> EIP712.Util.decode_hex_input!("0x55")
<<0x55>>
iex> EIP712.Util.decode_hex_input!(<<0x55>>)
<<0x55>>
"""
def decode_hex_input!(hex = "0x" <> _), do: decode_hex!(hex)
def decode_hex_input!(hex) when is_binary(hex), do: hex
@doc ~S"""
Encodes a hex string, adding a `0x` prefix.
Note: if `short` is set, then any leading zeros will be stripped.
## Examples
iex> EIP712.Util.encode_hex(<<0x11, 0x22>>)
"0x1122"
iex> EIP712.Util.encode_hex(<<0xc>>)
"0x0c"
iex> EIP712.Util.encode_hex(<<0xc>>, true)
"0xc"
iex> EIP712.Util.encode_hex(<<0x0>>, true)
"0x0"
"""
def encode_hex(hex, short \\ false)
def encode_hex(nil, _short), do: nil
def encode_hex(hex, short) when is_binary(hex) do
enc = Base.encode16(hex, case: :lower)
"0x" <>
if short do
case String.replace_leading(enc, "0", "") do
"" ->
"0"
els ->
els
end
else
enc
end
end
def encode_hex(v, short) when is_integer(v), do: encode_hex(:binary.encode_unsigned(v), short)
@doc ~S"""
Encodes a number as a binary representation of a certain number of
bytes.
## Examples
iex> EIP712.Util.encode_bytes(257, 4)
<<0, 0, 1, 1>>
iex> EIP712.Util.encode_bytes(nil, 4)
nil
"""
def encode_bytes(nil, _), do: nil
def encode_bytes(b, size) do
pad(:binary.encode_unsigned(b), size)
end
@doc ~S"""
Pads a binary to a given length
## Examples
iex> EIP712.Util.pad(<<1, 2>>, 2)
<<1, 2>>
iex> EIP712.Util.pad(<<1, 2>>, 4)
<<0, 0, 1, 2>>
iex> EIP712.Util.pad(<<1, 2>>, 1)
** (FunctionClauseError) no function clause matching in EIP712.Util.pad/2
"""
def pad(bin, size) when size > byte_size(bin) do
padding_len_bits = (size - byte_size(bin)) * 8
<<0::size(padding_len_bits)>> <> bin
end
def pad(bin, size) when size == byte_size(bin), do: bin
@doc ~S"""
Returns an Ethereum address from a given DER-encoded public key.
## Examples
iex> public_key = EIP712.Util.decode_hex!("0x0422")
iex> EIP712.Util.get_eth_address(public_key) |> EIP712.Util.encode_hex()
"0x759f1afdc24aba433a3e18b683f8c04a6eaa69f0"
"""
def get_eth_address(public_key) do
<<4, public_key_raw::binary>> = public_key
<<_::bitstring-size(96), address::bitstring-size(160)>> = keccak(public_key_raw)
address
end
@doc ~S"""
Converts a number to wei, possibly from gwei, etc.
## Examples
iex> EIP712.Util.to_wei(100)
100
iex> EIP712.Util.to_wei({100, :gwei})
100000000000
"""
@spec to_wei(integer() | {integer, :gwei}) :: number()
def to_wei(amount) when is_integer(amount), do: amount
def to_wei({amount, :wei}) when is_integer(amount), do: amount
def to_wei({amount, :gwei}) when is_integer(amount), do: amount * 1_000_000_000
@chains %{
mainnet: 1,
ropsten: 2,
rinkeby: 4,
goerli: 5,
kovan: 42
}
@doc ~S"""
Parses a chain id, which can be given as an integer or an atom of a known network.
## Examples
iex> EIP712.Util.parse_chain_id(5)
5
iex> EIP712.Util.parse_chain_id(:goerli)
5
"""
def parse_chain_id(chain_id) when is_atom(chain_id), do: Map.fetch!(@chains, chain_id)
def parse_chain_id(chain_id) when is_integer(chain_id), do: chain_id
@doc ~S"""
Checksums an Ethereum address per [EIP-55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md),
the result is a string-encoded version of the address.
## Examples
iex> EIP712.Util.checksum_address("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"
iex> EIP712.Util.checksum_address("0xFB6916095CA1DF60BB79CE92CE3EA74C37C5D359")
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"
iex> EIP712.Util.checksum_address("0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb")
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"
iex> EIP712.Util.checksum_address("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb")
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
"""
def checksum_address(address = "0x" <> _), do: checksum_address(decode_hex!(address))
def checksum_address(address) when is_binary(address) and byte_size(address) == 20 do
# Weirdly instead of keccaking the address, we keccak the string representation...
"0x" <> address_enc = encode_hex(address)
hash = EIP712.Hash.keccak(String.downcase(address_enc))
# Use a charlist to semi-quickly get the correct hex digit
lower = ~c"0123456789abcdef"
upper = ~c"0123456789ABCDEF"
res =
for {nibble, hash_val} <- Enum.zip(nibbles(address), nibbles(hash)), into: [] do
casing = if hash_val >= 8, do: upper, else: lower
Enum.at(casing, nibble)
end
"0x" <> to_string(res)
end
@doc ~S"""
Returns the nibbles of a binary as a list.
## Examples
iex> EIP712.Util.nibbles(<<0xF5,0xE6,0xD0>>)
[0xF, 0x5, 0xE, 0x6, 0xD, 0x0]
"""
def nibbles(v) do
Enum.reverse(do_nibbles(v, []))
end
defp do_nibbles(<<>>, acc), do: acc
defp do_nibbles(<<high::4, low::4, rest::binary>>, acc),
do: do_nibbles(rest, [low, high | acc])
defmodule RecoveryBit do
@moduledoc """
There are a number of ways to look at recovery bits. Either:
* `:base`: In the range `{0,1}`, which are the outputs of a signer library
* `:ethereum`: In the range `{27,28}`, as defined in the yellow paper
* `:eip155`: In the range `{35+chain_id*2,35+chain_id*2+1}`, as defined in EIP-155
This module provides tools between switching through these choices.
"""
end
end