defmodule Bitcoinex.Secp256k1.Ecdsa do alias Bitcoinex.Secp256k1 alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey, Signature} @n Params.curve().n @generator_point %Point{ x: Params.curve().g_x, y: Params.curve().g_y } @doc """ deterministic_k implements rfc6979 and returns a deterministic k value for signatures """ @spec deterministic_k(PrivateKey.t(), non_neg_integer()) :: PrivateKey.t() def deterministic_k(%PrivateKey{d: d}, raw_z) do # RFC 6979 https://tools.ietf.org/html/rfc6979#section-3.2 k = :binary.list_to_bin(List.duplicate(<<0x00>>, 32)) v = :binary.list_to_bin(List.duplicate(<<0x01>>, 32)) n = @n z = lower_z(raw_z, n) # 3.2(d) - pad z and privkey for use sighash = Bitcoinex.Utils.pad(:binary.encode_unsigned(z), 32, :leading) secret = Bitcoinex.Utils.pad(:binary.encode_unsigned(d), 32, :leading) # 3.2(d) - hmac with key k k = :crypto.mac(:hmac, :sha256, k, v <> <<0x00>> <> secret <> sighash) # 3.2(e) - update v v = :crypto.mac(:hmac, :sha256, k, v) # 3.2(f) - update k k = :crypto.mac(:hmac, :sha256, k, v <> <<0x01>> <> secret <> sighash) # 3.2(g) - update v v = :crypto.mac(:hmac, :sha256, k, v) # 3.2(h) - algorithm final_k = find_candidate(k, v, n) %PrivateKey{d: final_k} end defp find_candidate(k, v, n) do # RFC 6979 https://tools.ietf.org/html/rfc6979#section-3.2 v = :crypto.mac(:hmac, :sha256, k, v) candidate = :binary.decode_unsigned(v) # 3.2(h).3 - check candidate in [1,n-1] and r != 0 if candidate >= 1 and candidate < n do case PrivateKey.to_point(%PrivateKey{d: candidate}) do {:error, _msg} -> find_next_candidate(k, v, n) pk -> if pk.x != 0 do candidate else find_next_candidate(k, v, n) end end else # if candidate is invalid find_next_candidate(k, v, n) end end defp find_next_candidate(k, v, n) do k = :crypto.mac(:hmac, :sha256, k, v <> <<0x00>>) v = :crypto.mac(:hmac, :sha256, k, v) find_candidate(k, v, n) end defp lower_z(z, n) do if z > n, do: z - n, else: z end @doc """ sign returns an ECDSA signature using the privkey and z where privkey is a PrivateKey object and z is an integer. The nonce is derived using RFC6979. """ @spec sign(PrivateKey.t(), integer) :: Signature.t() def sign(privkey, z) do case PrivateKey.validate(privkey) do {:error, msg} -> {:error, msg} {:ok, privkey} -> k = deterministic_k(privkey, z) case PrivateKey.to_point(k) do {:error, msg} -> {:error, msg} pk -> sig_r = pk.x inv_k = Math.inv(k.d, @n) sig_s = Math.modulo((z + sig_r * privkey.d) * inv_k, @n) if sig_s > @n / 2 do %Signature{r: sig_r, s: @n - sig_s} else %Signature{r: sig_r, s: sig_s} end end end end @doc """ sign_message returns an ECDSA signature using the privkey and "Bitcoin Signed Message: " where privkey is a PrivateKey object and msg is a binary message to be hashed. The message is hashed using hash256 (double SHA256) and the nonce is derived using RFC6979. """ @spec sign_message(PrivateKey.t(), binary) :: Signature.t() def sign_message(privkey, msg) do z = ("Bitcoin Signed Message:\n" <> msg) |> Bitcoinex.Utils.double_sha256() |> :binary.decode_unsigned() sign(privkey, z) end @doc """ verify whether the ecdsa signature is valid for the given message hash and public key """ @spec verify_signature(Point.t(), integer, Signature.t()) :: boolean def verify_signature(pubkey, sighash, %Signature{r: r, s: s}) do s_inv = Math.inv(s, @n) u = Math.modulo(sighash * s_inv, @n) v = Math.modulo(r * s_inv, @n) total = Math.add(Math.multiply(@generator_point, u), Math.multiply(pubkey, v)) total.x == r end @doc """ ecdsa_recover_compact does ECDSA public key recovery. """ @spec ecdsa_recover_compact(binary, binary, integer) :: {:ok, binary} | {:error, String.t()} def ecdsa_recover_compact(msg, compact_sig, recoveryId) do # Parse r and s from the signature. case Signature.parse_signature(compact_sig) do {:ok, sig} -> # Find the iteration. # R(x) = (n * i) + r # where n is the order of the curve and R is from the signature. r_x = @n * Integer.floor_div(recoveryId, 2) + sig.r # Check that R(x) is on the curve. if r_x > Params.curve().p do {:error, "R(x) is not on the curve"} else # Decompress to get R(y). case Secp256k1.get_y(r_x, rem(recoveryId, 2) == 1) do {:ok, r_y} -> # R(x,y) point_r = %Point{x: r_x, y: r_y} # Point Q is the recovered public key. # We satisfy this equation: Q = r^-1(sR-eG) inv_r = Math.inv(sig.r, @n) inv_r_s = (inv_r * sig.s) |> Math.modulo(@n) # R*s point_sr = Math.multiply(point_r, inv_r_s) # Find e using the message hash. e = :binary.decode_unsigned(msg) |> Kernel.*(-1) |> Math.modulo(@n) |> Kernel.*(inv_r |> Math.modulo(@n)) # G*e point_ge = Math.multiply(@generator_point, e) # R*e * G*e point_q = Math.add(point_sr, point_ge) # Returns serialized compressed public key. {:ok, Point.serialize_public_key(point_q)} {:error, error} -> {:error, error} end end {:error, e} -> {:error, e} end end end