defmodule EthWallet.Utils.Crypto do
@moduledoc """
Crypto Lib
"""
alias EthWallet.Utils.ExSha3
@address_prefix "0x"
@address_size 40
@base_recovery_id 27
@base_recovery_id_eip_155 35
def pubkey_to_address(public_key) do
@address_prefix <> pubkey_to_hex(public_key)
end
defp pubkey_to_hex(public_key) do
public_key
|> strip_leading_byte()
|> keccak_256sum()
|> String.slice(@address_size * -1, @address_size)
|> String.downcase()
end
defp strip_leading_byte(data = [_head | tail]) when is_list(data), do: tail
defp strip_leading_byte(data) when is_binary(data) do
data
|> :binary.bin_to_list()
|> strip_leading_byte()
|> :binary.list_to_bin()
end
def recover(digest, signature, recovery_id_handled , chain_id \\ nil) do
recovery_id =
recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id)
case ExSecp256k1.recover_compact(digest, signature, recovery_id) do
{:ok, public_key} -> {:ok, public_key}
{:error, reason} -> {:error, to_string(reason)}
end
end
defp recovery_id_to_recovery_id_handled(recovery_id, chain_id) do
if chain_id do
chain_id * 2 + @base_recovery_id_eip_155 + recovery_id
else
@base_recovery_id + recovery_id
end
end
defp recovery_id_handled_to_recovery_id(recovery_id_handled, chain_id) do
if chain_id do
recovery_id_handled - chain_id * 2 - @base_recovery_id_eip_155
else
recovery_id_handled - @base_recovery_id
end
end
@doc """
The test is here:
https://github.com/exthereum/exth_crypto/blob/master/lib/signature/signature.ex
Attention: hash should be 32 bytes.
"""
def sign_compact(digest, privkey, chain_id \\ nil) do
# {:libsecp256k1, "~> 0.1.9"} is useful.
{:ok, {<<r::size(256), s::size(256)>> = sig, recovery_id}} =
ExSecp256k1.sign_compact(digest, privkey)
recovery_id_handled =
recovery_id_to_recovery_id_handled(recovery_id, chain_id)
sig_hex =
sig
|> Kernel.<>(<<recovery_id_handled>>)
|> Base.encode16(case: :lower)
%{v: recovery_id_handled, r: r, s: s, sig: "0x#{sig_hex}"}
end
@doc """
The test is here:
https://github.com/exthereum/exth_crypto/blob/master/lib/signature/signature.ex
Attention: hash should be 32 bytes.
"""
def verify(digest, sig, pubkey) do
# :crypto.verify(:ecdsa, :sha256, msg, sig, [pubkey, :secp256k1])
case :libsecp256k1.ecdsa_verify(digest, sig, pubkey) do
:ok -> true
_ -> false
end
end
def verify_compact(digest, sig, pubkey) do
case ExSecp256k1.verify(digest, sig, pubkey) do
:ok -> true
_ -> false
end
end
def sha256(data), do: :crypto.hash(:sha256, data)
def ripemd160(data), do: :crypto.hash(:ripemd160, data)
@spec double_sha256(binary) :: binary
def double_sha256(data), do: data |> sha256() |> sha256()
@doc """
:crypto.sign(:ecdsa, :sha256, msg, [privkey, :secp256k1])
equal to
msg |> sha256() |> :libsecp256k1.ecdsa_sign
> sig format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
- sha256 digest using for bitcoin
"""
def sign(digest, priv) do
{:ok, res} = :libsecp256k1.ecdsa_sign(digest, priv, :default, <<>>)
res
end
def generate_key_secp256k1() do
{pubkey, privkey} = :crypto.generate_key(:ecdh, :secp256k1)
do_generate_key_secp256k1(pubkey, privkey)
end
def generate_key_secp256k1(privkey) do
{pubkey, privkey} = :crypto.generate_key(:ecdh, :secp256k1, privkey)
%{pub: pubkey, priv: privkey}
end
defp do_generate_key_secp256k1(pubkey, privkey) do
if byte_size(privkey) != 32 do
generate_key_secp256k1()
else
%{pub: pubkey, priv: privkey}
end
end
defp keccak_256sum(data) do
data
|> kec()
|> Base.encode16()
end
def kec(data) do
ExSha3.keccak_256(data)
end
# +------------------------------+
# | encrypt the data in database |
# +------------------------------+
@spec encrypt_key(binary(),binary()) :: binary()
def encrypt_key(unencrypted_key, password) do
md5_pwd = md5(password)
{:ok, {init_vec, cipher_text}} = ExCrypto.encrypt(md5_pwd, unencrypted_key)
# init_vec: 16 bytes
init_vec <> cipher_text
end
@spec decrypt_key(binary(),binary()) :: binary()
def decrypt_key(encrypted_key, password) do
md5_pwd = md5(password)
<<init_vec :: binary-size(16), cipher_text :: binary>> = encrypted_key
{:ok, unencrypted_key} = ExCrypto.decrypt(md5_pwd, init_vec, cipher_text)
unencrypted_key
end
def pad(data, block_size) do
to_add = block_size - rem(byte_size(data), block_size)
data <> to_string(:string.chars(to_add, to_add))
end
def unpad(data) do
to_remove = :binary.last(data)
:binary.part(data, 0, byte_size(data) - to_remove)
end
def md5(data) do
:md5
|> :crypto.hash(data)
|> Base.encode16(case: :lower)
end
end