defmodule Forge.Crypto do @moduledoc """ Crypto related functions """ @doc """ Generate an Ethereum compatible sha3 iex> Forge.Crypto.sha3("hello") <<28, 138, 255, 149, 6, 133, 194, 237, 75, 195, 23, 79, 52, 114, 40, 123, 86, 217, 81, 123, 156, 148, 129, 39, 49, 154, 9, 167, 163, 109, 234, 200>> iex> Forge.Crypto.sha3("hello", :upper) "1C8AFF950685C2ED4BC3174F3472287B56D9517B9C948127319A09A7A36DEAC8" iex> Forge.Crypto.sha3("hello", :lower) "1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8" """ @spec sha3(binary) :: binary def sha3(data), do: :keccakf1600.hash(:sha3_256, data) @spec sha3(binary, :upper | :lower) :: binary def sha3(data, :upper), do: data |> sha3() |> Base.encode16() def sha3(data, :lower), do: data |> sha3() |> Base.encode16(case: :lower) @doc """ Sign data with private key """ @spec ecdsa_sign!(binary, binary) :: binary def ecdsa_sign!(data, <>) do ecdsa_sign!(data, Base.decode16!(encoded_private_key)) end def ecdsa_sign!(data, <>) do {:ok, signature} = :libsecp256k1.ecdsa_sign(data, private_key, :default, <<>>) signature end @doc """ Verify signature with public key """ @spec ecdsa_verify(binary, binary, binary) :: :ok | :error def ecdsa_verify(data, signature, <>) do ecdsa_verify(data, signature, Base.decode16!(encoded_public_key)) end def ecdsa_verify(data, signature, <<4::size(8), _::binary-size(64)>> = public_key) do :libsecp256k1.ecdsa_verify(data, signature, public_key) end @doc """ Create a random private key """ @spec create_private_key :: String.t() def create_private_key, do: :crypto.strong_rand_bytes(32) @doc """ Create a public key based on private key """ @spec create_public_key(String.t()) :: String.t() def create_public_key(<>) do encoded_private_key |> Base.decode16!() |> create_public_key() end def create_public_key(private_key) do {:ok, public_key} = :libsecp256k1.ec_pubkey_create(private_key, :uncompressed) public_key end @doc """ Create wallet address based on public key """ @spec create_wallet_address(String.t()) :: String.t() def create_wallet_address(<>) do encoded_public_key |> Base.decode16!() |> create_wallet_address() end def create_wallet_address(public_key) do <<4::size(8), key::binary-size(64)>> = public_key <<_::binary-size(12), address::binary-size(20)>> = sha3(key) Base.encode16(address, case: :lower) end @doc """ Generate a tendermint tx hash based on tx data iex> Forge.Crypto.get_tx_hash("hello") "2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C" """ @spec get_tx_hash(String.t()) :: String.t() def get_tx_hash(data) do <> = :crypto.hash(:sha256, data) Base.encode16(hash) end end