Skip to content

Commit

Permalink
docs: fix docs and typespecs
Browse files Browse the repository at this point in the history
  • Loading branch information
vhf committed Mar 18, 2024
1 parent 8a9724f commit dbb01bb
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 250 deletions.
12 changes: 11 additions & 1 deletion lib/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ defmodule Tezex.Crypto do
@doc """
Verify that `signature` is a valid signature for `message` signed with the private key corresponding to public key `pubkey`
"""
@spec verify_signature(binary, binary, binary) :: boolean
@spec verify_signature(binary, binary, binary) :: boolean()
def verify_signature(signature, message, "ed" <> _ = pubkey) do
# tz1…
message_hash = hash_message(message)
Expand Down Expand Up @@ -213,6 +213,7 @@ defmodule Tezex.Crypto do
end

defp decode_privkey({privkey, passphrase}) do
throw("not implemented")
decode_privkey(privkey, passphrase)
end

Expand Down Expand Up @@ -249,6 +250,15 @@ defmodule Tezex.Crypto do

@doc """
Sign the hexadecimal/Micheline representation of a string, Micheline encoding is done when `bytes` do not start with `"0501"`.
## Examples
iex> encoded_private_key = "spsk24EJohZHJkZnWEzj3w9wE7BFARpFmq5WAo9oTtqjdJ2t4pyoB3"
iex> Tezex.Crypto.sign_message(encoded_private_key, "foo")
"sigm9uJiGjdk2DpuqTmHcjzpAdTSQfqKxFuDKodyNT8JP3UvrfoPFTNkFbFgDP1WfAi2PjJ3dcpZFLTagD7gUBmwVWbPr5mk"
iex> msg = Tezex.Micheline.string_to_micheline_hex("foo")
"050100000003666F6F"
iex> Tezex.Crypto.sign_message(encoded_private_key, msg)
"sigm9uJiGjdk2DpuqTmHcjzpAdTSQfqKxFuDKodyNT8JP3UvrfoPFTNkFbFgDP1WfAi2PjJ3dcpZFLTagD7gUBmwVWbPr5mk"
"""
@spec sign_message(privkey_param(), binary()) :: nonempty_binary()
def sign_message(privkey_param, "0501" <> _ = bytes) do
Expand Down
54 changes: 42 additions & 12 deletions lib/crypto/curve.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,64 @@ defmodule Tezex.Crypto.Curve do
Specific elliptic curve data.
Parameters:
- `:A` [number]: angular coefficient of x in the curve equation. ex: 123
- `:B` [number]: linear coefficient of x in the curve equation. ex: 123
- `:P` [number]: curve modulo. ex: 12345
- `:N` [number]: curve order. ex: 12345
- `:G` [%Point]: EC Point corresponding to the public key. ex: %Point{x: 123, y: 456}
- `:name` [atom]: curve name. ex: :secp256k1
- `:A` [`t:non_neg_integer/0`]: angular coefficient of x in the curve equation. ex: 123
- `:B` [`t:non_neg_integer/0`]: linear coefficient of x in the curve equation. ex: 123
- `:P` [`t:non_neg_integer/0`]: curve modulo. ex: 12345
- `:N` [`t:non_neg_integer/0`]: curve order. ex: 12345
- `:G` [`t:Tezex.Crypto.Point.t/0`]: EC Point corresponding to the public key. ex: `%Tezex.Crypto.Point{x: 123, y: 456}`
- `:name` [`t:atom/0`]: curve name. ex: :secp256k1
"""
defstruct [:A, :B, :P, :N, :G, :name]

alias Tezex.Crypto.Point
alias Tezex.Crypto.Utils

@type t :: %__MODULE__{
name: atom(),
A: non_neg_integer,
B: non_neg_integer,
P: non_neg_integer,
N: non_neg_integer,
A: non_neg_integer(),
B: non_neg_integer(),
P: non_neg_integer(),
N: non_neg_integer(),
G: Point.t()
}

@doc """
Verifies if the point `p` is on the curve using the elliptic curve equation:
y^2 = x^3 + A*x + B (mod P)
Parameters:
- `curve` [`t:Tezex.Crypto.Curve.t/0`]: curve data
- `p` [`t:Tezex.Crypto.Point.t/0`]: curve point
Returns:
- `result` [`t:boolean/0`]: true if point is on the curve, false otherwise
"""
@spec contains?(t(), Point.t()) :: boolean()
def contains?(curve, p) do
cond do
p.x < 0 || p.x > curve."P" - 1 ->
false

p.y < 0 || p.y > curve."P" - 1 ->
false

(Utils.ipow(p.y, 2) - (Utils.ipow(p.x, 3) + curve."A" * p.x + curve."B"))
|> Utils.mod(curve."P") != 0 ->
false

true ->
true
end
end

@doc """
Get the curve length
Parameters:
- `curve` [%Tezex.Crypto.Curve]: curve data
- `curve` [`t:Tezex.Crypto.Curve.t/0`]: curve data
Returns:
- `length` [integer]: curve length
- `length` [`t:non_neg_integer/0`]: curve length
"""
@spec get_length(t()) :: non_neg_integer()
def get_length(curve) do
Expand Down
88 changes: 44 additions & 44 deletions lib/crypto/ecdsa.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ defmodule Tezex.Crypto.ECDSA do
```
Parameters:
- `compressed_pubkey` [`binary`]: the public key to decode
- `curve` [`%Curve{}`]: the curve to use (or one of `:prime256v1`, `:secp256k1` for the two known curves supported by default)
- `compressed_pubkey` [`t:binary/0`]: the public key to decode
- `curve` [`t:Tezex.Crypto.Curve.t/0`]: the curve to use (or one of `:prime256v1`, `:secp256k1` for the two known curves supported by default)
Returns:
- public_key [`%PublicKey{}`]: a struct containing the public point and the curve;
- public_key [`t:Tezex.Crypto.PublicKey.t/0`]: a struct containing the public point and the curve;
"""
@spec decode_public_key(nonempty_binary, :prime256v1 | :secp256k1 | Curve.t()) :: PublicKey.t()
def decode_public_key(compressed_pubkey, curve_name) when is_atom(curve_name) do
Expand All @@ -51,7 +51,7 @@ defmodule Tezex.Crypto.ECDSA do
%PublicKey{point: decode_point(compressed_pubkey, curve), curve: curve}
end

@spec decode_point(nonempty_binary, Tezex.Crypto.Curve.t()) :: Tezex.Crypto.Point.t()
@spec decode_point(nonempty_binary, Curve.t()) :: Point.t()
def decode_point(compressed_pubkey, %Curve{name: :prime256v1} = curve) do
prime = curve."P"
b = curve."B"
Expand Down Expand Up @@ -126,17 +126,17 @@ defmodule Tezex.Crypto.ECDSA do
Verifies a message signature based on a public key
Parameters:
- `message` [`binary`]: message that was signed
- `signature` [`%Signature{}`]: signature associated with the message
- `public_key` [`%PublicKey{}`]: public key associated with the message signer
- `message` [`t:binary/0`]: message that was signed
- `signature` [`t:Tezex.Crypto.Signature.t/0`]: signature associated with the message
- `public_key` [`t:Tezex.Crypto.PublicKey.t/0`]: public key associated with the message signer
- `options` [`kw list`]: refines request
- `:hashfunc` [`fun/1`]: hash function applied to the message. Default: `fn msg -> :crypto.hash(:sha256, msg) end`
Returns:
- verified [`bool`]: true if message, public key and signature are compatible, false otherwise
"""
@spec verify?(nonempty_binary, Signature.t(), PublicKey.t(), list) :: boolean
@spec verify?(nonempty_binary, Signature.t(), PublicKey.t()) :: boolean
@spec verify?(nonempty_binary, Signature.t(), PublicKey.t(), list()) :: boolean()
@spec verify?(nonempty_binary, Signature.t(), PublicKey.t()) :: boolean()
def verify?(message, signature, public_key, options \\ []) do
%{hashfunc: hashfunc} =
Enum.into(options, %{hashfunc: fn msg -> :crypto.hash(:sha256, msg) end})
Expand Down Expand Up @@ -187,44 +187,44 @@ defmodule Tezex.Crypto.ECDSA do
curve_data = private_key.curve

message = hashfunc.(message)
number_message = Utils.number_from_string(message)
drbg = HMACDRBG.init(private_key.secret, message)

ns1 = :binary.encode_unsigned(curve_data."N" - 1)
message =
message
|> Utils.number_from_string()
|> Utils.truncate_to_n(curve_data."N")

generate_signature(drbg, private_key.secret, message, curve_data)
end

defp generate_signature(drbg, secret, message, curve_data) do
{k, drbg} = HMACDRBG.generate(drbg, 32)
nh = curve_data."N" >>> 1
ns1 = :binary.encode_unsigned(curve_data."N" - 1)

drbg = HMACDRBG.init(private_key.secret, message)
k =
k
|> :binary.decode_unsigned()
|> Utils.truncate_to_n(curve_data."N", true)

with true <- not (k <= 1 or k >= ns1),
kp = Math.multiply(curve_data."G", k, curve_data."N", curve_data."A", curve_data."P"),
false <- Point.is_at_infinity?(kp),
r = rem(kp.x, curve_data."N"),
true <- r != 0,
s = Math.inv(k, curve_data."N") * (r * :binary.decode_unsigned(secret) + message),
s = rem(s, curve_data."N"),
true <- s != 0 do
s =
if s > nh do
curve_data."N" - s
else
s
end

message = Utils.truncate_to_n(number_message, curve_data."N")

Enum.reduce_while(1..1_000_000, drbg, fn _, drbg ->
{k, drbg} = HMACDRBG.generate(drbg, 32)

k =
k
|> :binary.decode_unsigned()
|> Utils.truncate_to_n(curve_data."N", true)

with true <- not (k <= 1 or k >= ns1),
kp = Math.multiply(curve_data."G", k, curve_data."N", curve_data."A", curve_data."P"),
false <- Point.is_at_infinity?(kp),
r <- rem(kp.x, curve_data."N"),
true <- r != 0,
s =
Math.inv(k, curve_data."N") *
(r * :binary.decode_unsigned(private_key.secret) + message),
s = rem(s, curve_data."N"),
true <- s != 0 do
s =
if s > nh do
curve_data."N" - s
else
s
end

{:halt, %Signature{r: r, s: s}}
else
_ -> {:continue, drbg}
end
end)
%Signature{r: r, s: s}
else
_ -> generate_signature(drbg, secret, message, curve_data)
end
end
end
27 changes: 12 additions & 15 deletions lib/crypto/hmacdrbg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -132,26 +132,23 @@ defmodule Tezex.Crypto.HMACDRBG do
_ -> update(state, add)
end

{v, result} = generate_bytes(state, size)
state = %{state | v: v}
{state, result} = generate_bytes(state, size, <<>>)

state = update(state, add)

{result, %{state | count: state.count + 1}}
end

defp generate_bytes(state, size) do
Enum.reduce_while(1..10000, {state.v, <<>>}, fn _i, {v, result} ->
if byte_size(result) < size / 2 do
v =
:crypto.mac_init(:hmac, state.algo, state.k)
|> :crypto.mac_update(v)
|> :crypto.mac_final()

{:cont, {v, result <> v}}
else
{:halt, {v, result}}
end
end)
defp generate_bytes(state, size, result) when byte_size(result) < size / 2 do
v =
:crypto.mac_init(:hmac, state.algo, state.k)
|> :crypto.mac_update(state.v)
|> :crypto.mac_final()

generate_bytes(%{state | v: v}, size, result <> v)
end

defp generate_bytes(state, _size, result) do
{state, result}
end
end
2 changes: 1 addition & 1 deletion lib/crypto/known_curves.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copied and modified from <https://github.com/starkbank/ecdsa-elixir>, Copyright (c) 2020 Stark Bank S.A, MIT License.
defmodule Tezex.Crypto.KnownCurves do
@moduledoc """
Describes the elliptic curves supported by the package
The supported elliptic curves
"""

alias Tezex.Crypto.Curve
Expand Down
Loading

0 comments on commit dbb01bb

Please sign in to comment.