From 49aa64452f2eea3d62558f781770dddd0a8477c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:08:53 -0300 Subject: [PATCH 1/6] feat: add ERC-2335 compatible keystore --- lib/keystore.ex | 96 +++++++++++++++++++++++++++++++++++++ mix.exs | 1 + mix.lock | 2 + test/unit/bit_list_test.exs | 2 +- test/unit/keystore_test.exs | 78 ++++++++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 lib/keystore.ex create mode 100644 test/unit/keystore_test.exs diff --git a/lib/keystore.ex b/lib/keystore.ex new file mode 100644 index 000000000..afd3136e0 --- /dev/null +++ b/lib/keystore.ex @@ -0,0 +1,96 @@ +defmodule Keystore do + @moduledoc """ + [ERC-2335](https://eips.ethereum.org/EIPS/eip-2335) compliant keystore. + """ + + @secret_key_bytes 32 + @salt_bytes 32 + @derived_key_size 32 + @iv_size 16 + @checksum_message_size 32 + + @spec from_json!(String.t()) :: {Types.bls_pubkey(), Bls.privkey()} + def from_json!(json) do + %{pubkey: hex_pubkey, version: 4} = Jason.decode!(json) + pubkey = Base.decode16!(hex_pubkey, case: :mixed) + privkey = "" + {pubkey, privkey} + end + + def decrypt!(password, %{"kdf" => kdf, "checksum" => checksum, "cipher" => cipher}) do + password = sanitize_password(password) + derived_key = derive_key!(kdf, password) + + {iv, cipher_message} = parse_cipher!(cipher) + checksum_message = parse_checksum!(checksum) + verify_password!(derived_key, cipher_message, checksum_message) + secret = decrypt_secret(derived_key, iv, cipher_message) + + if byte_size(secret) != @secret_key_bytes do + raise "Invalid secret length: #{byte_size(secret)}" + end + + secret + end + + defp derive_key!(%{"function" => "scrypt", "params" => params}, password) do + %{"dklen" => @derived_key_size, "salt" => hex_salt, "n" => n, "p" => p, "r" => r} = params + salt = parse_binary(hex_salt) + + if byte_size(salt) != @salt_bytes do + raise "Invalid salt size: #{byte_size(salt)}" + end + + log_n = n |> :math.log2() |> trunc() + Scrypt.hash(password, salt, log_n, r, p, @derived_key_size) + end + + defp derive_key!(%{"function" => "pbkdf2"} = drf, _password) do + %{"dklen" => _dklen, "salt" => _salt, "c" => _c, "prf" => "hmac-sha256"} = drf + end + + defp decrypt_secret(derived_key, iv, cipher_message) do + <> = derived_key + :crypto.crypto_one_time(:aes_128_ctr, key, iv, cipher_message, false) + end + + defp verify_password!(derived_key, cipher_message, checksum_message) do + dk_slice = derived_key |> binary_part(16, 16) + + pre_image = dk_slice <> cipher_message + checksum = :crypto.hash(:sha256, pre_image) + + if checksum != checksum_message do + raise "Invalid password" + end + end + + defp parse_checksum!(%{"function" => "sha256", "message" => hex_message}) do + message = parse_binary(hex_message) + + if byte_size(message) != @checksum_message_size do + "Invalid checksum size: #{byte_size(message)}" + end + + message + end + + defp parse_cipher!(%{ + "function" => "aes-128-ctr", + "params" => %{"iv" => hex_iv}, + "message" => hex_message + }) do + iv = parse_binary(hex_iv) + + if byte_size(iv) != @iv_size do + raise "Invalid IV size: #{byte_size(iv)}" + end + + {iv, parse_binary(hex_message)} + end + + defp parse_binary(hex), do: Base.decode16!(hex, case: :mixed) + + defp sanitize_password(password), + do: password |> String.normalize(:nfkd) |> String.replace(~r/[\x00-\x1f\x80-\x9f\x7f]/, "") +end diff --git a/mix.exs b/mix.exs index 7fb74bb2b..199522e4f 100644 --- a/mix.exs +++ b/mix.exs @@ -35,6 +35,7 @@ defmodule LambdaEthereumConsensus.MixProject do {:tesla, "~> 1.4"}, {:exleveldb, "~> 0.14"}, {:jason, "~> 1.4"}, + {:scrypt_elixir, "~> 0.1.1", hex: :scrypt_elixir_copy}, {:joken, "~> 2.6"}, {:rustler, "~> 0.32"}, {:broadway, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index d837f6533..7f6146fd6 100644 --- a/mix.lock +++ b/mix.lock @@ -17,6 +17,7 @@ "eep": {:git, "https://github.com/virtan/eep", "8f6e5e3ade0606390d928830db61350a5451dda8", [branch: "master"]}, "eflambe": {:hex, :eflambe, "0.3.1", "ef0a35084fad1f50744496730a9662782c0a9ebf449d3e03143e23295c5926ea", [:rebar3], [{:meck, "0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "58d5997be606d4e269e9e9705338e055281fdf3e4935cc902c8908e9e4516c5f"}, "eleveldb": {:hex, :eleveldb, "2.2.20", "1fff63a5055bbf4bf821f797ef76065882b193f5e8095f95fcd9287187773b58", [:rebar3], [], "hexpm", "0e67df12ef836a7bcdde9373c59f1ae18b335defd1d66b820d3d4dd7ca1844e2"}, + "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "escape": {:hex, :escape, "0.1.0", "548edab75e6e6938b1e199ef59cb8e504bcfd3bcf83471d4ae9a3c7a7a3c7d45", [:mix], [], "hexpm", "a5d8e92db4677155df54bc1306d401b5233875d570d474201db03cb3047491cd"}, "ex2ms": {:hex, :ex2ms, "1.7.0", "45b9f523d0b777667ded60070d82d871a37e294f0b6c5b8eca86771f00f82ee1", [:mix], [], "hexpm", "2589eee51f81f1b1caa6d08c990b1ad409215fe6f64c73f73c67d36ed10be827"}, @@ -60,6 +61,7 @@ "rewrite": {:hex, :rewrite, "0.10.1", "238073297d122dad6b5501d761cb3bc0ce5bb4ab86e34c826c395f5f44b2f562", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "91f8d6fe363033e8ff60097bb5e0b76867667df0b4d67e79c2850444c02d8b19"}, "rexbug": {:hex, :rexbug, "1.0.6", "024071c67d970151fbdc06f299faf8db3e1b2ac759a28623a9cc80a517fc74f2", [:mix], [{:mix_test_watch, ">= 0.5.0", [hex: :mix_test_watch, repo: "hexpm", optional: true]}, {:redbug, "~> 1.2", [hex: :redbug, repo: "hexpm", optional: false]}], "hexpm", "148ea724979413e9fd84ca3b4bb5d2d8b840ac481adfd645f5846fda409a642c"}, "rustler": {:hex, :rustler, "0.32.1", "f4cf5a39f9e85d182c0a3f75fa15b5d0add6542ab0bf9ceac6b4023109ebd3fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:toml, "~> 0.6", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "b96be75526784f86f6587f051bc8d6f4eaff23d6e0f88dbcfe4d5871f52946f7"}, + "scrypt_elixir": {:hex, :scrypt_elixir_copy, "0.1.1", "2b23573e8d9e6c93c8116cd17f9b453b6ebf0725b5317ecaeacaf73353a4dbd3", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "1eb5768b6b6c657770cbc00a9724f47bad4e9d664a2da3916030d591223561e7"}, "sentry": {:hex, :sentry, "10.4.0", "d8ffe8ce15b4b53f5e879299c3c222324c289a47a507c0b251c4f91ce7bae9ff", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "e5f98892152879dc87363b1b7f774eeddb8cf7dddfa7355e40eba188b2cae58a"}, "snappyer": {:hex, :snappyer, "1.2.9", "9cc58470798648ce34c662ca0aa6daae31367667714c9a543384430a3586e5d3", [:rebar3], [], "hexpm", "18d00ca218ae613416e6eecafe1078db86342a66f86277bd45c95f05bf1c8b29"}, "sourceror": {:hex, :sourceror, "1.0.2", "c5e86fdc14881f797749d1fe5df017ca66727a8146e7ee3e736605a3df78f3e6", [:mix], [], "hexpm", "832335e87d0913658f129d58b2a7dc0490ddd4487b02de6d85bca0169ec2bd79"}, diff --git a/test/unit/bit_list_test.exs b/test/unit/bit_list_test.exs index db18fdfbc..67b0fb003 100644 --- a/test/unit/bit_list_test.exs +++ b/test/unit/bit_list_test.exs @@ -1,4 +1,4 @@ -defmodule BitListTest do +defmodule Unit.BitListTest do use ExUnit.Case alias LambdaEthereumConsensus.Utils.BitList diff --git a/test/unit/keystore_test.exs b/test/unit/keystore_test.exs new file mode 100644 index 000000000..ef6b85898 --- /dev/null +++ b/test/unit/keystore_test.exs @@ -0,0 +1,78 @@ +defmodule Unit.KeystoreTest do + use ExUnit.Case + + @eip_password "testpassword" + @eip_secret Base.decode16!("000000000019D6689C085AE165831E934FF763AE46A2A6C172B3F1B60A8CE26F") + + # Taken from lighthouse + @scrypt_json ~s({ + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "description": "This is a test keystore that uses scrypt to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/3141592653/589793238", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "version": 4 + }) + + @pbkdf2_json ~s({ + "crypto": { + "kdf": { + "function": "pbkdf2", + "params": { + "dklen": 32, + "c": 262144, + "prf": "hmac-sha256", + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" + } + }, + "description": "This is a test keystore that uses PBKDF2 to secure the secret.", + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "path": "m/12381/60/0/0", + "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", + "version": 4 + }) + + test "eip scrypt test vector" do + %{"crypto" => crypto} = Jason.decode!(@scrypt_json) + secret = Keystore.decrypt!(@eip_password, crypto) + assert secret == @eip_secret + end +end From 176589da01a3d017362b42b450d6d2b2dd6f5f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:38:24 -0300 Subject: [PATCH 2/6] Add nicer API --- lib/keystore.ex | 30 +++++++++--- test/unit/keystore_test.exs | 96 ++++++++++++------------------------- 2 files changed, 55 insertions(+), 71 deletions(-) diff --git a/lib/keystore.ex b/lib/keystore.ex index afd3136e0..cc26a41a5 100644 --- a/lib/keystore.ex +++ b/lib/keystore.ex @@ -9,15 +9,32 @@ defmodule Keystore do @iv_size 16 @checksum_message_size 32 - @spec from_json!(String.t()) :: {Types.bls_pubkey(), Bls.privkey()} - def from_json!(json) do - %{pubkey: hex_pubkey, version: 4} = Jason.decode!(json) - pubkey = Base.decode16!(hex_pubkey, case: :mixed) - privkey = "" + @spec decode_from_files!(Path.t(), Path.t()) :: {Types.bls_pubkey(), Bls.privkey()} + def decode_from_files!(json, password) do + password = File.read!(password) + File.read!(json) |> decode_str!(password) + end + + @spec decode_str!(String.t(), String.t()) :: {Types.bls_pubkey(), Bls.privkey()} + def decode_str!(json, password) do + decoded_json = Jason.decode!(json) + # We only support version 4 (the only one) + %{"version" => 4} = decoded_json + validate_empty_path!(decoded_json["path"]) + + privkey = decrypt!(decoded_json["crypto"], password) + # TODO: derive from privkey and validate with this pubkey + pubkey = Map.fetch!(decoded_json, "pubkey") {pubkey, privkey} end - def decrypt!(password, %{"kdf" => kdf, "checksum" => checksum, "cipher" => cipher}) do + # TODO: support keystore paths + defp validate_empty_path!(path) when byte_size(path) > 0, + do: raise("Only empty-paths are supported") + + defp validate_empty_path!(_), do: :ok + + defp decrypt!(%{"kdf" => kdf, "checksum" => checksum, "cipher" => cipher}, password) do password = sanitize_password(password) derived_key = derive_key!(kdf, password) @@ -45,6 +62,7 @@ defmodule Keystore do Scrypt.hash(password, salt, log_n, r, p, @derived_key_size) end + # TODO: support pbkdf2 defp derive_key!(%{"function" => "pbkdf2"} = drf, _password) do %{"dklen" => _dklen, "salt" => _salt, "c" => _c, "prf" => "hmac-sha256"} = drf end diff --git a/test/unit/keystore_test.exs b/test/unit/keystore_test.exs index ef6b85898..ecf078c97 100644 --- a/test/unit/keystore_test.exs +++ b/test/unit/keystore_test.exs @@ -6,73 +6,39 @@ defmodule Unit.KeystoreTest do # Taken from lighthouse @scrypt_json ~s({ - "crypto": { - "kdf": { - "function": "scrypt", - "params": { - "dklen": 32, - "n": 262144, - "p": 1, - "r": 8, - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" - } - }, - "description": "This is a test keystore that uses scrypt to secure the secret.", - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "path": "m/12381/60/3141592653/589793238", - "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", - "version": 4 - }) - - @pbkdf2_json ~s({ - "crypto": { - "kdf": { - "function": "pbkdf2", - "params": { - "dklen": 32, - "c": 262144, - "prf": "hmac-sha256", - "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" - }, - "message": "" - }, - "checksum": { - "function": "sha256", - "params": {}, - "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" - }, - "cipher": { - "function": "aes-128-ctr", - "params": { - "iv": "264daa3f303d7259501c93d997d84fe6" - }, - "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" - } - }, - "description": "This is a test keystore that uses PBKDF2 to secure the secret.", - "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", - "path": "m/12381/60/0/0", - "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", - "version": 4 - }) + "crypto": { + "kdf": { + "function": "scrypt", + "params": { + "dklen": 32, + "n": 262144, + "p": 1, + "r": 8, + "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" + }, + "message": "" + }, + "checksum": { + "function": "sha256", + "params": {}, + "message": "149aafa27b041f3523c53d7acba1905fa6b1c90f9fef137568101f44b531a3cb" + }, + "cipher": { + "function": "aes-128-ctr", + "params": { + "iv": "264daa3f303d7259501c93d997d84fe6" + }, + "message": "54ecc8863c0550351eee5720f3be6a5d4a016025aa91cd6436cfec938d6a8d30" + } + }, + "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", + "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", + "path": "", + "version": 4 + }) test "eip scrypt test vector" do - %{"crypto" => crypto} = Jason.decode!(@scrypt_json) - secret = Keystore.decrypt!(@eip_password, crypto) + {_pubkey, secret} = Keystore.decode_str!(@scrypt_json, @eip_password) assert secret == @eip_secret end end From c3cd4be7342d1d5cc01fd8f384b6467dcac91d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:48:53 -0300 Subject: [PATCH 3/6] Fix: wasn't decoding pubkey --- lib/keystore.ex | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/keystore.ex b/lib/keystore.ex index cc26a41a5..959d292ab 100644 --- a/lib/keystore.ex +++ b/lib/keystore.ex @@ -24,7 +24,7 @@ defmodule Keystore do privkey = decrypt!(decoded_json["crypto"], password) # TODO: derive from privkey and validate with this pubkey - pubkey = Map.fetch!(decoded_json, "pubkey") + pubkey = Map.fetch!(decoded_json, "pubkey") |> parse_binary!() {pubkey, privkey} end @@ -52,7 +52,7 @@ defmodule Keystore do defp derive_key!(%{"function" => "scrypt", "params" => params}, password) do %{"dklen" => @derived_key_size, "salt" => hex_salt, "n" => n, "p" => p, "r" => r} = params - salt = parse_binary(hex_salt) + salt = parse_binary!(hex_salt) if byte_size(salt) != @salt_bytes do raise "Invalid salt size: #{byte_size(salt)}" @@ -84,7 +84,7 @@ defmodule Keystore do end defp parse_checksum!(%{"function" => "sha256", "message" => hex_message}) do - message = parse_binary(hex_message) + message = parse_binary!(hex_message) if byte_size(message) != @checksum_message_size do "Invalid checksum size: #{byte_size(message)}" @@ -98,16 +98,16 @@ defmodule Keystore do "params" => %{"iv" => hex_iv}, "message" => hex_message }) do - iv = parse_binary(hex_iv) + iv = parse_binary!(hex_iv) if byte_size(iv) != @iv_size do raise "Invalid IV size: #{byte_size(iv)}" end - {iv, parse_binary(hex_message)} + {iv, parse_binary!(hex_message)} end - defp parse_binary(hex), do: Base.decode16!(hex, case: :mixed) + defp parse_binary!(hex), do: Base.decode16!(hex, case: :mixed) defp sanitize_password(password), do: password |> String.normalize(:nfkd) |> String.replace(~r/[\x00-\x1f\x80-\x9f\x7f]/, "") From 1fc8bdb93a99c90c1e48075fe8fff6d65422f4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:50:17 -0300 Subject: [PATCH 4/6] Add test assertion --- test/unit/keystore_test.exs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/unit/keystore_test.exs b/test/unit/keystore_test.exs index ecf078c97..059615d52 100644 --- a/test/unit/keystore_test.exs +++ b/test/unit/keystore_test.exs @@ -38,7 +38,14 @@ defmodule Unit.KeystoreTest do }) test "eip scrypt test vector" do - {_pubkey, secret} = Keystore.decode_str!(@scrypt_json, @eip_password) + {pubkey, secret} = Keystore.decode_str!(@scrypt_json, @eip_password) + + expected_pubkey = + Base.decode16!( + "9612D7A727C9D0A22E185A1C768478DFE919CADA9266988CB32359C11F2B7B27F4AE4040902382AE2910C15E2B420D07" + ) + assert secret == @eip_secret + assert pubkey == expected_pubkey end end From 32dd2d33ea9ecedac6de3717995d0e890041e4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:50:04 -0300 Subject: [PATCH 5/6] Add BLS signing assertion --- test/unit/keystore_test.exs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/unit/keystore_test.exs b/test/unit/keystore_test.exs index 059615d52..4524acc56 100644 --- a/test/unit/keystore_test.exs +++ b/test/unit/keystore_test.exs @@ -38,14 +38,18 @@ defmodule Unit.KeystoreTest do }) test "eip scrypt test vector" do - {pubkey, secret} = Keystore.decode_str!(@scrypt_json, @eip_password) + {pubkey, privkey} = Keystore.decode_str!(@scrypt_json, @eip_password) expected_pubkey = Base.decode16!( "9612D7A727C9D0A22E185A1C768478DFE919CADA9266988CB32359C11F2B7B27F4AE4040902382AE2910C15E2B420D07" ) - assert secret == @eip_secret + assert privkey == @eip_secret assert pubkey == expected_pubkey + + digest = :crypto.hash(:sha256, "test message") + {:ok, signature} = Bls.sign(privkey, digest) + assert Bls.valid?(pubkey, digest, signature) end end From 1ec70eef15eb92f108ebca336c26de5d786cd405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Gr=C3=BCner?= <47506558+MegaRedHand@users.noreply.github.com> Date: Mon, 22 Apr 2024 11:50:21 -0300 Subject: [PATCH 6/6] fix: NIF panicked when `message` wasn't 32 bytes --- native/bls_nif/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/native/bls_nif/src/lib.rs b/native/bls_nif/src/lib.rs index b4c53d444..f2966caf8 100644 --- a/native/bls_nif/src/lib.rs +++ b/native/bls_nif/src/lib.rs @@ -16,6 +16,9 @@ fn sign<'env>( private_key: Binary, message: Binary, ) -> Result, String> { + if message.len() != 32 { + return Err(format!("Message must be 32 bytes long")); + } let sk = match SecretKey::deserialize(private_key.as_slice()) { Ok(sk) => sk, Err(e) => return Err(format!("{:?}", e)), @@ -51,6 +54,9 @@ fn aggregate<'env>(env: Env<'env>, signatures: Vec) -> Result(public_key: Binary, message: Binary, signature: Binary) -> Result { + if message.len() != 32 { + return Err(format!("Message must be 32 bytes long")); + } let sig = Signature::deserialize(signature.as_slice()).map_err(|err| format!("{:?}", err))?; let pubkey = PublicKey::deserialize(public_key.as_slice()).map_err(|err| format!("{:?}", err))?; @@ -86,6 +92,9 @@ fn fast_aggregate_verify<'env>( message: Binary, signature: Binary, ) -> Result { + if message.len() != 32 { + return Err(format!("Message must be 32 bytes long")); + } let aggregate_sig = AggregateSignature::deserialize(signature.as_slice()) .map_err(|err| format!("{:?}", err))?; let pubkeys_result = public_keys @@ -104,6 +113,9 @@ fn eth_fast_aggregate_verify<'env>( message: Binary, signature: Binary, ) -> Result { + if message.len() != 32 { + return Err(format!("Message must be 32 bytes long")); + } let aggregate_sig = AggregateSignature::deserialize(signature.as_slice()) .map_err(|err| format!("{:?}", err))?; let pubkeys_result = public_keys