Skip to content

Commit

Permalink
feat(crypto): replace libsecp256k1 NIF with elixir implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
vhf committed Apr 28, 2023
1 parent 5a14089 commit d347b73
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 22 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ This lib depends on [enacl](https://github.com/jlouis/enacl#installrequirements)
For macOS: `brew install libsodium gmp automake libtool gcc`.
If you have an Apple Silicon processor take a look here: https://github.com/jlouis/enacl/issues/53 .

`libsecp256k1` sometimes fails to compile on the first attempt, it should work after `mix deps.compile --force libsecp256k1`

## Test

```sh
Expand Down
26 changes: 17 additions & 9 deletions lib/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -70,28 +70,36 @@ defmodule Tezex.Crypto do
end
end

def verify_signature("sp" <> _sig = signature, message, pubkey) do
def verify_signature("sp" <> _sig = signature, msg, pubkey) do
# tz2…
message_hash = hash_message(message)
signature = decode_signature(signature)
sig = decode_signature(signature)

<<r::unsigned-integer-size(256), s::unsigned-integer-size(256)>> = sig
signature = %EllipticCurve.Signature{r: r, s: s}

message = :binary.decode_hex(msg)

pubkey =
pubkey
|> decode_base58()
|> binary_part(4, 33)
# <<0x03, 0xFE, 0xE2, 0x56>>
<<3, 254, 226, 86>> <> <<public_key::binary-size(33)>> <> _ = decode_base58(pubkey)

public_key = ECDSA.decode_public_key(public_key, :secp256k1)

:ok == :libsecp256k1.ecdsa_verify_compact(message_hash, signature, pubkey)
ECDSA.verify?(message, signature, public_key,
hashfunc: fn msg -> :enacl.generichash(32, msg) end
)
end

def verify_signature("p2" <> _sig = signature, msg, pubkey) do
# tz3…
<<54, 240, 44, 52>> <> <<sig::binary-size(64)>> <> _ = decode_base58(signature)

<<r::unsigned-integer-size(256), s::unsigned-integer-size(256)>> = sig
signature = %EllipticCurve.Signature{r: r, s: s}

message = :binary.decode_hex(msg)

<<0x03, 0xB2, 0x8B, 0x7F>> <> <<public_key::binary-size(33)>> <> _ = decode_base58(pubkey)
# <<0x03, 0xB2, 0x8B, 0x7F>>
<<3, 178, 139, 127>> <> <<public_key::binary-size(33)>> <> _ = decode_base58(pubkey)

public_key = ECDSA.decode_public_key(public_key, :prime256v1)

Expand Down
71 changes: 62 additions & 9 deletions lib/crypto/ecdsa.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ defmodule Tezex.Crypto.ECDSA do
%PublicKey{point: decode_point(compressed_pubkey, curve), curve: curve}
end

def decode_point(compressed_pubkey, curve) do
def decode_point(compressed_pubkey, %EllipticCurve.Curve{name: :prime256v1} = curve) do
prime = curve."P"

b = curve."B"

p_ident = div(prime + 1, 4)
<<sign_y::unsigned-integer-8>> <> x = compressed_pubkey
sign_y = rem(sign_y, 2)

x = :binary.decode_unsigned(x)
a = x ** 3 - x * 3 + b
Expand All @@ -59,16 +60,51 @@ defmodule Tezex.Crypto.ECDSA do
:crypto.mod_pow(a, p_ident, prime)
|> :binary.decode_unsigned()
|> then(fn y ->
if rem(y, 2) != sign_y do
prime - y
else
if rem(y, 2) == sign_y do
y
else
prime - y
end
end)

%Point{x: x, y: y}
end

def decode_point(compressed_pubkey, %EllipticCurve.Curve{name: :secp256k1} = curve) do
# Determine the prefix of the compressed public key and parse the x-coordinate from the compressed public key
<<prefix::unsigned-integer-8>> <> x = compressed_pubkey
x = :binary.decode_unsigned(x)

p = curve."P"

# Compute the square of the x-coordinate
x_squared =
:crypto.mod_pow(x, 3, p)
|> :binary.decode_unsigned()

# Compute the right-hand side of the secp256k1 equation
y_squared = mod_add(x_squared, 7, p)

# Compute the square root of y_squared modulo p
y = :crypto.mod_pow(y_squared, div(p + 1, 4), p) |> :binary.decode_unsigned()

# Choose the correct y-coordinate based on the prefix
y =
if rem(prefix, 2) == 0 do
# If the prefix is even, choose the even value of y
y_even = y
y_odd = mod_sub(p, y_even, p)
if rem(y_odd, 2) == 0, do: y_odd, else: y_even
else
# If the prefix is odd, choose the odd value of y
y_odd = y
y_even = mod_sub(p, y_odd, p)
if rem(y_even, 2) == 0, do: y_even, else: y_odd
end

%Point{x: x, y: y}
end

@doc """
Verifies a message signature based on a public key
Expand Down Expand Up @@ -117,11 +153,28 @@ defmodule Tezex.Crypto.ECDSA do
)

cond do
signature.r < 1 || signature.r >= curve_data."N" -> false
signature.s < 1 || signature.s >= curve_data."N" -> false
Point.isAtInfinity?(v) -> false
IntegerUtils.modulo(v.x, curve_data."N") != signature.r -> false
true -> true
signature.r < 1 || signature.r >= curve_data."N" ->
false

signature.s < 1 || signature.s >= curve_data."N" ->
false

Point.isAtInfinity?(v) ->
false

IntegerUtils.modulo(v.x, curve_data."N") != signature.r ->
false

true ->
true
end
end

defp mod_add(left, right, modulus) do
rem(left + right, modulus)
end

defp mod_sub(left, right, modulus) do
rem(left - right, modulus)
end
end
1 change: 0 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ defmodule Tezex.MixProject do
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
{:ex_unit_notifier, "~> 1.2", only: :test},
{:excoveralls, "~> 0.15.1"},
{:libsecp256k1, "~> 0.1.9"},
{:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false},
{:starkbank_ecdsa, "~> 1.1.0"}
]
Expand Down
1 change: 0 additions & 1 deletion mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"libsecp256k1": {:hex, :libsecp256k1, "0.1.10", "d27495e2b9851c7765129b76c53b60f5e275bd6ff68292c50536bf6b8d091a4d", [:make, :mix], [{:mix_erlang_tasks, "0.1.0", [hex: :mix_erlang_tasks, repo: "hexpm", optional: false]}], "hexpm", "09ea06239938571124f7f5a27bc9ac45dfb1cfc2df40d46ee9b59c3d51366652"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
Expand Down

0 comments on commit d347b73

Please sign in to comment.