From d86d337fc14d9b56bff0d50478df2db1d368ee7c Mon Sep 17 00:00:00 2001 From: Razvan Draghici Date: Mon, 18 Dec 2023 21:42:48 -0500 Subject: [PATCH] Bring in checksum eth addresses --- CHANGELOG.md | 4 ++ README.md | 2 +- lib/block_keys/ethereum/address.ex | 48 +++++++++++++++++++++-- mix.exs | 2 +- test/block_keys/ethereum/address_test.exs | 2 +- 5 files changed, 51 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c789589..95850ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.0.1 + + * Brings in Ethereum EIP-55 checksum addresses, credit goes to @wchenNL + ## v1.0.0 **Breaking Changes** diff --git a/README.md b/README.md index 92a662b..de73b3e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Add the dependency to your `mix.exs`: ``` def deps do [ - {:block_keys, "~> 1.0.0"} + {:block_keys, "~> 1.0.1"} ] end ``` diff --git a/lib/block_keys/ethereum/address.ex b/lib/block_keys/ethereum/address.ex index 889c360..0c3add1 100644 --- a/lib/block_keys/ethereum/address.ex +++ b/lib/block_keys/ethereum/address.ex @@ -1,6 +1,7 @@ defmodule BlockKeys.Ethereum.Address do @moduledoc """ Converts a public extended key into an Ethereum Address + Follows EIP-55 checksum address encoding: https://eips.ethereum.org/EIPS/eip-55 """ alias BlockKeys.{Encoding, Crypto} @@ -9,10 +10,19 @@ defmodule BlockKeys.Ethereum.Address do xpub |> maybe_decode() |> decompress() - |> keccak256() + |> ExKeccak.hash_256() |> to_address() end + def valid_address?("0x" <> address = full_address) when byte_size(address) == 40 do + case to_checksum_address(full_address) do + {:ok, checksum} -> checksum == full_address + _ -> false + end + end + + def valid_address?(_address), do: false + defp maybe_decode(<<"xpub", _rest::binary>> = encoded_key) do decoded_key = encoded_key @@ -31,9 +41,39 @@ defmodule BlockKeys.Ethereum.Address do end defp to_address(<<_::binary-12, address::binary-20>>) do - "0x" - |> Kernel.<>(address |> Base.encode16(case: :lower)) + {:ok, checksum_address} = + "0x" + |> Kernel.<>(address |> Base.encode16(case: :lower)) + |> to_checksum_address() + + checksum_address end - defp keccak256(data), do: ExKeccak.hash_256(data) + defp to_checksum_address("0x" <> address) when byte_size(address) == 40 do + address = String.downcase(address) + + checksum = + Enum.zip( + String.graphemes(address), + String.graphemes(address |> ExKeccak.hash_256() |> Base.encode16(case: :lower)) + ) + |> Enum.map_join(fn {each_address, each_hash} -> + cond do + String.match?(each_address, ~r/[0-9]/) -> + each_address + + String.match?(each_address, ~r/[a-f]/) and elem(Integer.parse(each_hash, 16), 0) >= 8 -> + String.upcase(each_address) + + String.match?(each_address, ~r/[a-f]/) -> + each_address + end + end) + + {:ok, "0x" <> checksum} + end + + defp to_checksum_address(_address) do + {:error, "unrecognized address"} + end end diff --git a/mix.exs b/mix.exs index a73a645..9db8511 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule BlockKeys.MixProject do def project do [ app: :block_keys, - version: "1.0.0", + version: "1.0.1", elixir: "~> 1.7", description: description(), start_permanent: Mix.env() == :prod, diff --git a/test/block_keys/ethereum/address_test.exs b/test/block_keys/ethereum/address_test.exs index fd8c9a1..636bb99 100644 --- a/test/block_keys/ethereum/address_test.exs +++ b/test/block_keys/ethereum/address_test.exs @@ -15,6 +15,6 @@ defmodule EthereumAddressTest do assert CKD.derive(root_key, "M/44'/60'/0'/0/0") |> Address.from_xpub() == - "0x73bb50c828fd325c011d740fde78d02528826156" + "0x73Bb50c828fD325c011d740fDe78d02528826156" end end