diff --git a/lib/crypto.ex b/lib/crypto.ex index 72910a4..8ea75ad 100644 --- a/lib/crypto.ex +++ b/lib/crypto.ex @@ -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) @@ -213,6 +213,7 @@ defmodule Tezex.Crypto do end defp decode_privkey({privkey, passphrase}) do + throw("not implemented") decode_privkey(privkey, passphrase) end @@ -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 diff --git a/lib/crypto/curve.ex b/lib/crypto/curve.ex index bba9772..3ee69fc 100644 --- a/lib/crypto/curve.ex +++ b/lib/crypto/curve.ex @@ -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 diff --git a/lib/crypto/ecdsa.ex b/lib/crypto/ecdsa.ex index 08c10c9..9437bd6 100644 --- a/lib/crypto/ecdsa.ex +++ b/lib/crypto/ecdsa.ex @@ -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 @@ -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" @@ -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}) @@ -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 diff --git a/lib/crypto/hmacdrbg.ex b/lib/crypto/hmacdrbg.ex index 3b95bab..a3bf25d 100644 --- a/lib/crypto/hmacdrbg.ex +++ b/lib/crypto/hmacdrbg.ex @@ -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 diff --git a/lib/crypto/known_curves.ex b/lib/crypto/known_curves.ex index 796adb1..e27cb33 100644 --- a/lib/crypto/known_curves.ex +++ b/lib/crypto/known_curves.ex @@ -1,7 +1,7 @@ # Copied and modified from , 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 diff --git a/lib/crypto/math.ex b/lib/crypto/math.ex index ee1888e..a21c2cd 100644 --- a/lib/crypto/math.ex +++ b/lib/crypto/math.ex @@ -2,19 +2,20 @@ defmodule Tezex.Crypto.Math do @moduledoc false - alias Tezex.Crypto.{Point, Utils} + alias Tezex.Crypto.Point + alias Tezex.Crypto.Utils @doc """ Fast way to multiply point and scalar in elliptic curves - - `p` [%Point]: First Point to mutiply - - `n` [integer]: Scalar to mutiply - - `c_n` [integer]: Order of the elliptic curve - - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) - - `c_a` [integer]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) + - `p` [`t:Tezex.Crypto.Point.t/0`]: First Point to multiply + - `n` [`t:integer/0`]: Scalar to multiply + - `c_n` [`t:integer/0`]: Order of the elliptic curve + - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + - `c_a` [`t:integer/0`]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) Returns: - - `point` [%Point]: point that represents the sum of First and Second Point + - `point` [`t:Tezex.Crypto.Point.t/0`]: point that represents the sum of First and Second Point """ @spec multiply(Point.t(), integer, integer, integer, integer) :: Point.t() def multiply(p, n, c_n, c_a, c_p) do @@ -27,13 +28,13 @@ defmodule Tezex.Crypto.Math do @doc """ Fast way to add two points in elliptic curves - - `p` [%Point]: First Point you want to add - - `q` [%Point]: Second Point you want to add - - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) - - `c_a` [integer]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) + - `p` [`t:Tezex.Crypto.Point.t/0`]: First Point you want to add + - `q` [`t:Tezex.Crypto.Point.t/0`]: Second Point you want to add + - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + - `c_a` [`t:integer/0`]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) Returns: - - `point` [%Point]: point that represents the sum of First and Second Point + - `point` [`t:Tezex.Crypto.Point.t/0`]: point that represents the sum of First and Second Point """ @spec add(Point.t(), Point.t(), integer, integer) :: Point.t() def add(p, q, c_a, c_p) do @@ -44,11 +45,11 @@ defmodule Tezex.Crypto.Math do @doc """ Extended Euclidean Algorithm. It's the 'division' in elliptic curves - - `x` [integer]: Divisor - - `n` [integer]: Mod for division + - `x` [`t:integer/0`]: Divisor + - `n` [`t:integer/0`]: Mod for division Returns: - - `value` [integer]: value representing the division + - `value` [`t:integer/0`]: value representing the division """ def inv(x, _n) when x == 0 do 0 @@ -71,11 +72,11 @@ defmodule Tezex.Crypto.Math do # Converts point back from Jacobian coordinates - # - `p` [integer]: First Point you want to add - # - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `p` [`t:Tezex.Crypto.Point.t/0`]: Point you want to add + # - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) # Returns: - # - `point` [%Point]: point in default coordinates + # - `point` [`t:Tezex.Crypto.Point.t/0`]: point in default coordinates defp to_jacobian(p) do %Point{x: p.x, y: p.y, z: 1} end @@ -84,146 +85,132 @@ defmodule Tezex.Crypto.Math do z = inv(p.z, c_p) %Point{ - x: - Utils.mod( - p.x * Utils.ipow(z, 2), - c_p - ), - y: - Utils.mod( - p.y * Utils.ipow(z, 3), - c_p - ) + x: Utils.mod(p.x * Utils.ipow(z, 2), c_p), + y: Utils.mod(p.y * Utils.ipow(z, 3), c_p) } end # Doubles a point in elliptic curves - # - `p` [integer]: Point you want to double - # - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) - # - `c_a` [integer]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `p` [`t:Tezex.Crypto.Point.t/0`]: Point you want to double + # - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `c_a` [`t:integer/0`]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) # Returns: - # - `point` [%Point]: point that represents the sum of First and Second Point + # - `point` [`t:Tezex.Crypto.Point.t/0`]: point that represents the sum of First and Second Point + defp jacobian_double(%Point{y: 0}, _c_a, _c_p), do: %Point{x: 0, y: 0, z: 0} + defp jacobian_double(p, c_a, c_p) do - if p.y == 0 do - %Point{x: 0, y: 0, z: 0} + ysq = + Utils.ipow(p.y, 2) + |> Utils.mod(c_p) + + s = + (4 * p.x * ysq) + |> Utils.mod(c_p) + + m = + (3 * Utils.ipow(p.x, 2) + c_a * Utils.ipow(p.z, 4)) + |> Utils.mod(c_p) + + nx = + (Utils.ipow(m, 2) - 2 * s) + |> Utils.mod(c_p) + + ny = + (m * (s - nx) - 8 * Utils.ipow(ysq, 2)) + |> Utils.mod(c_p) + + nz = + (2 * p.y * p.z) + |> Utils.mod(c_p) + + %Point{x: nx, y: ny, z: nz} + end + + # Adds two points in the elliptic curve + # - `p` [`t:Tezex.Crypto.Point.t/0`]: First Point you want to add + # - `q` [`t:Tezex.Crypto.Point.t/0`]: Second Point you want to add + # - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `c_a` [`t:integer/0`]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) + + # Returns: + # - `point` [`t:Tezex.Crypto.Point.t/0`]: point that represents the sum of first and second Point + @spec jacobian_add(Point.t(), Point.t(), integer(), integer()) :: Point.t() + defp jacobian_add(%Point{y: 0}, q, _c_a, _c_p), do: q + defp jacobian_add(p, %Point{y: 0}, _c_a, _c_p), do: p + + defp jacobian_add(p, q, c_a, c_p) do + u1 = + (p.x * Utils.ipow(q.z, 2)) + |> Utils.mod(c_p) + + u2 = + (q.x * Utils.ipow(p.z, 2)) + |> Utils.mod(c_p) + + s1 = + (p.y * Utils.ipow(q.z, 3)) + |> Utils.mod(c_p) + + s2 = + (q.y * Utils.ipow(p.z, 3)) + |> Utils.mod(c_p) + + if u1 == u2 do + if s1 != s2 do + %Point{x: 0, y: 0, z: 1} + else + jacobian_double(p, c_a, c_p) + end else - ysq = - Utils.ipow(p.y, 2) + h = u2 - u1 + r = s2 - s1 + + h2 = + (h * h) |> Utils.mod(c_p) - s = - (4 * p.x * ysq) + h3 = + (h * h2) |> Utils.mod(c_p) - m = - (3 * Utils.ipow(p.x, 2) + c_a * Utils.ipow(p.z, 4)) + u1h2 = + (u1 * h2) |> Utils.mod(c_p) nx = - (Utils.ipow(m, 2) - 2 * s) + (Utils.ipow(r, 2) - h3 - 2 * u1h2) |> Utils.mod(c_p) ny = - (m * (s - nx) - 8 * Utils.ipow(ysq, 2)) + (r * (u1h2 - nx) - s1 * h3) |> Utils.mod(c_p) nz = - (2 * p.y * p.z) + (h * p.z * q.z) |> Utils.mod(c_p) %Point{x: nx, y: ny, z: nz} end end - # Adds two points in the elliptic curve - # - `p` [integer]: First Point you want to add - # - `q` [integer]: Second Point you want to add - # - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) - # - `c_a` [integer]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) - - # Returns: - # - `point` [%Point]: point that represents the sum of first and second Point - defp jacobian_add(p, q, c_a, c_p) do - if p.y == 0 do - q - else - if q.y == 0 do - p - else - u1 = - (p.x * Utils.ipow(q.z, 2)) - |> Utils.mod(c_p) - - u2 = - (q.x * Utils.ipow(p.z, 2)) - |> Utils.mod(c_p) - - s1 = - (p.y * Utils.ipow(q.z, 3)) - |> Utils.mod(c_p) - - s2 = - (q.y * Utils.ipow(p.z, 3)) - |> Utils.mod(c_p) - - if u1 == u2 do - if s1 != s2 do - %Point{x: 0, y: 0, z: 1} - else - jacobian_double(p, c_a, c_p) - end - else - h = u2 - u1 - - r = s2 - s1 - - h2 = - (h * h) - |> Utils.mod(c_p) - - h3 = - (h * h2) - |> Utils.mod(c_p) - - u1h2 = - (u1 * h2) - |> Utils.mod(c_p) - - nx = - (Utils.ipow(r, 2) - h3 - 2 * u1h2) - |> Utils.mod(c_p) - - ny = - (r * (u1h2 - nx) - s1 * h3) - |> Utils.mod(c_p) - - nz = - (h * p.z * q.z) - |> Utils.mod(c_p) - - %Point{x: nx, y: ny, z: nz} - end - end - end - end - - # Multily point and scalar in elliptic curves + # Multiply point and scalar on elliptic curves - # - `p` [integer]: First Point to mutiply - # - `n` [integer]: Scalar to mutiply - # - `c_n` [integer]: Order of the elliptic curve - # - `c_p` [integer]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) - # - `c_a` [integer]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `p` [`t:Tezex.Crypto.Point.t/0`]: Point to multiply + # - `n` [`t:integer/0`]: Scalar to multiply + # - `c_n` [`t:integer/0`]: Order of the elliptic curve + # - `c_p` [`t:integer/0`]: Prime number in the module of the equation Y^2 = X^3 + c_a*X + B (mod p) + # - `c_a` [`t:integer/0`]: Coefficient of the first-order term of the equation Y^2 = X^3 + c_a*X + B (mod p) # Returns: - # - `point` [%Point]: point that represents the sum of First and Second Point - defp jacobian_multiply(_p, n, _c_n, _c_a, _c_p) when n == 0 do + # - `point` [`t:Tezex.Crypto.Point.t/0`]: point that represents the sum of First and Second Point + @spec jacobian_multiply(Point.t(), integer(), integer(), integer(), integer()) :: Point.t() + defp jacobian_multiply(_p, 0, _c_n, _c_a, _c_p) do %Point{x: 0, y: 0, z: 1} end - defp jacobian_multiply(p, n, _c_n, _c_a, _c_p) when n == 1 do + defp jacobian_multiply(p, 1, _c_n, _c_a, _c_p) do if p.y == 0 do %Point{x: 0, y: 0, z: 1} else @@ -239,7 +226,7 @@ defmodule Tezex.Crypto.Math do end end - defp jacobian_multiply(p, _n, _c_n, _c_a, _c_p) when p.y == 0 do + defp jacobian_multiply(%Point{y: 0}, _n, _c_n, _c_a, _c_p) do %Point{x: 0, y: 0, z: 1} end diff --git a/lib/crypto/point.ex b/lib/crypto/point.ex index 6bc31f6..6e16f2e 100644 --- a/lib/crypto/point.ex +++ b/lib/crypto/point.ex @@ -3,9 +3,9 @@ defmodule Tezex.Crypto.Point do Holds point data. Is usually handled internally by the library and serves only as detailed information to the end-user. Parameters: - - `:x` [integer]: first point coordinate; - - `:y` [integer]: first point coordinate; - - `:z` [integer]: first point coordinate (used only in Jacobian coordinates); + - `:x` [`t:non_neg_integer/0`]: first point coordinate; + - `:y` [`t:non_neg_integer/0`]: first point coordinate; + - `:z` [`t:non_neg_integer/0`]: first point coordinate (used only in Jacobian coordinates); """ defstruct [:x, :y, z: 0] @@ -15,7 +15,7 @@ defmodule Tezex.Crypto.Point do z: non_neg_integer } - @spec is_at_infinity?(__MODULE__.t()) :: boolean + @spec is_at_infinity?(__MODULE__.t()) :: boolean() def is_at_infinity?(p) do p.y == 0 end diff --git a/lib/crypto/private_key.ex b/lib/crypto/private_key.ex index 062c8e3..c812bf9 100644 --- a/lib/crypto/private_key.ex +++ b/lib/crypto/private_key.ex @@ -1,18 +1,23 @@ defmodule Tezex.Crypto.PrivateKey do @moduledoc """ - Used to create private keys or convert them between struct and .der or .pem formats. Also allows creations of public keys from private keys. + Holds private key data. + + Used to create private keys and created public keys from private keys. + + Parameters: + - `:secret` [`t:binary/0`]: public key point data; + - `:curve` [`t:Tezex.Crypto.Curve.t/0`]: public key curve information. """ alias Tezex.Crypto.Curve alias Tezex.Crypto.KnownCurves alias Tezex.Crypto.Math - alias Tezex.Crypto.Point alias Tezex.Crypto.PrivateKey alias Tezex.Crypto.PublicKey alias Tezex.Crypto.Utils @type t :: %__MODULE__{ - secret: any(), + secret: binary(), curve: Curve.t() } @@ -20,8 +25,8 @@ defmodule Tezex.Crypto.PrivateKey do Holds private key data. Parameters: - - `:secret` [integer]: private key secret number - - `:curve` [%Tezex.Crypto.Curve]: private key curve information + - `:secret` [`t:binary/0`]: private key secret number as bytes + - `:curve` [`t:Tezex.Crypto.Curve.t/0`]: private key curve information """ defstruct [:secret, :curve] @@ -29,27 +34,33 @@ defmodule Tezex.Crypto.PrivateKey do Creates a new private key Parameters: - - `secret` [integer]: private key secret. Default: nil -> random key will be generated - - `curve` [atom]: curve name. Default: :secp256k1 + - `secret` [`t:binary/0`]: private key secret. Default: nil -> random key will be generated + - `curve_name` [`t:atom/0`]: curve name. Default: :secp256k1 Returns: - - `private_key` [%Tezex.Crypto.PrivateKey]: private key struct + - `private_key` [`t:Tezex.Crypto.PrivateKey.t/0`]: private key struct ## Example: iex> Tezex.Crypto.PrivateKey.generate() %Tezex.Crypto.PrivateKey{...} """ - def generate(secret \\ nil, curve \\ :secp256k1) + @spec generate(nil | binary()) :: t() + @spec generate(nil | binary(), atom()) :: t() + def generate(secret \\ nil, curve_name \\ :secp256k1) - def generate(secret, curve) when is_nil(secret) do - generate(Utils.between(1, KnownCurves.get_curve_by_name(curve)."N" - 1), curve) + def generate(secret, curve_name) when is_nil(secret) do + curve = KnownCurves.get_curve_by_name(curve_name) + + Utils.between(1, curve."N" - 1) + |> Utils.string_from_number(Curve.get_length(curve)) + |> generate(curve_name) end - def generate(secret, curve) do + def generate(secret, curve_name) when is_binary(secret) and is_atom(curve_name) do %PrivateKey{ secret: secret, - curve: KnownCurves.get_curve_by_name(curve) + curve: KnownCurves.get_curve_by_name(curve_name) } end @@ -57,31 +68,35 @@ defmodule Tezex.Crypto.PrivateKey do Gets the public associated with a private key Parameters: - - `private_key` [%Tezex.Crypto.PrivateKey]: private key struct + - `private_key` [`t:Tezex.Crypto.PrivateKey.t/0`]: private key struct Returns: - - `public_key` [%Tezex.Crypto.PublicKey]: public key struct + - `public_key` [`t:Tezex.Crypto.PublicKey.t/0`]: public key struct ## Example: iex> Tezex.Crypto.PrivateKey.get_public_key(private_key) %Tezex.Crypto.PublicKey{...} """ + @spec get_public_key(t()) :: PublicKey.t() def get_public_key(private_key) do curve = private_key.curve + secret = Utils.number_from_string(private_key.secret) %PublicKey{ - point: Math.multiply(curve."G", private_key.secret, curve."N", curve."A", curve."P"), + point: Math.multiply(curve."G", secret, curve."N", curve."A", curve."P"), curve: curve } end @doc false + @spec to_string(t()) :: binary() def to_string(private_key) do - Utils.string_from_number(private_key.secret, Curve.get_length(private_key.curve)) + private_key.secret end @doc false + @spec from_string(binary()) :: {:error, map()} | {:ok, t()} def from_string(string, curve \\ :secp256k1) do {:ok, from_string!(string, curve)} rescue @@ -89,24 +104,17 @@ defmodule Tezex.Crypto.PrivateKey do end @doc false - def from_string!(string, curve \\ :secp256k1) do + def from_string!(string, curve \\ :secp256k1) when is_binary(string) do curve = KnownCurves.get_curve_by_name(curve) n = Utils.number_from_string(string) |> Utils.mod(curve."N") + |> Utils.string_from_number(Curve.get_length(curve)) %PrivateKey{ secret: n, curve: curve } end - - def to_point(%PrivateKey{} = pk) do - curve = pk.curve - g = curve."G" - g = %Point{x: g.x, y: g.y, z: 0} - - Math.multiply(g, pk.secret, curve."N", curve."A", curve."P") - end end diff --git a/lib/crypto/public_key.ex b/lib/crypto/public_key.ex index 5aed2f5..a62364a 100644 --- a/lib/crypto/public_key.ex +++ b/lib/crypto/public_key.ex @@ -3,15 +3,83 @@ defmodule Tezex.Crypto.PublicKey do Holds public key data. Parameters: - - `:point` [%Point]: public key point data; - - `:curve` [%Curve]: public key curve information. + - `:point` [`t:Tezex.Crypto.Point.t/0`]: public key point data; + - `:curve` [`t:Tezex.Crypto.Curve.t/0`]: public key curve information. """ defstruct [:point, :curve] - alias Tezex.Crypto.{Point, Curve} + alias Tezex.Crypto.Curve + alias Tezex.Crypto.KnownCurves + alias Tezex.Crypto.Math + alias Tezex.Crypto.Point + alias Tezex.Crypto.Utils @type t :: %__MODULE__{ point: Point.t(), curve: Curve.t() } + + @doc false + def to_string(public_key, encoded \\ false) do + curve_length = Curve.get_length(public_key.curve) + + x_string = + Utils.string_from_number( + public_key.point.x, + curve_length + ) + + y_string = + Utils.string_from_number( + public_key.point.y, + curve_length + ) + + if encoded do + "\x00\x04" <> x_string <> y_string + else + x_string <> y_string + end + end + + @doc false + def from_string(string, curve \\ :secp256k1, validate_point \\ true) do + {:ok, from_string!(string, curve, validate_point)} + rescue + e in RuntimeError -> {:error, e} + end + + @doc false + def from_string!(string, curve \\ :secp256k1, validate_point \\ true) do + curve = KnownCurves.get_curve_by_name(curve) + base_length = Curve.get_length(curve) + + xs = binary_part(string, 0, base_length) + ys = binary_part(string, base_length, byte_size(string) - base_length) + + point = %Point{ + x: Utils.number_from_string(xs), + y: Utils.number_from_string(ys) + } + + publicKey = %__MODULE__{point: point, curve: curve} + + cond do + validate_point == false -> + publicKey + + Point.is_at_infinity?(point) -> + raise "Public Key point is at infinity" + + Curve.contains?(curve, point) == false -> + raise "Point (#{point.x},#{point.y}) is not valid for curve #{curve.name}" + + Point.is_at_infinity?(Math.multiply(point, curve."N", curve."N", curve."A", curve."P")) == + false -> + raise "Point (#{point.x},#{point.y}) * #{curve.name}.N is not at infinity" + + true -> + publicKey + end + end end diff --git a/lib/crypto/signature.ex b/lib/crypto/signature.ex index 4180935..36b6715 100644 --- a/lib/crypto/signature.ex +++ b/lib/crypto/signature.ex @@ -3,8 +3,8 @@ defmodule Tezex.Crypto.Signature do Holds signature data. Parameters: - - `:r` [integer]: first signature number; - - `:s` [integer]: second signature number. + - `:r` [`t:pos_integer/0`]: first signature number; + - `:s` [`t:pos_integer/0`]: second signature number. """ defstruct [:r, :s] diff --git a/lib/micheline.ex b/lib/micheline.ex index be92c43..2caee78 100644 --- a/lib/micheline.ex +++ b/lib/micheline.ex @@ -63,19 +63,7 @@ defmodule Tezex.Micheline do # sequence def hex_to_micheline(<<"02", length::binary-size(8), rest::binary>>) do - length = hex_to_dec(length) * 2 - - {xs, consumed} = - Stream.cycle([nil]) - |> Enum.reduce_while({[], rest, 0}, fn _, {acc, part, consumed} -> - if consumed < length do - {content, length} = hex_to_micheline(part) - <<_consumed::binary-size(length), part::binary>> = part - {:cont, {[content | acc], part, consumed + length}} - else - {:halt, {acc, consumed}} - end - end) + {xs, consumed} = read_sequence(rest, 0, hex_to_dec(length) * 2, []) {Enum.reverse(xs), consumed + 8 + 2} end @@ -203,7 +191,7 @@ defmodule Tezex.Micheline do {%{bytes: bytes}, length + 2} end - @spec micheline_hex_to_string(binary) :: {binary, pos_integer} + @spec micheline_hex_to_string(binary()) :: {binary(), pos_integer()} def micheline_hex_to_string(<>) do length = hex_to_dec(length) * 2 <> = rest @@ -270,4 +258,14 @@ defmodule Tezex.Micheline do {d, ""} = Integer.parse(hex, 16) d end + + defp read_sequence(to_read, consumed, length, acc) when consumed < length do + {content, l} = hex_to_micheline(to_read) + <<_consumed::binary-size(l), to_read::binary>> = to_read + read_sequence(to_read, consumed + l, length, [content | acc]) + end + + defp read_sequence(_to_read, consumed, _length, acc) do + {acc, consumed} + end end diff --git a/lib/micheline/zarith.ex b/lib/micheline/zarith.ex index 09e4dd5..e47d164 100644 --- a/lib/micheline/zarith.ex +++ b/lib/micheline/zarith.ex @@ -20,7 +20,7 @@ defmodule Tezex.Micheline.Zarith do iex> Tezex.Micheline.Zarith.decode("00e1d22c") -365_729 """ - @spec decode(nonempty_binary) :: integer + @spec decode(nonempty_binary()) :: integer() def decode(binary_input) when is_binary(binary_input) do {%{int: integer}, _} = consume(binary_input) integer @@ -39,7 +39,7 @@ defmodule Tezex.Micheline.Zarith do iex> Tezex.Micheline.Zarith.encode(-365_729) "e1d22c" """ - @spec encode(integer) :: nonempty_binary + @spec encode(integer()) :: nonempty_binary() def encode(integer) when is_integer(integer) do sign = if integer < 0, do: 1, else: 0 @@ -108,7 +108,7 @@ defmodule Tezex.Micheline.Zarith do Takes a binary and returns the decoded integer in base 10 along with how many characters of the input binary were used to decode the integer. """ - @spec consume(nonempty_binary) :: {%{int: integer}, pos_integer} + @spec consume(nonempty_binary()) :: {%{int: integer()}, pos_integer()} def consume(binary_input) when is_binary(binary_input) do {carved_int, rest} = find_int(binary_input)