From 9c53b3adf792dd1ef4c71f3e42b7106a77527e15 Mon Sep 17 00:00:00 2001 From: James Duncombe Date: Mon, 11 Mar 2024 12:22:40 +0000 Subject: [PATCH] Merges the wallet protocol and behaviour into behaviour. --- lib/tt_eth/behaviours/wallet.ex | 17 -------- lib/tt_eth/behaviours/wallet_adapter.ex | 32 ++++++++++++++ lib/tt_eth/local_wallet.ex | 58 ++++++++++++------------- lib/tt_eth/protocols/wallet.ex | 21 --------- lib/tt_eth/wallet.ex | 28 ++++++------ mix.exs | 5 +-- test/tt_eth/local_wallet_test.exs | 41 ++++++++--------- 7 files changed, 95 insertions(+), 107 deletions(-) delete mode 100644 lib/tt_eth/behaviours/wallet.ex create mode 100644 lib/tt_eth/behaviours/wallet_adapter.ex delete mode 100644 lib/tt_eth/protocols/wallet.ex diff --git a/lib/tt_eth/behaviours/wallet.ex b/lib/tt_eth/behaviours/wallet.ex deleted file mode 100644 index d818300..0000000 --- a/lib/tt_eth/behaviours/wallet.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule TTEth.Behaviours.Wallet do - @moduledoc """ - Behaviour for wallet adapters. - """ - - @typedoc """ - This represents the config for a wallet adapter. - - Check the documentation for the adapter for specific configuration options. - """ - @type config :: map() | binary() - - @doc """ - Returns a new populated wallet struct. - """ - @callback new(config) :: struct() -end diff --git a/lib/tt_eth/behaviours/wallet_adapter.ex b/lib/tt_eth/behaviours/wallet_adapter.ex new file mode 100644 index 0000000..b386d03 --- /dev/null +++ b/lib/tt_eth/behaviours/wallet_adapter.ex @@ -0,0 +1,32 @@ +defmodule TTEth.Behaviours.WalletAdapter do + @moduledoc """ + Defines a shared behaviour for wallet adapters. + """ + + @typedoc """ + This represents the config for a wallet adapter. + + Check the documentation for the adapter for specific configuration options. + """ + @type config :: map() | binary() + + @typedoc """ + Represents a wallet adapter. + """ + @type wallet_adapter :: struct() + + @doc """ + Returns a new populated wallet adapter struct. + """ + @callback new(config) :: wallet_adapter + + @doc """ + Provides the attributes needed to build a `Wallet.t` using the passed `wallet_adapter`. + """ + @callback wallet_attrs(wallet_adapter) :: map() + + @doc """ + Signs `digest` using the given `wallet_adapter`. + """ + @callback sign(wallet_adapter, digest :: binary()) :: {:ok, binary()} | {:error, any()} +end diff --git a/lib/tt_eth/local_wallet.ex b/lib/tt_eth/local_wallet.ex index a869159..0723eb7 100644 --- a/lib/tt_eth/local_wallet.ex +++ b/lib/tt_eth/local_wallet.ex @@ -14,44 +14,19 @@ defmodule TTEth.LocalWallet do """ alias TTEth.Type.{Address, PublicKey, PrivateKey} alias TTEth.Secp256k1 - alias TTEth.Behaviours.Wallet, as: WalletBehaviour + alias TTEth.Behaviours.WalletAdapter, as: WalletAdapterBehaviour @type t :: %__MODULE__{} - @behaviour WalletBehaviour + @behaviour WalletAdapterBehaviour defstruct [ :private_key, :human_private_key ] - defimpl TTEth.Protocols.Wallet, for: __MODULE__ do - def wallet_attrs(%@for{} = wallet) do - pub = - wallet.private_key - |> PublicKey.from_private_key!() - |> PublicKey.from_human!() - - address = pub |> Address.from_public_key!() - - [ - address: address, - public_key: pub, - human_address: address |> Address.to_human!(), - human_public_key: pub |> PublicKey.to_human!(), - _adapter: wallet - ] - end - - def sign(%@for{} = wallet, "" <> digest), - do: - digest - |> Secp256k1.ecdsa_sign_compact(wallet.private_key) - end - - @impl WalletBehaviour - - def new(%{private_key: private_key} = _config), + @impl WalletAdapterBehaviour + def new(%{private_key: "" <> private_key} = _config), do: __MODULE__ |> struct!(%{ @@ -59,11 +34,36 @@ defmodule TTEth.LocalWallet do human_private_key: private_key }) + @impl WalletAdapterBehaviour def new("" <> private_key = _config), do: %{private_key: private_key} |> new() + @impl WalletAdapterBehaviour + def wallet_attrs(%__MODULE__{} = wallet) do + pub = + wallet.private_key + |> PublicKey.from_private_key!() + |> PublicKey.from_human!() + + address = pub |> Address.from_public_key!() + + %{ + address: address, + public_key: pub, + human_address: address |> Address.to_human!(), + human_public_key: pub |> PublicKey.to_human!(), + _adapter: wallet + } + end + + @impl WalletAdapterBehaviour + def sign(%__MODULE__{} = wallet, "" <> digest), + do: + digest + |> Secp256k1.ecdsa_sign_compact(wallet.private_key) + ## Helpers. @doc """ diff --git a/lib/tt_eth/protocols/wallet.ex b/lib/tt_eth/protocols/wallet.ex deleted file mode 100644 index f88fc51..0000000 --- a/lib/tt_eth/protocols/wallet.ex +++ /dev/null @@ -1,21 +0,0 @@ -defprotocol TTEth.Protocols.Wallet do - @moduledoc """ - Protocol for wallet adapters. - """ - - @typedoc """ - All the types that implement this protocol. - """ - @type t :: any() - - @doc """ - Returns a map of attributes used to construct a `TTEth.Wallet.t()`. - """ - def wallet_attrs(t) - - @doc """ - Returns a signature. - """ - @spec sign(t, binary()) :: binary() - def sign(t, digest) -end diff --git a/lib/tt_eth/wallet.ex b/lib/tt_eth/wallet.ex index f886fad..4d971a5 100644 --- a/lib/tt_eth/wallet.ex +++ b/lib/tt_eth/wallet.ex @@ -2,7 +2,6 @@ defmodule TTEth.Wallet do @moduledoc """ Provides a handle struct - `TTEth.Wallet.t()` for encapsulating a wallet. """ - alias TTEth.Protocols.Wallet, as: WalletProtocol alias TTEth.Type.Signature, as: EthSignature alias TTEth.Type.Address, as: EthAddress alias TTEth.Type.PublicKey, as: EthPublicKey @@ -59,21 +58,22 @@ defmodule TTEth.Wallet do @doc """ Creates a new wallet from an underlying wallet or a random one. """ - def new(wallet \\ TTEth.LocalWallet.generate()) when is_struct(wallet), - do: - __MODULE__ - |> struct!( - wallet - |> WalletProtocol.wallet_attrs() - ) + def new(%wallet_adapter{} = wallet \\ TTEth.LocalWallet.generate()) + when is_struct(wallet), + do: + __MODULE__ + |> struct!( + wallet + |> wallet_adapter.wallet_attrs() + ) @doc """ Signs a digest using the passed wallet. """ - def sign(%__MODULE__{_adapter: wallet}, "" <> digest), + def sign(%__MODULE__{_adapter: %wallet_adapter{} = wallet_adapter_state}, "" <> digest), do: - wallet - |> WalletProtocol.sign(digest) + wallet_adapter_state + |> wallet_adapter.sign(digest) @doc """ Same as `sign/2` but raises if the signing process is not successful. @@ -111,9 +111,9 @@ defmodule TTEth.Wallet do ## Private. - defp build_with_adapter!(config, adapter \\ TTEth.LocalWallet) - when (is_binary(config) or is_map(config)) and is_atom(adapter), + defp build_with_adapter!(config, wallet_adapter \\ TTEth.LocalWallet) + when (is_binary(config) or is_map(config)) and is_atom(wallet_adapter), do: config - |> adapter.new() + |> wallet_adapter.new() end diff --git a/mix.exs b/mix.exs index 7008d35..bd38b0f 100644 --- a/mix.exs +++ b/mix.exs @@ -47,10 +47,7 @@ defmodule TTEth.MixProject do Behaviours: [ TTEth.Behaviours.ChainClient, TTEth.Behaviours.Transaction, - TTEth.Behaviours.Wallet - ], - Protocols: [ - TTEth.Protocols.Wallet + TTEth.Behaviours.WalletAdapter ], Types: [ TTEth.Type.Address, diff --git a/test/tt_eth/local_wallet_test.exs b/test/tt_eth/local_wallet_test.exs index 9df82e4..aab8834 100644 --- a/test/tt_eth/local_wallet_test.exs +++ b/test/tt_eth/local_wallet_test.exs @@ -2,49 +2,46 @@ defmodule TTEth.LocalWalletTest do use TTEth.Case alias TTEth.LocalWallet alias TTEth.Type.PrivateKey - alias TTEth.Protocols.Wallet, as: WalletProtocol @human_private_key "0xfa015243f2e6d8694ab037a7987dc73b1630fc8cb1ce82860344684c15d24026" - describe "implements TTEth.Protocols.Wallet protocol" do - setup :build_local_wallet + describe "implements TTEth.Behaviours.Wallet behaviour" do + setup [:build_local_wallet] + + test "new/1 - initializes a new wallet adapter struct" do + decoded_private_key = @human_private_key |> PrivateKey.from_human!() + + %{private_key: @human_private_key} + |> LocalWallet.new() + |> assert_match(%LocalWallet{ + private_key: ^decoded_private_key, + human_private_key: @human_private_key + }) + end test "wallet_attrs/1 - returns attributes needed when building a wallet", %{ - local_wallet: local_wallet + local_wallet: %wallet_adapter{} = local_wallet } do local_wallet - |> WalletProtocol.wallet_attrs() - |> assert_match( + |> wallet_adapter.wallet_attrs() + |> assert_match(%{ address: _, public_key: _, human_address: "0x" <> _, human_public_key: "0x" <> _, _adapter: ^local_wallet - ) + }) end test "sign/2 - signs the given digest with the wallet", %{ - local_wallet: local_wallet + local_wallet: %wallet_adapter{} = local_wallet } do local_wallet - |> WalletProtocol.sign("some plaintext" |> TTEth.keccak()) + |> wallet_adapter.sign("some plaintext" |> TTEth.keccak()) |> assert_match({:ok, {<<_signature::512>>, recovery_id}} when recovery_id in [0, 1]) end end - describe "implements TTEth.Behaviours.Wallet behaviour" do - test "new/1 - initializes a new CloudKMS struct" do - decoded_private_key = @human_private_key |> PrivateKey.from_human!() - - %{private_key: @human_private_key} - |> LocalWallet.new() - |> assert_match(%LocalWallet{ - private_key: ^decoded_private_key, - human_private_key: @human_private_key - }) - end - end - ## Private. defp build_local_wallet(_),