text:
defmodule Bitcoinex.ExtendedKey do
  @moduledoc """
  	Contains an an extended key as documented in BIP 32.
  """
  alias Bitcoinex.Secp256k1.{Params, Point, PrivateKey, Math}
  alias Bitcoinex.Base58

  import Bitwise

  defmodule DerivationPath do
    @moduledoc """
    Contains a list of integers (or the :any atom) representing a bip32 derivation path.
    The :any atom represents a wildcard in the derivation path. DerivationPath structs can
    be used by ExtendedKey.derive_extended_key to derive a child key at the given path.
    """

    @min_non_hardened_child_num 0
    # 2^31 - 1
    @max_non_hardened_child_num 0x80000000 - 1
    @min_hardened_child_num 0x80000000
    # 2^32 - 1
    @max_hardened_child_num 0x100000000 - 1

    @type hardened_child_num ::
            unquote(@min_hardened_child_num)..unquote(@max_hardened_child_num)
    @type non_hardened_child_num ::
            unquote(@min_non_hardened_child_num)..unquote(@max_non_hardened_child_num)

    @type child_num :: hardened_child_num | non_hardened_child_num

    @type t :: %__MODULE__{
            child_nums: list(child_num)
          }

    @enforce_keys [
      :child_nums
    ]
    defstruct [:child_nums]

    def min_hardened_child_num(), do: @min_hardened_child_num
    def max_hardened_child_num(), do: @max_hardened_child_num

    def new(), do: %__MODULE__{child_nums: []}

    @spec to_string(t()) :: {:ok, String.t()} | {:error, String.t()}
    def to_string(%__MODULE__{child_nums: path}), do: tto_string(path, "")

    defp tto_string([], path_acc), do: {:ok, path_acc}

    defp tto_string([l | rest], path_acc) do
      cond do
        l == :any ->
          tto_string(rest, path_acc <> "*/")

        l == :anyh ->
          tto_string(rest, path_acc <> "*'/")

        l > @max_hardened_child_num ->
          {:error, "index cannot be greater than #{@max_hardened_child_num}"}

        l < @min_non_hardened_child_num ->
          {:error, "index cannot be less than #{@min_non_hardened_child_num}"}

        # hardened
        l >= @min_hardened_child_num ->
          tto_string(
            rest,
            path_acc <>
              (l
               |> Math.modulo(@min_hardened_child_num)
               |> Integer.to_string()
               |> Kernel.<>("'/"))
          )

        # unhardened
        true ->
          tto_string(rest, path_acc <> Integer.to_string(l) <> "/")
      end
    end

    @spec from_string(String.t()) :: {:ok, t()} | {:error, String.t()}
    def from_string(pathstr) do
      try do
        {:ok, %__MODULE__{child_nums: tfrom_string(String.split(pathstr, "/"))}}
      rescue
        e in ArgumentError -> {:error, e.message}
      end
    end

    defp tfrom_string(path_list) do
      case path_list do
        [] -> []
        [""] -> []
        ["m" | rest] -> tfrom_string(rest)
        ["*" | rest] -> [:any | tfrom_string(rest)]
        ["*'" | rest] -> [:anyh | tfrom_string(rest)]
        ["*h" | rest] -> [:anyh | tfrom_string(rest)]
        [i | rest] -> [str_to_level(i) | tfrom_string(rest)]
      end
    end

    defp str_to_level(level) do
      {num, is_hardened} =
        case String.split(level, ["'", "h"]) do
          [num] ->
            {num, false}

          [num, ""] ->
            {num, true}
        end

      nnum = String.to_integer(num)

      if nnum in @min_non_hardened_child_num..@max_non_hardened_child_num do
        if is_hardened do
          nnum + @min_hardened_child_num
        else
          nnum
        end
      else
        raise(ArgumentError, message: "invalid derivation path")
      end
    end

    def add(%__MODULE__{child_nums: path1}, %__MODULE__{child_nums: path2}),
      do: %__MODULE__{child_nums: path1 ++ path2}
  end

  @type t :: %__MODULE__{
          prefix: binary,
          depth: binary,
          parent_fingerprint: binary,
          child_num: binary,
          chaincode: binary,
          key: binary,
          checksum: binary
        }

  @enforce_keys [
    :prefix,
    :depth,
    :parent_fingerprint,
    :child_num,
    :chaincode,
    :key,
    :checksum
  ]

  defstruct [
    :prefix,
    :depth,
    :parent_fingerprint,
    :child_num,
    :chaincode,
    :key,
    :checksum
  ]

  @xpub_pfx <<0x04, 0x88, 0xB2, 0x1E>>
  @xprv_pfx <<0x04, 0x88, 0xAD, 0xE4>>
  @tpub_pfx <<0x04, 0x35, 0x87, 0xCF>>
  @tprv_pfx <<0x04, 0x35, 0x83, 0x94>>

  @prv_prefixes [
    @xprv_pfx,
    @tprv_pfx
  ]

  @pub_prefixes [
    @xpub_pfx,
    @tpub_pfx
  ]

  @all_prefixes @prv_prefixes ++ @pub_prefixes

  defp pfx_atom_to_bin(pfx) do
    case pfx do
      :xpub -> @xpub_pfx
      :xprv -> @xprv_pfx
      :tpub -> @tpub_pfx
      :tprv -> @tprv_pfx
    end
  end

  defp prv_to_pub_prefix(prv_pfx) do
    case prv_pfx do
      @xprv_pfx -> @xpub_pfx
      @tprv_pfx -> @tpub_pfx
    end
  end

  defp mainnet_prefixes do
    [
      @xpub_pfx,
      @xprv_pfx
    ]
  end

  @spec network_from_prefix(binary) :: atom
  defp network_from_prefix(prefix) do
    if prefix in mainnet_prefixes(), do: :mainnet, else: :testnet
  end

  @doc """
    network_from_extended_key returns :testnet or :mainnet
    depending on the network prefix of the key.
  """
  @spec network_from_extended_key(t()) :: atom
  def network_from_extended_key(%__MODULE__{prefix: prefix}), do: network_from_prefix(prefix)

  # GETTERS

  @spec get_prefix(t()) :: binary
  def get_prefix(%__MODULE__{prefix: prefix}), do: prefix

  @spec get_depth(t()) :: binary
  def get_depth(%__MODULE__{depth: depth}), do: depth

  @spec get_parent_fingerprint(t()) :: binary
  def get_parent_fingerprint(%__MODULE__{parent_fingerprint: pfp}), do: pfp

  @spec get_fingerprint(t()) :: binary
  def get_fingerprint(xkey = %__MODULE__{}) do
    if xkey.prefix in @prv_prefixes do
      {:ok, prvkey} =
        xkey.key
        |> :binary.decode_unsigned()
        |> PrivateKey.new()

      prvkey
      |> PrivateKey.to_point()
      |> Point.sec()
      |> Bitcoinex.Utils.hash160()
      |> :binary.part(0, 4)
    else
      xkey.key
      |> Bitcoinex.Utils.hash160()
      |> :binary.part(0, 4)
    end
  end

  @spec get_child_num(t()) :: binary
  def get_child_num(%__MODULE__{child_num: child_num}), do: child_num

  # PARSE & SERIALIZE

  @doc """
    parse_extended_key takes binary or string representation
    of an extended key and parses it to an extended key object
  """
  @spec parse_extended_key(binary) :: {:ok, t()} | {:error, String.t()}
  def parse_extended_key(
        xkey =
          <<prefix::binary-size(4), depth::binary-size(1), parent_fingerprint::binary-size(4),
            child_num::binary-size(4), chaincode::binary-size(32), key::binary-size(33),
            checksum::binary-size(4)>>
      ) do
    cond do
      prefix not in @all_prefixes ->
        {:error, "invalid prefix"}

      # BIP 32 instructs to check that public key is valid upon import
      prefix not in @prv_prefixes and not check_point(key) ->
        {:error, "invalid public key"}

      true ->
        case Base58.validate_checksum(xkey) do
          {:error, msg} ->
            {:error, msg}

          _ ->
            {:ok,
             %__MODULE__{
               prefix: prefix,
               depth: depth,
               parent_fingerprint: parent_fingerprint,
               child_num: child_num,
               chaincode: chaincode,
               key: key,
               checksum: checksum
             }}
        end
    end
  end

  # parse from string
  def parse_extended_key(xkey) do
    case Base58.decode(xkey) do
      {:error, _} ->
        {:error, "error parsing key"}

      {:ok, xkey} ->
        xkey
        |> Base58.append_checksum()
        |> parse_extended_key()
    end
  end

  # verify if point is valid on secp256k1
  defp check_point(key) do
    {:ok, pubkey} = Point.parse_public_key(key)
    Bitcoinex.Secp256k1.verify_point(pubkey)
  end

  @doc """
    serialize_extended_key takes an extended key
    and returns the binary
  """
  @spec serialize_extended_key(t()) :: binary
  def serialize_extended_key(xkey) do
    (xkey.prefix <>
       xkey.depth <> xkey.parent_fingerprint <> xkey.child_num <> xkey.chaincode <> xkey.key)
    |> Base58.append_checksum()
  end

  @doc """
    display returns the extended key as a string
  """
  @spec display_extended_key(t()) :: String.t()
  def display_extended_key(xkey) do
    xkey
    |> serialize_extended_key()
    |> Base58.encode_base()
  end

  @doc """
    seed_to_master_private_key transforms a bip32 seed
    into a master extended private key
  """
  @spec seed_to_master_private_key(binary, atom) :: {:ok, t()} | {:error, String.t()}
  def seed_to_master_private_key(<<seed::binary>>, pfx \\ :xprv) do
    prefix = pfx_atom_to_bin(pfx)

    if prefix in @prv_prefixes do
      <<key::binary-size(32), chaincode::binary-size(32)>> =
        :crypto.mac(:hmac, :sha512, "Bitcoin seed", seed)

      depth_fingerprint_childnum = <<0, 0, 0, 0, 0, 0, 0, 0, 0>>

      (prefix <> depth_fingerprint_childnum <> chaincode <> <<0>> <> key)
      |> Base58.append_checksum()
      |> parse_extended_key()
    else
      {:error, "invalid extended private key prefix"}
    end
  end

  @doc """
    to_extended_public_key takes an extended private key
    and returns an extended public key
  """
  @spec to_extended_public_key(t()) :: {:ok, t()} | {:error, String.t()}
  def to_extended_public_key(xprv) do
    if xprv.prefix in @prv_prefixes do
      try do
        {:ok, privkey} = PrivateKey.new(:binary.decode_unsigned(xprv.key, :big))

        pubkey =
          privkey
          |> PrivateKey.to_point()
          |> Point.sec()

        xprv.prefix
        |> prv_to_pub_prefix()
        |> Kernel.<>(xprv.depth)
        |> Kernel.<>(xprv.parent_fingerprint)
        |> Kernel.<>(xprv.child_num)
        |> Kernel.<>(xprv.chaincode)
        |> Kernel.<>(pubkey)
        |> Base58.append_checksum()
        |> parse_extended_key()
      rescue
        _ in MatchError -> {:error, "invalid private key"}
      end
    else
      # it is an xpub already
      xprv
    end
  end

  @doc """
    to_private_key takes an extended private key
    and returns the contained private key.
  """
  @spec to_private_key(t()) :: {:ok, PrivateKey.t()} | {:error, String.t()}
  def to_private_key(xprv) do
    if xprv.prefix in @prv_prefixes do
      secret = :binary.decode_unsigned(xprv.key, :big)
      PrivateKey.new(secret)
    else
      {:error, "key is not a extended private key."}
    end
  end

  @doc """
    to_public_key takes an extended key xkey and
    returns the public key.
  """
  @spec to_public_key(t()) :: {:ok, Point.t()} | {:error, String.t()}
  def to_public_key(xkey) do
    if xkey.prefix in @prv_prefixes do
      case to_private_key(xkey) do
        {:ok, prvkey} -> {:ok, PrivateKey.to_point(prvkey)}
        x -> x
      end
    else
      Point.parse_public_key(xkey.key)
    end
  end

  @doc """
    derive_child uses a public or private key xkey to
    derive the public or private key at index idx.
    public key -> public child
    private key -> private child
  """
  @spec derive_child_key(t(), non_neg_integer) :: {:ok, t()} | {:error, String.t()}
  def derive_child_key(xkey, idx) do
    if xkey.prefix in @prv_prefixes do
      derive_private_child(xkey, idx)
    else
      derive_public_child(xkey, idx)
    end
  end

  @doc """
    derive_public_child uses a public or private key xkey to
    derive the public key at index idx
  """
  @spec derive_public_child(t(), non_neg_integer) :: {:ok, t()} | {:error, String.t()}
  def derive_public_child(xkey, idx) do
    cond do
      xkey.prefix in @prv_prefixes ->
        {:ok, child_xprv} = derive_private_child(xkey, idx)
        to_extended_public_key(child_xprv)

      idx >= DerivationPath.min_hardened_child_num() or idx < 0 ->
        {:error, "idx must be in 0..2**31-1"}

      true ->
        child_depth = incr(xkey.depth)

        i =
          idx
          |> :binary.encode_unsigned()
          |> Bitcoinex.Utils.pad(4, :leading)

        fingerprint =
          xkey.key
          |> Bitcoinex.Utils.hash160()
          |> :binary.part(0, 4)

        ent = get_pub_child_entropy(xkey, idx)
        child_chaincode = :binary.part(ent, byte_size(ent), -32)

        key_secret =
          ent
          |> :binary.part(0, 32)
          |> :binary.decode_unsigned()

        if key_secret >= Params.curve().n do
          {:error, "invalid key derived. Bad luck!"}
        else
          {:ok, parent_pubkey} = Point.parse_public_key(xkey.key)
          {:ok, prvkey} = PrivateKey.new(key_secret)

          pubkey =
            prvkey
            |> PrivateKey.to_point()
            |> Math.add(parent_pubkey)

          if Point.is_inf(pubkey) do
            {:error, "pubkey is point at infinity, bad luck!"}
          else
            (xkey.prefix <>
               child_depth <> fingerprint <> i <> child_chaincode <> Point.sec(pubkey))
            |> Base58.append_checksum()
            |> parse_extended_key()
          end
        end
    end
  end

  @doc """
    derive_private_child uses a private key xkey to
    derive the private key at index idx
  """
  @spec derive_private_child(t(), non_neg_integer()) :: {:ok, t()} | {:error, String.t()}
  def derive_private_child(_, idx) when idx >>> 32 != 0, do: {:error, "idx must be in 0..2**32-1"}

  def derive_private_child(%{prefix: prefix}, _) when prefix not in @prv_prefixes,
    do: {:error, "public key cannot derive private child"}

  def derive_private_child(xkey, idx) do
    child_depth = incr(xkey.depth)

    i =
      idx
      |> :binary.encode_unsigned()
      |> Bitcoinex.Utils.pad(4, :leading)

    key_secret =
      xkey.key
      |> :binary.decode_unsigned()

    try do
      {:ok, prvkey} = PrivateKey.new(key_secret)

      fingerprint =
        prvkey
        |> PrivateKey.to_point()
        |> Point.sec()
        |> Bitcoinex.Utils.hash160()
        |> :binary.part(0, 4)

      ent = get_prv_child_entropy(xkey, idx)
      child_chaincode = :binary.part(ent, byte_size(ent), -32)

      child_key =
        ent
        |> :binary.part(0, 32)
        |> :binary.decode_unsigned()
        |> Kernel.+(key_secret)
        |> Bitcoinex.Secp256k1.Math.modulo(Params.curve().n)
        |> :binary.encode_unsigned()

      (xkey.prefix <> child_depth <> fingerprint <> i <> child_chaincode <> <<0>> <> child_key)
      |> Base58.append_checksum()
      |> parse_extended_key()
    rescue
      _ in MatchError -> {:error, "invalid private key in extended private key"}
    end
  end

  # increment byte by 1
  defp incr(byte) do
    byte
    |> :binary.decode_unsigned()
    |> Kernel.+(1)
    |> :binary.encode_unsigned()
  end

  # BIP32 spec
  defp get_prv_child_entropy(xprv, idx) do
    i =
      idx
      |> :binary.encode_unsigned()
      |> Bitcoinex.Utils.pad(4, :leading)

    if idx >= DerivationPath.min_hardened_child_num() do
      # hardened child from priv key
      :crypto.mac(:hmac, :sha512, xprv.chaincode, xprv.key <> i)
    else
      # unhardened child from privkey
      {:ok, prvkey} = PrivateKey.new(:binary.decode_unsigned(xprv.key))

      pubkey =
        prvkey
        |> PrivateKey.to_point()
        |> Point.sec()

      :crypto.mac(:hmac, :sha512, xprv.chaincode, pubkey <> i)
    end
  end

  defp get_pub_child_entropy(xpub, idx) do
    i =
      idx
      |> :binary.encode_unsigned()
      |> Bitcoinex.Utils.pad(4, :leading)

    :crypto.mac(:hmac, :sha512, xpub.chaincode, xpub.key <> i)
  end

  @doc """
    derive_extended_key uses an extended xkey and a derivation
    path to derive the extended key at that path
  """
  @spec derive_extended_key(t() | binary, DerivationPath.t()) :: {:ok, t()} | {:error, String.t()}
  def derive_extended_key(xkey = %__MODULE__{}, %DerivationPath{child_nums: path}),
    do: rderive_extended_key(xkey, path)

  def derive_extended_key(seed, %DerivationPath{child_nums: path}) do
    {:ok, xkey} = seed_to_master_private_key(seed)
    rderive_extended_key(xkey, path)
  end

  defp rderive_extended_key(xkey = %__MODULE__{}, []), do: {:ok, xkey}

  defp rderive_extended_key(xkey = %__MODULE__{}, [p | rest]) do
    try do
      case p do
        # if asterisk (:any) is in path, return the immediate parent xkey
        :any ->
          {:ok, xkey}

        # otherwise it is an integer, so derive child at that index.
        _ ->
          case derive_child_key(xkey, p) do
            {:ok, child_key} -> rderive_extended_key(child_key, rest)
            {:error, msg} -> {:error, msg}
          end
      end
    rescue
      e in ArgumentError -> {:error, e.message}
    end
  end
end


text:
defmodule Bitcoinex.Address do
  @moduledoc """
  Bitcoinex.Address supports Base58 and Bech32 address encoding and validation.
  """

  alias Bitcoinex.{Segwit, Base58, Network}

  @typedoc """
    The address_type describes the address type to use.

    Four address types are supported:
    * p2pkh: Pay-to-Public-Key-Hash
    * p2sh: Pay-to-Script-Hash
    * p2wpkh: Pay-to-Witness-Public-Key-Hash
    * p2wsh: Pay-To-Witness-Script-Hash
  """
  @type address_type :: :p2pkh | :p2sh | :p2wpkh | :p2wsh
  @address_types ~w(p2pkh p2sh p2wpkh p2wsh p2tr)a

  @doc """
  Accepts a public key hash, network, and address_type and returns its address.
  """
  @spec encode(binary, Bitcoinex.Network.network_name(), address_type) :: String.t()
  def encode(pubkey_hash, network_name, :p2pkh) do
    network = Network.get_network(network_name)
    decimal_prefix = network.p2pkh_version_decimal_prefix

    Base58.encode(<<decimal_prefix>> <> pubkey_hash)
  end

  def encode(script_hash, network_name, :p2sh) do
    network = Network.get_network(network_name)
    decimal_prefix = network.p2sh_version_decimal_prefix
    Base58.encode(<<decimal_prefix>> <> script_hash)
  end

  @doc """
  Checks if the address is valid.
  Both encoding and network is checked.
  """
  @spec is_valid?(String.t(), Bitcoinex.Network.network_name()) :: boolean
  def is_valid?(address, network_name) do
    Enum.any?(@address_types, &is_valid?(address, network_name, &1))
  end

  @doc """
  Checks if the address is valid and matches the given address_type.
  Both encoding and network is checked.
  """
  @spec is_valid?(String.t(), Bitcoinex.Network.network_name(), address_type) :: boolean
  def is_valid?(address, network_name, :p2pkh) do
    network = apply(Bitcoinex.Network, network_name, [])
    is_valid_base58_check_address?(address, network.p2pkh_version_decimal_prefix)
  end

  def is_valid?(address, network_name, :p2sh) do
    network = apply(Bitcoinex.Network, network_name, [])
    is_valid_base58_check_address?(address, network.p2sh_version_decimal_prefix)
  end

  def is_valid?(address, network_name, :p2wpkh) do
    case Segwit.decode_address(address) do
      {:ok, {^network_name, witness_version, witness_program}}
      when witness_version == 0 and length(witness_program) == 20 ->
        true

      # network is not same as network set in config
      {:ok, {_network_name, _, _}} ->
        false

      {:error, _error} ->
        false
    end
  end

  def is_valid?(address, network_name, :p2wsh) do
    case Segwit.decode_address(address) do
      {:ok, {^network_name, witness_version, witness_program}}
      when witness_version == 0 and length(witness_program) == 32 ->
        true

      # network is not same as network set in config
      {:ok, {_network_name, _, _}} ->
        false

      {:error, _error} ->
        false
    end
  end

  def is_valid?(address, network_name, :p2tr) do
    case Segwit.decode_address(address) do
      {:ok, {^network_name, witness_version, witness_program}}
      when witness_version == 1 and length(witness_program) == 32 ->
        true

      # network is not same as network set in config
      {:ok, {_network_name, _, _}} ->
        false

      {:error, _error} ->
        false
    end
  end

  @doc """
  Returns a list of supported address types.
  """
  def supported_address_types() do
    @address_types
  end

  defp is_valid_base58_check_address?(address, valid_prefix) do
    case Base58.decode(address) do
      {:ok, <<^valid_prefix::8, _::binary>>} ->
        true

      _ ->
        false
    end
  end

  @doc """
  Decodes an address and returns the address_type.
  """
  @spec decode_type(String.t(), Bitcoinex.Network.network_name()) ::
          {:ok, address_type} | {:error, :decode_error}
  def decode_type(address, network_name) do
    case Enum.find(@address_types, &is_valid?(address, network_name, &1)) do
      nil -> {:error, :decode_error}
      type -> {:ok, type}
    end
  end
end

repo_name: RiverFinancial/bitcoinex

repo_language: Elixir

mime_type: text/plain


text:
defmodule Bitcoinex.PSBT do
  @moduledoc """
  Support for Partially Signed Bitcoin Transactions (PSBT).

  The format consists of key-value maps.
  Each map consists of a sequence of key-value records, terminated by a 0x00 byte.

  Reference: https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki
  """
  alias Bitcoinex.PSBT
  alias Bitcoinex.PSBT.Global
  alias Bitcoinex.PSBT.In
  alias Bitcoinex.PSBT.Out
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  @type t() :: %__MODULE__{}

  defstruct [
    :global,
    :inputs,
    :outputs
  ]

  @magic 0x70736274
  @separator 0xFF

  def separator, do: @separator

  @doc """
  Decodes a base64 encoded string into a PSBT.
  """
  @spec decode(String.t()) :: {:ok, t()} | {:error, term()}
  def decode(psbt_b64) when is_binary(psbt_b64) do
    case Base.decode64(psbt_b64) do
      {:ok, psbt_b64} ->
        parse(psbt_b64)

      :error ->
        {:error, :decode_error}
    end
  end

  @doc """
    Decodes a binary-encoded PSBT file.
  """
  @spec from_file(String.t()) :: {:ok, t()} | {:error, term()}
  def from_file(filename) do
    filename
    |> File.read!()
    |> parse()
  end

  @spec serialize(t()) :: binary()
  defp serialize(packet) do
    global = Global.serialize_global(packet.global)
    inputs = In.serialize_inputs(packet.inputs)
    outputs = Out.serialize_outputs(packet.outputs)

    <<@magic::big-size(32)>> <>
      <<@separator::big-size(8)>> <>
      global <> inputs <> outputs
  end

  @doc """
    to_file writes a PSBT to file as binary.
  """
  @spec to_file(t(), String.t()) :: :ok | {:error, File.posix()}
  def to_file(packet, filename) do
    bin = serialize(packet)
    File.write(filename, bin)
  end

  @spec encode_b64(t()) :: String.t()
  def encode_b64(packet) do
    packet
    |> serialize()
    |> Base.encode64()
  end

  defp parse(<<@magic::big-size(32), @separator::big-size(8), psbt::binary>>) do
    # key-value pairs for all global data
    {global, psbt} = Global.parse_global(psbt)
    in_counter = length(global.unsigned_tx.inputs)
    {inputs, psbt} = In.parse_inputs(psbt, in_counter)
    out_counter = length(global.unsigned_tx.outputs)
    {outputs, _} = Out.parse_outputs(psbt, out_counter)

    {:ok,
     %PSBT{
       global: global,
       inputs: inputs,
       outputs: outputs
     }}
  end
end

defmodule Bitcoinex.PSBT.Utils do
  @moduledoc """
  Contains utility functions used throughout PSBT serialization.
  """
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  def parse_compact_size_value(key_value) do
    {len, key_value} = TxUtils.get_counter(key_value)
    <<value::binary-size(len), remaining::binary>> = key_value
    {value, remaining}
  end

  # parses key value pairs with a provided parse function
  def parse_key_value(psbt, kv, parse_func) do
    case psbt do
      # separator
      <<0x00::big-size(8), psbt::binary>> ->
        {kv, psbt}

      _ ->
        case parse_compact_size_value(psbt) do
          {key, psbt} ->
            {kv, psbt} = parse_func.(key, psbt, kv)
            parse_key_value(psbt, kv, parse_func)
        end
    end
  end

  def serialize_kv(key, val) do
    key_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(key))
    val_len = TxUtils.serialize_compact_size_unsigned_int(byte_size(val))
    key_len <> key <> val_len <> val
  end
end

defmodule Bitcoinex.PSBT.Global do
  @moduledoc """
  Global properties of a partially signed bitcoin transaction.
  """
  alias Bitcoinex.PSBT.Global
  alias Bitcoinex.Transaction
  alias Bitcoinex.Transaction.Utils, as: TxUtils
  alias Bitcoinex.PSBT.Utils, as: PsbtUtils
  alias Bitcoinex.Base58

  defstruct [
    :unsigned_tx,
    :xpub,
    :version,
    :proprietary
  ]

  @psbt_global_unsigned_tx 0x00
  @psbt_global_xpub 0x01
  @psbt_global_version 0xFB
  @psbt_global_proprietary 0xFC

  def parse_global(psbt) do
    PsbtUtils.parse_key_value(psbt, %Global{}, &parse/3)
  end

  # unsigned transaction
  defp parse(<<@psbt_global_unsigned_tx::big-size(8)>>, psbt, global) do
    {txn_len, psbt} = TxUtils.get_counter(psbt)

    <<txn_bytes::binary-size(txn_len), psbt::binary>> = psbt
    # todo, different decode function for txn, directly in bytes
    case Transaction.decode(Base.encode16(txn_bytes, case: :lower)) do
      {:ok, txn} ->
        {%Global{global | unsigned_tx: txn}, psbt}

      {:error, error_msg} ->
        {:error, error_msg}
    end
  end

  defp parse(<<@psbt_global_xpub::big-size(8), xpub::binary-size(78)>>, psbt, global) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)

    <<master::little-unsigned-32, paths::binary>> = value

    indexes = for <<chunk::little-unsigned-32 <- paths>>, do: chunk

    global_xpub =
      case global.xpub do
        nil ->
          [
            %{
              xpub: Base58.encode(xpub),
              master_pfp: master,
              derivation: indexes
            }
          ]

        _ ->
          global.xpub ++
            [
              %{
                xpub: Base58.encode(xpub),
                master_pfp: master,
                derivation: indexes
              }
            ]
      end

    global = %Global{global | xpub: global_xpub}

    {global, psbt}
  end

  defp parse(<<@psbt_global_version::big-size(8)>>, psbt, global) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    global = %Global{global | version: value}
    {global, psbt}
  end

  defp parse(<<@psbt_global_proprietary::big-size(8)>>, psbt, global) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    global = %Global{global | proprietary: value}
    {global, psbt}
  end

  defp serialize_kv(:unsigned_tx, value) when value != nil do
    PsbtUtils.serialize_kv(<<@psbt_global_unsigned_tx::big-size(8)>>, TxUtils.serialize(value))
  end

  defp serialize_kv(:xpub, value) when value != nil do
    key = <<@psbt_global_xpub::big-size(8)>>
    {:ok, key_data} = Base58.decode(value.xpub)

    val =
      <<value.master_pfp::little-size(32)>> <>
        (for(chunk <- value.derivation, do: <<chunk::little-size(32)>>)
         |> :erlang.list_to_binary())

    PsbtUtils.serialize_kv(key <> key_data, val)
  end

  def serialize_global(global) do
    # TODO: serialize all other fields in global.
    serialized_global = serialize_kv(:unsigned_tx, global.unsigned_tx)

    bip32 =
      if global.xpub != nil do
        for(bip32 <- global.xpub, do: serialize_kv(:xpub, bip32))
        |> :erlang.list_to_binary()
      else
        <<>>
      end

    serialized_global <> bip32 <> <<0x00::big-size(8)>>
  end
end

defmodule Bitcoinex.PSBT.In do
  @moduledoc """
  Input properties of a partially signed bitcoin transaction.
  """
  alias Bitcoinex.Transaction
  alias Bitcoinex.Transaction.Witness
  alias Bitcoinex.Transaction.Out
  alias Bitcoinex.PSBT.In
  alias Bitcoinex.PSBT.Utils, as: PsbtUtils
  alias Bitcoinex.Transaction.Utils, as: TxUtils

  defstruct [
    :non_witness_utxo,
    :witness_utxo,
    :partial_sig,
    :sighash_type,
    :redeem_script,
    :witness_script,
    :bip32_derivation,
    :final_scriptsig,
    :final_scriptwitness,
    :por_commitment,
    :proprietary
  ]

  @psbt_in_non_witness_utxo 0x00
  @psbt_in_witness_utxo 0x01
  @psbt_in_partial_sig 0x02
  @psbt_in_sighash_type 0x03
  @psbt_in_redeem_script 0x04
  @psbt_in_witness_script 0x05
  @psbt_in_bip32_derivation 0x06
  @psbt_in_final_scriptsig 0x07
  @psbt_in_final_scriptwitness 0x08
  @psbt_in_por_commitment 0x09
  @psbt_in_proprietary 0xFC

  def parse_inputs(psbt, num_inputs) do
    psbt
    |> parse_input([], num_inputs)
  end

  defp serialize_kv(:non_witness_utxo, value) when value != nil do
    PsbtUtils.serialize_kv(<<@psbt_in_non_witness_utxo::big-size(8)>>, TxUtils.serialize(value))
  end

  defp serialize_kv(:witness_utxo, value) when value != nil do
    script = Base.decode16!(value.script_pub_key, case: :lower)

    val =
      <<value.value::little-size(64)>> <>
        TxUtils.serialize_compact_size_unsigned_int(byte_size(script)) <> script

    PsbtUtils.serialize_kv(<<@psbt_in_witness_utxo::big-size(8)>>, val)
  end

  defp serialize_kv(:partial_sig, value) when value != nil do
    key_data = Base.decode16!(value.public_key, case: :lower)
    val = Base.decode16!(value.signature, case: :lower)

    PsbtUtils.serialize_kv(<<@psbt_in_partial_sig::big-size(8)>> <> key_data, val)
  end

  defp serialize_kv(:sighash_type, value) when value != nil do
    PsbtUtils.serialize_kv(<<@psbt_in_sighash_type::big-size(8)>>, value)
  end

  defp serialize_kv(:final_scriptsig, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_in_final_scriptsig::big-size(8)>>,
      Base.decode16!(value, case: :lower)
    )
  end

  defp serialize_kv(:redeem_script, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_in_redeem_script::big-size(8)>>,
      Base.decode16!(value, case: :lower)
    )
  end

  defp serialize_kv(:witness_script, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_in_witness_script::big-size(8)>>,
      Base.decode16!(value, case: :lower)
    )
  end

  defp serialize_kv(:final_scriptwitness, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_in_final_scriptwitness::big-size(8)>>,
      Witness.serialize_witness([value])
    )
  end

  defp serialize_kv(:bip32_derivation, value) when value != nil do
    key_data = Base.decode16!(value.public_key, case: :lower)

    val =
      <<value.pfp::little-size(32)>> <>
        (for(chunk <- value.derivation, do: <<chunk::little-size(32)>>)
         |> :erlang.list_to_binary())

    PsbtUtils.serialize_kv(<<@psbt_in_bip32_derivation::big-size(8)>> <> key_data, val)
  end

  defp serialize_kv(_key, _value) do
    <<>>
  end

  def serialize_inputs(inputs) when is_list(inputs) and length(inputs) > 0 do
    serialize_input(inputs, <<>>)
  end

  def serialize_inputs(_inputs) do
    <<>>
  end

  defp serialize_input([], serialized_inputs), do: serialized_inputs

  defp serialize_input(inputs, serialized_inputs) do
    [input | inputs] = inputs

    serialized_input =
      Enum.reduce(
        [
          :non_witness_utxo,
          :witness_utxo,
          :sighash_type,
          :partial_sig,
          :redeem_script,
          :final_scriptsig,
          :witness_script
        ],
        <<>>,
        fn k, acc ->
          case Map.get(input, k) do
            nil ->
              acc

            v ->
              acc <> serialize_kv(k, v)
          end
        end
      )

    bip32 =
      if input.bip32_derivation != nil do
        for(bip32 <- input.bip32_derivation, do: serialize_kv(:bip32_derivation, bip32))
        |> :erlang.list_to_binary()
      else
        <<>>
      end

    serialized_input =
      serialized_input <>
        bip32 <>
        serialize_kv(:final_scriptwitness, input.final_scriptwitness) <> <<0x00::big-size(8)>>

    serialize_input(inputs, serialized_inputs <> serialized_input)
  end

  defp parse_input(psbt, inputs, 0), do: {Enum.reverse(inputs), psbt}

  defp parse_input(psbt, inputs, num_inputs) do
    case PsbtUtils.parse_key_value(psbt, %In{}, &parse/3) do
      {nil, psbt} ->
        parse_input(psbt, inputs, num_inputs - 1)

      {input, psbt} ->
        input =
          case input do
            %{bip32_derivation: bip32_derivation} when is_list(bip32_derivation) ->
              %{input | bip32_derivation: Enum.reverse(bip32_derivation)}

            _ ->
              input
          end

        parse_input(psbt, [input | inputs], num_inputs - 1)
    end
  end

  defp parse(<<@psbt_in_non_witness_utxo::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    {:ok, txn} = Transaction.decode(Base.encode16(value, case: :lower))
    input = %In{input | non_witness_utxo: txn}
    {input, psbt}
  end

  defp parse(<<@psbt_in_witness_utxo::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    out = Out.output(value)
    input = %In{input | witness_utxo: out}
    {input, psbt}
  end

  defp parse(<<@psbt_in_partial_sig::big-size(8), public_key::binary-size(33)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)

    input = %In{
      input
      | partial_sig: %{
          public_key: Base.encode16(public_key, case: :lower),
          signature: Base.encode16(value, case: :lower)
        }
    }

    {input, psbt}
  end

  defp parse(<<@psbt_in_sighash_type::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | sighash_type: value}
    {input, psbt}
  end

  defp parse(<<@psbt_in_redeem_script::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | redeem_script: Base.encode16(value, case: :lower)}
    {input, psbt}
  end

  defp parse(<<@psbt_in_witness_script::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | witness_script: Base.encode16(value, case: :lower)}
    {input, psbt}
  end

  defp parse(<<@psbt_in_bip32_derivation::big-size(8), public_key::binary-size(33)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)

    <<pfp::little-unsigned-32, paths::binary>> = value
    indexes = for <<chunk::little-unsigned-32 <- paths>>, do: chunk

    bip32_derivation =
      case input.bip32_derivation do
        nil ->
          [
            %{
              public_key: Base.encode16(public_key, case: :lower),
              pfp: pfp,
              derivation: indexes
            }
          ]

        _ ->
          [
            %{
              public_key: Base.encode16(public_key, case: :lower),
              pfp: pfp,
              derivation: indexes
            }
            | input.bip32_derivation
          ]
      end

    input = %In{input | bip32_derivation: bip32_derivation}
    {input, psbt}
  end

  defp parse(<<@psbt_in_final_scriptsig::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | final_scriptsig: Base.encode16(value, case: :lower)}
    {input, psbt}
  end

  defp parse(<<@psbt_in_por_commitment::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | por_commitment: value}
    {input, psbt}
  end

  defp parse(<<@psbt_in_proprietary::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    input = %In{input | proprietary: value}
    {input, psbt}
  end

  defp parse(<<@psbt_in_final_scriptwitness::big-size(8)>>, psbt, input) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    value = Witness.witness(value)
    input = %In{input | final_scriptwitness: value}
    {input, psbt}
  end
end

defmodule Bitcoinex.PSBT.Out do
  @moduledoc """
  Output properties of a partially signed bitcoin transaction.
  """
  alias Bitcoinex.PSBT.Out
  alias Bitcoinex.PSBT.Utils, as: PsbtUtils

  defstruct [
    :redeem_script,
    :witness_script,
    :bip32_derivation,
    :proprietary
  ]

  @psbt_out_redeem_script 0x00
  @psbt_out_scriptwitness 0x01
  @psbt_out_bip32_derivation 0x02

  def serialize_outputs(outputs) when is_list(outputs) and length(outputs) > 0 do
    serialize_output(outputs, <<>>)
  end

  def serialize_outputs(_outputs) do
    <<>>
  end

  defp serialize_kv(:redeem_script, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_out_redeem_script::big-size(8)>>,
      Base.decode16!(value, case: :lower)
    )
  end

  defp serialize_kv(:witness_script, value) when value != nil do
    PsbtUtils.serialize_kv(
      <<@psbt_out_scriptwitness::big-size(8)>>,
      Base.decode16!(value, case: :lower)
    )
  end

  defp serialize_kv(:bip32_derivation, value) when value != nil do
    key_data = Base.decode16!(value.public_key, case: :lower)

    val =
      <<value.pfp::little-size(32)>> <>
        (for(chunk <- value.derivation, do: <<chunk::little-size(32)>>)
         |> :erlang.list_to_binary())

    PsbtUtils.serialize_kv(<<@psbt_out_bip32_derivation::big-size(8)>> <> key_data, val)
  end

  defp serialize_kv(_key, _value) do
    <<>>
  end

  defp serialize_output([], serialize_outputs), do: serialize_outputs

  defp serialize_output(outputs, serialized_outputs) do
    [output | outputs] = outputs

    serialized_output =
      case output do
        %Out{bip32_derivation: nil, proprietary: nil, redeem_script: nil, witness_script: nil} ->
          <<0x00::big-size(8)>>

        _ ->
          serialized_output =
            serialize_kv(:redeem_script, output.redeem_script) <>
              serialize_kv(:witness_script, output.witness_script)

          bip32 =
            if output.bip32_derivation != nil do
              for(bip32 <- output.bip32_derivation, do: serialize_kv(:bip32_derivation, bip32))
              |> :erlang.list_to_binary()
            else
              <<>>
            end

          serialized_output <> bip32 <> <<0x00::big-size(8)>>
      end

    serialize_output(outputs, serialized_outputs <> serialized_output)
  end

  def parse_outputs(psbt, num_outputs) do
    parse_output(psbt, [], num_outputs)
  end

  defp parse_output(psbt, outputs, 0), do: {Enum.reverse(outputs), psbt}

  defp parse_output(psbt, outputs, num_outputs) do
    case PsbtUtils.parse_key_value(psbt, %Out{}, &parse/3) do
      {output = %Out{
         bip32_derivation: nil,
         proprietary: nil,
         redeem_script: nil,
         witness_script: nil
       }, psbt} ->
        parse_output(psbt, [output | outputs], num_outputs - 1)

      {output, psbt} ->
        output =
          case output do
            %{bip32_derivation: bip32_derivation} when is_list(bip32_derivation) ->
              %{output | bip32_derivation: Enum.reverse(bip32_derivation)}

            _ ->
              output
          end

        parse_output(psbt, [output | outputs], num_outputs - 1)
    end
  end

  defp parse(<<@psbt_out_redeem_script::big-size(8)>>, psbt, output) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    output = %Out{output | redeem_script: Base.encode16(value, case: :lower)}
    {output, psbt}
  end

  defp parse(<<@psbt_out_scriptwitness::big-size(8)>>, psbt, output) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)
    output = %Out{output | witness_script: Base.encode16(value, case: :lower)}
    {output, psbt}
  end

  defp parse(
         <<@psbt_out_bip32_derivation::big-size(8), public_key::binary-size(33)>>,
         psbt,
         output
       ) do
    {value, psbt} = PsbtUtils.parse_compact_size_value(psbt)

    <<pfp::little-unsigned-32, paths::binary>> = value
    indexes = for <<chunk::little-unsigned-32 <- paths>>, do: chunk

    bip32_derivation =
      case output.bip32_derivation do
        nil ->
          [
            %{
              public_key: Base.encode16(public_key, case: :lower),
              pfp: pfp,
              derivation: indexes
            }
          ]

        _ ->
          [
            %{
              public_key: Base.encode16(public_key, case: :lower),
              pfp: pfp,
              derivation: indexes
            }
            | output.bip32_derivation
          ]
      end

    output = %Out{
      output
      | bip32_derivation: bip32_derivation
    }

    {output, psbt}
  end
end


text:
defmodule Bitcoinex.Secp256k1.Params do
  @doc """
  Secp256k1 parameters.
  http://www.secg.org/sec2-v2.pdf
  """
  @spec curve :: %{
          p: non_neg_integer(),
          a: non_neg_integer(),
          b: non_neg_integer(),
          g_x: non_neg_integer(),
          g_y: non_neg_integer(),
          n: non_neg_integer(),
          h: non_neg_integer()
        }
  def curve do
    %{
      p: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_FFFFFC2F,
      a: 0x00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000000,
      b: 0x00000000_00000000_00000000_00000000_00000000_00000000_00000000_00000007,
      g_x: 0x79BE667E_F9DCBBAC_55A06295_CE870B07_029BFCDB_2DCE28D9_59F2815B_16F81798,
      g_y: 0x483ADA77_26A3C465_5DA4FBFC_0E1108A8_FD17B448_A6855419_9C47D08F_FB10D4B8,
      n: 0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFE_BAAEDCE6_AF48A03B_BFD25E8C_D0364141,
      h: 0x01
    }
  end
end


text:
defmodule Bitcoinex.Secp256k1.PrivateKey do
  @moduledoc """
   	Contains an integer used to create a Point and sign.
  """
  alias Bitcoinex.Secp256k1.{Params, Math, Point}
  alias Bitcoinex.Base58
  alias Bitcoinex.Utils

  @n Params.curve().n

  @max_privkey @n - 1

  @type t :: %__MODULE__{
          d: non_neg_integer()
        }

  @enforce_keys [
    :d
  ]
  defstruct [:d]

  def validate(%__MODULE__{d: d}) do
    if d > @max_privkey do
      {:error, "invalid private key out of range."}
    else
      {:ok, %__MODULE__{d: d}}
    end
  end

  @doc """
    new creates a private key from an integer
  """
  @spec new(non_neg_integer()) :: {:ok, t()} | {:error, String.t()}
  def new(d) do
    validate(%__MODULE__{d: d})
  end

  @doc """
    to_point calculate Point from private key or integer
  """
  @spec to_point(t() | non_neg_integer()) :: Point.t() | {:error, String.t()}
  def to_point(prvkey = %__MODULE__{}) do
    case validate(prvkey) do
      {:error, msg} ->
        {:error, msg}

      {:ok, %__MODULE__{d: d}} ->
        g = %Point{x: Params.curve().g_x, y: Params.curve().g_y, z: 0}
        Math.multiply(g, d)
    end
  end

  def to_point(d) when is_integer(d) do
    case new(d) do
      {:ok, sk} ->
        to_point(sk)

      {:error, msg} ->
        {:error, msg}
    end
  end

  @doc """
   serialize_private_key serializes a private key into hex
  """
  @spec serialize_private_key(t()) :: String.t()
  def serialize_private_key(prvkey = %__MODULE__{}) do
    case validate(prvkey) do
      {:error, msg} ->
        {:error, msg}

      {:ok, %__MODULE__{d: d}} ->
        d
        |> :binary.encode_unsigned()
        |> Utils.pad(32, :leading)
        |> Base.encode16(case: :lower)
    end
  end

  @doc """
  wif returns the base58check encoded private key as a string
  assumes all keys are compressed
  """
  @spec wif!(t(), Bitcoinex.Network.network_name()) :: String.t()
  def wif!(prvkey = %__MODULE__{}, network_name) do
    case validate(prvkey) do
      {:error, msg} ->
        {:error, msg}

      {:ok, %__MODULE__{d: d}} ->
        d
        |> :binary.encode_unsigned()
        |> Utils.pad(32, :leading)
        |> wif_prefix(network_name)
        |> compressed_suffix()
        |> Base58.encode()
    end
  end

  def parse_wif!(wif_str) do
    {:ok, privkey, _, _} = parse_wif(wif_str)
    privkey
  end

  @doc """
  returns the base58check encoded private key as a string
  assumes all keys are compressed
  """
  @spec parse_wif(String.t()) :: {:ok, t(), atom, boolean}
  def parse_wif(wif_str) do
    {state, bin} = Base58.decode(wif_str)

    case state do
      :error -> {:error, bin}
      :ok -> parse_wif_bin(bin)
    end
  end

  # parse compressed
  @spec parse_wif_bin(binary) :: {:ok, t(), atom, boolean}
  def parse_wif_bin(<<prefix::binary-size(1), wif::binary-size(32), 0x01>>) do
    {state, network_name} = wif_prefix(prefix)

    if state == :error do
      {:error, network_name}
    else
      secret = :binary.decode_unsigned(wif)

      case validate(%__MODULE__{d: secret}) do
        {:error, msg} ->
          {:error, msg}

        {:ok, %__MODULE__{d: d}} ->
          {:ok, %__MODULE__{d: d}, network_name, true}
      end
    end
  end

  # parse uncompressed
  def parse_wif_bin(<<prefix::binary-size(1), wif::binary-size(32)>>) do
    {state, network_name} = wif_prefix(prefix)

    if state == :error do
      {:error, network_name}
    else
      secret = :binary.decode_unsigned(wif)

      case validate(%__MODULE__{d: secret}) do
        {:error, msg} ->
          {:error, msg}

        {:ok, %__MODULE__{d: d}} ->
          {:ok, %__MODULE__{d: d}, network_name, false}
      end
    end
  end

  defp compressed_suffix(binary), do: binary <> <<0x01>>
  # encoding
  defp wif_prefix(binary, :mainnet), do: <<0x80>> <> binary
  defp wif_prefix(binary, :testnet), do: <<0xEF>> <> binary
  defp wif_prefix(binary, :regtest), do: <<0xEF>> <> binary
  # decoding
  defp wif_prefix(<<0x80>>), do: {:ok, :mainnet}
  defp wif_prefix(<<0xEF>>), do: {:ok, :testnet}
  defp wif_prefix(_), do: {:error, "unrecognized network prefix for WIF"}
end


text:
defmodule Bitcoinex.Secp256k1.Schnorr do
  @moduledoc """
  Schnorr-specific secp256k1 operations
  """
  alias Bitcoinex.Secp256k1
  alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey, Signature}
  alias Bitcoinex.Utils

  @n Params.curve().n
  @p Params.curve().p

  @generator_point %Point{
    x: Params.curve().g_x,
    y: Params.curve().g_y
  }

  @spec sign(PrivateKey.t(), non_neg_integer(), non_neg_integer()) ::
          {:ok, Signature.t()} | {:error, String.t()}
  def sign(privkey, z, aux) do
    case PrivateKey.validate(privkey) do
      {:error, msg} ->
        {:error, msg}

      {:ok, privkey} ->
        z_bytes = Utils.int_to_big(z, 32)
        aux_bytes = Utils.int_to_big(aux, 32)
        d_point = PrivateKey.to_point(privkey)

        case Secp256k1.force_even_y(privkey) do
          {:error, msg} ->
            {:error, msg}

          d ->
            d_bytes = Utils.int_to_big(d.d, 32)
            tagged_aux_hash = tagged_hash_aux(aux_bytes)
            t = Utils.xor_bytes(d_bytes, tagged_aux_hash)

            {:ok, k0} =
              tagged_hash_nonce(t <> Point.x_bytes(d_point) <> z_bytes)
              |> :binary.decode_unsigned()
              |> Math.modulo(@n)
              |> PrivateKey.new()

            if k0.d == 0 do
              {:error, "invalid aux randomness"}
            else
              r_point = PrivateKey.to_point(k0)

              case Secp256k1.force_even_y(k0) do
                {:error, msg} ->
                  {:error, msg}

                k ->
                  e =
                    tagged_hash_challenge(
                      Point.x_bytes(r_point) <> Point.x_bytes(d_point) <> z_bytes
                    )
                    |> :binary.decode_unsigned()
                    |> Math.modulo(@n)

                  sig_s =
                    (k.d + d.d * e)
                    |> Math.modulo(@n)

                  {:ok, %Signature{r: r_point.x, s: sig_s}}
              end
            end
        end
    end
  end

  defp tagged_hash_aux(aux), do: Utils.tagged_hash("BIP0340/aux", aux)
  defp tagged_hash_nonce(nonce), do: Utils.tagged_hash("BIP0340/nonce", nonce)
  defp tagged_hash_challenge(chal), do: Utils.tagged_hash("BIP0340/challenge", chal)

  @doc """
    verify whether the schnorr signature is valid for the given message hash and public key
  """
  @spec verify_signature(Point.t(), non_neg_integer, Signature.t()) ::
          boolean | {:error, String.t()}
  def verify_signature(_pubkey, _z, %Signature{r: r, s: s})
      when r >= @p or s >= @n,
      do: {:error, "invalid signature"}

  def verify_signature(pubkey, z, %Signature{r: r, s: s}) do
    r_bytes = Utils.int_to_big(r, 32)
    z_bytes = Utils.int_to_big(z, 32)

    e =
      tagged_hash_challenge(r_bytes <> Point.x_bytes(pubkey) <> z_bytes)
      |> :binary.decode_unsigned()
      |> Math.modulo(@n)

    r_point =
      @generator_point
      |> Math.multiply(s)
      |> Math.add(Math.multiply(pubkey, @n - e))

    !Point.is_inf(r_point) && Point.has_even_y(r_point) && r_point.x == r
  end
end


text:
defmodule Bitcoinex.Secp256k1 do
  @moduledoc """
  General Secp256k1 curve operations.
  libsecp256k1: https://github.com/bitcoin-core/secp256k1

  Currently supports ECDSA public key recovery.

  In the future, we will NIF for critical operations. However, it is more portable to have a native elixir version.
  """
  import Bitwise
  alias Bitcoinex.Secp256k1.{Math, Params, Point, PrivateKey}

  defmodule Signature do
    @moduledoc """
    Contains r,s in signature.
    """
    alias Bitcoinex.Utils

    @type t :: %__MODULE__{
            r: pos_integer(),
            s: pos_integer()
          }

    @enforce_keys [
      :r,
      :s
    ]
    defstruct [:r, :s]

    @spec parse_signature(binary) ::
            {:ok, t()} | {:error, String.t()}
    @doc """
    accepts a compact signature and returns a Signature containing r,s
    """
    def parse_signature(<<r::binary-size(32), s::binary-size(32)>>) do
      # Get r,s from signature.
      r = :binary.decode_unsigned(r)
      s = :binary.decode_unsigned(s)

      # Verify that r,s are integers in [1, n-1] where n is the integer order of G.
      cond do
        r < 1 ->
          {:error, "invalid signature"}

        r > Params.curve().n - 1 ->
          {:error, "invalid signature"}

        s < 1 ->
          {:error, "invalid signature"}

        s > Params.curve().n - 1 ->
          {:error, "invalid signature"}

        true ->
          {:ok, %Signature{r: r, s: s}}
      end
    end

    # attempt to parse 64-byte string
    def parse_signature(compact_sig) when is_binary(compact_sig) do
      case Utils.hex_to_bin(compact_sig) do
        {:error, msg} ->
          {:error, msg}

        sig_bytes ->
          parse_signature(sig_bytes)
      end
    end

    def parse_signature(_), do: {:error, "invalid signature size"}

    @doc """
    der_parse_signature parses a DER binary to a Signature
    """
    # @spec der_parse_signature(binary) :: {:ok, Signature.()} | {:error, String.t()}
    def der_parse_signature(<<0x30>> <> der_sig) when is_binary(der_sig) do
      sig_len = :binary.at(der_sig, 0)

      if sig_len + 1 != byte_size(der_sig) do
        {:error, "invalid signature length"}
      else
        case parse_sig_key(der_sig, 1) do
          {:error, err} ->
            {:error, err}

          {r, s_pos} ->
            case parse_sig_key(der_sig, s_pos) do
              {:error, err} ->
                {:error, err}

              {s, sig_len} ->
                if sig_len != byte_size(der_sig) do
                  {:error, "invalid signature: signature is too long"}
                else
                  {:ok, %Signature{r: r, s: s}}
                end
            end
        end
      end
    end

    def der_parse_signature(_), do: {:error, "invalid signature"}

    defp parse_sig_key(data, pos) do
      if :binary.at(data, pos) != 0x02 do
        {:error, "invalid signature key marker"}
      else
        k_len = :binary.at(data, pos + 1)
        len_k = :binary.part(data, pos + 2, k_len)
        {:binary.decode_unsigned(len_k), pos + 2 + k_len}
      end
    end

    @spec serialize_signature(t()) :: binary
    def serialize_signature(%__MODULE__{r: r, s: s}) do
      :binary.encode_unsigned(r) <> :binary.encode_unsigned(s)
    end

    @doc """
    der_serialize_signature returns the DER serialization of an ecdsa signature
    """
    @spec der_serialize_signature(Signature.t()) :: binary
    def der_serialize_signature(%Signature{r: r, s: s}) do
      r_bytes = serialize_sig_key(r)
      s_bytes = serialize_sig_key(s)
      <<0x30>> <> len_as_bytes(r_bytes <> s_bytes) <> r_bytes <> s_bytes
    end

    def der_serialize_signature(_), do: {:error, "Signature object required"}

    defp serialize_sig_key(k) do
      k
      |> :binary.encode_unsigned()
      |> lstrip(<<0x00>>)
      |> add_high_bit()
      |> prefix_key()
    end

    defp len_as_bytes(data), do: :binary.encode_unsigned(byte_size(data))

    defp lstrip(<<head::binary-size(1)>> <> tail, val) do
      if head == val, do: lstrip(tail, val), else: head <> tail
    end

    defp add_high_bit(k_bytes) do
      unless (:binary.at(k_bytes, 0) &&& 0x80) == 0 do
        <<0x00>> <> k_bytes
      else
        k_bytes
      end
    end

    defp prefix_key(k_bytes), do: <<0x02>> <> len_as_bytes(k_bytes) <> k_bytes
  end

  @doc """
  Returns the y-coordinate of a secp256k1 curve point (P) using the x-coordinate.
  To get P(y), we solve for y in this equation: y^2 = x^3 + 7.
  """
  @spec get_y(integer, boolean) :: {:ok, integer} | {:error, String.t()}
  def get_y(x, is_y_odd) do
    # x^3 + 7
    y_sq =
      :crypto.mod_pow(x, 3, Params.curve().p)
      |> :binary.decode_unsigned()
      |> Kernel.+(7 |> Math.modulo(Params.curve().p))

    # Solve for y.
    y =
      :crypto.mod_pow(y_sq, Integer.floor_div(Params.curve().p + 1, 4), Params.curve().p)
      |> :binary.decode_unsigned()

    y =
      case rem(y, 2) == 1 do
        ^is_y_odd ->
          y

        _ ->
          Params.curve().p - y
      end

    # Check.
    if y_sq != :crypto.mod_pow(y, 2, Params.curve().p) |> :binary.decode_unsigned() do
      {:error, "invalid sq root"}
    else
      {:ok, y}
    end
  end

  @doc """
    force_even_y returns the negated private key
    if the associated Point has an odd y. Otherwise
    it returns the private key
  """
  @spec force_even_y(PrivateKey.t()) :: PrivateKey.t() | {:error, String.t()}
  def force_even_y(%PrivateKey{} = privkey) do
    case PrivateKey.to_point(privkey) do
      {:error, msg} ->
        {:error, msg}

      pubkey ->
        if Point.is_inf(pubkey) do
          {:error, "pubkey is infinity. bad luck"}
        else
          if Point.has_even_y(pubkey) do
            privkey
          else
            %PrivateKey{d: Params.curve().n - privkey.d}
          end
        end
    end
  end

  @doc """
    verify_point verifies that a given point is on the secp256k1
    curve
  """
  @spec verify_point(Point.t()) :: bool
  def verify_point(%Point{x: x, y: y}) do
    y_odd = rem(y, 2) == 1
    {:ok, new_y} = get_y(x, y_odd)
    y == new_y
  end
end


text:
defmodule Bitcoinex.Secp256k1.Math do
  @moduledoc """
  Contains math utilities when dealing with secp256k1 curve points and scalars.

  All of the addition and multiplication uses the secp256k1 curve paramaters.

  Several of the jacobian multiplication and addition functions are borrowed heavily from https://github.com/starkbank/ecdsa-elixir/.
  """
  alias Bitcoinex.Secp256k1.{Params, Point}
  import Bitcoinex.Secp256k1.Point

  @doc """
  pow performs integer pow,
  where x is raised to the power of y.
  """
  # Integer.pow/2 was added since 1.12.0. This function_exported? can be removed when we decide
  # to only support >= 1.12.0 in the future
  if function_exported?(Integer, :pow, 2) do
    defdelegate pow(base, exponent), to: Integer
  else
    # copy from https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/integer.ex#L104
    @spec pow(integer, non_neg_integer) :: integer
    def pow(base, exponent) when is_integer(base) and is_integer(exponent) and exponent >= 0 do
      guarded_pow(base, exponent)
    end

    # https://en.wikipedia.org/wiki/Exponentiation_by_squaring
    defp guarded_pow(_, 0), do: 1
    defp guarded_pow(b, 1), do: b
    defp guarded_pow(b, e) when (e &&& 1) == 0, do: guarded_pow(b * b, e >>> 1)
    defp guarded_pow(b, e), do: b * guarded_pow(b * b, e >>> 1)
  end

  @doc """
  Inv performs the Extended Euclidean Algorithm to find
  the inverse of a number x mod n.
  """
  @spec inv(integer, pos_integer) :: integer
  def inv(x, n) when is_integer(x) and is_integer(n) and n >= 1 do
    do_inv(x, n)
  end

  defp do_inv(x, _n) when x == 0, do: 0
  defp do_inv(x, n), do: do_inv(1, 0, modulo(x, n), n) |> modulo(n)

  defp do_inv(lm, hm, low, high) when low > 1 do
    r = div(high, low)

    do_inv(
      hm - lm * r,
      lm,
      high - low * r,
      low
    )
  end

  defp do_inv(lm, _hm, _low, _high) do
    lm
  end

  @spec modulo(integer, integer) :: integer
  def modulo(x, n) when is_integer(x) and is_integer(n) do
    r = rem(x, n)
    if r < 0, do: r + n, else: r
  end

  @doc """
  multiply accepts a point P and scalar n and,
  does jacobian multiplication to return resulting point.
  """
  def multiply(p, n) when is_point(p) and is_integer(n) do
    p
    |> toJacobian()
    |> jacobianMultiply(n)
    |> fromJacobian()
  end

  @doc """
  add accepts points p and q and,
  does jacobian addition to return resulting point.
  """
  def add(p, q) when is_point(p) and is_point(q) do
    jacobianAdd(toJacobian(p), toJacobian(q))
    |> fromJacobian()
  end

  # Convert our point P to jacobian coordinates.
  defp toJacobian(p) do
    %Point{x: p.x, y: p.y, z: 1}
  end

  # Convert our jacobian coordinates to a point P on secp256k1 curve.
  defp fromJacobian(p) do
    z = inv(p.z, Params.curve().p)

    %Point{
      x:
        modulo(
          p.x * pow(z, 2),
          Params.curve().p
        ),
      y:
        modulo(
          p.y * pow(z, 3),
          Params.curve().p
        )
    }
  end

  # double Point P to get point P + P
  # We use the dbl-1998-cmo-2 doubling formula.
  # For reference, http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html.
  defp jacobianDouble(p) do
    if p.y == 0 do
      %Point{x: 0, y: 0, z: 0}
    else
      # XX = X1^2
      xsq =
        pow(p.x, 2)
        |> modulo(Params.curve().p)

      # YY = Y1^2
      ysq =
        pow(p.y, 2)
        |> modulo(Params.curve().p)

      # S = 4 * X1 * YY
      s =
        (4 * p.x * ysq)
        |> modulo(Params.curve().p)

      # M = 3 * XX + a * Z1^4
      m =
        (3 * xsq + Params.curve().a * pow(p.z, 4))
        |> modulo(Params.curve().p)

      # T = M^2 - 2 * S
      t =
        (pow(m, 2) - 2 * s)
        |> modulo(Params.curve().p)

      # X3 = T
      nx = t

      # Y3 = M * (S - T) - 8 * YY^2
      ny =
        (m * (s - t) - 8 * pow(ysq, 2))
        |> modulo(Params.curve().p)

      # Z3 = 2 * Y1 * Z1
      nz =
        (2 * p.y * p.z)
        |> modulo(Params.curve().p)

      %Point{x: nx, y: ny, z: nz}
    end
  end

  # add points P and Q to get P + Q
  # We use the add-1998-cmo-2 addition formula.
  # For reference, http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html.
  defp jacobianAdd(p, q) do
    if p.y == 0 do
      q
    else
      if q.y == 0 do
        p
      else
        # U1 = X1 * Z2^2
        u1 =
          (p.x * pow(q.z, 2))
          |> modulo(Params.curve().p)

        # U2 = X2 * Z2^2
        u2 =
          (q.x * pow(p.z, 2))
          |> modulo(Params.curve().p)

        # S1 = Y1 * Z2^3
        s1 =
          (p.y * pow(q.z, 3))
          |> modulo(Params.curve().p)

        # S2 = y2 * Z1^3
        s2 =
          (q.y * pow(p.z, 3))
          |> modulo(Params.curve().p)

        if u1 == u2 do
          if s1 != s2 do
            %Point{x: 0, y: 0, z: 1}
          else
            jacobianDouble(p)
          end
        else
          # H = U2 - U1
          h = u2 - u1

          # r = S2 - S1
          r = s2 - s1

          # HH = H^2
          h2 =
            (h * h)
            |> modulo(Params.curve().p)

          # HHH = H * HH
          h3 =
            (h * h2)
            |> modulo(Params.curve().p)

          # V = U1 * HH
          v =
            (u1 * h2)
            |> modulo(Params.curve().p)

          # X3 = 42 - HHH - 2 * V
          nx =
            (pow(r, 2) - h3 - 2 * v)
            |> modulo(Params.curve().p)

          # Y3 = r * (V - X3) - S1 * HHH
          ny =
            (r * (v - nx) - s1 * h3)
            |> modulo(Params.curve().p)

          # Z3 = Z1 * Z2 * H
          nz =
            (h * p.z * q.z)
            |> modulo(Params.curve().p)

          %Point{x: nx, y: ny, z: nz}
        end
      end
    end
  end

  # multply point P with scalar n
  defp jacobianMultiply(_p, n) when n == 0 do
    %Point{x: 0, y: 0, z: 1}
  end

  defp jacobianMultiply(p, n) when n == 1 do
    if p.y == 0 do
      %Point{x: 0, y: 0, z: 1}
    else
      p
    end
  end

  defp jacobianMultiply(p, n)
       # This integer is n, the integer order of G for secp256k1.
       # Unfortunately cannot call Params.curve.n to get the curve order integer,
       # so instead, it is pasted it here.
       # In the future we should move it back to Params.
       when n < 0 or
              n >
                115_792_089_237_316_195_423_570_985_008_687_907_852_837_564_279_074_904_382_605_163_141_518_161_494_337 do
    if p.y == 0 do
      %Point{x: 0, y: 0, z: 1}
    else
      jacobianMultiply(p, modulo(n, Params.curve().n))
    end
  end

  defp jacobianMultiply(p, n) when rem(n, 2) == 0 do
    if p.y == 0 do
      %Point{x: 0, y: 0, z: 1}
    else
      jacobianMultiply(p, div(n, 2))
      |> jacobianDouble()
    end
  end

  defp jacobianMultiply(p, n) do
    if p.y == 0 do
      %Point{x: 0, y: 0, z: 1}
    else
      jacobianMultiply(p, div(n, 2))
      |> jacobianDouble()
      |> jacobianAdd(p)
    end
  end
end


text:
defmodule Bitcoinex.Secp256k1.Point do
  @moduledoc """
  Contains the x, y, and z of an elliptic curve point.
  """

  import Bitwise
  alias Bitcoinex.Utils
  alias Bitcoinex.Secp256k1
  alias Bitcoinex.Secp256k1.Params

  @p Params.curve().p

  @type t :: %__MODULE__{
          x: integer(),
          y: integer(),
          z: integer()
        }

  @enforce_keys [
    :x,
    :y
  ]
  defstruct [:x, :y, z: 0]

  defguard is_point(term)
           when is_map(term) and :erlang.map_get(:__struct__, term) == __MODULE__ and
                  :erlang.is_map_key(:x, term) and :erlang.is_map_key(:y, term) and
                  :erlang.is_map_key(:z, term)

  @doc """
    is_inf returns whether or not point P is
    the point at infinity, ie. P.x == P.y == 0
  """
  @spec is_inf(t()) :: boolean
  def is_inf(%__MODULE__{x: 0, y: 0}), do: true
  def is_inf(_), do: false

  @doc """
  parse_public_key parses a public key
  """
  @spec parse_public_key(binary) :: {:ok, t()} | {:error, String.t()}
  def parse_public_key(<<0x04, x::binary-size(32), y::binary-size(32)>>) do
    {:ok, %__MODULE__{x: :binary.decode_unsigned(x), y: :binary.decode_unsigned(y)}}
  end

  # Above matches with uncompressed keys. Below matches with compressed keys
  def parse_public_key(<<prefix::binary-size(1), x_bytes::binary-size(32)>>) do
    x = :binary.decode_unsigned(x_bytes)

    case :binary.decode_unsigned(prefix) do
      2 ->
        case Bitcoinex.Secp256k1.get_y(x, false) do
          {:ok, y} -> {:ok, %__MODULE__{x: x, y: y}}
          _ -> {:error, "invalid public key"}
        end

      3 ->
        case Bitcoinex.Secp256k1.get_y(x, true) do
          {:ok, y} -> {:ok, %__MODULE__{x: x, y: y}}
          _ -> {:error, "invalid public key"}
        end
    end
  end

  # Allow parse_public_key to parse SEC strings
  def parse_public_key(key) do
    key
    |> String.downcase()
    |> Base.decode16!(case: :lower)
    |> parse_public_key()
  end

  @doc """
    lift_x returns the Point P where P.x = x
    and P.y is even.
  """
  @spec lift_x(integer | binary) :: {:ok, t()} | {:error, String.t()}
  def lift_x(x) when is_integer(x) and x >= @p, do: {:error, "invalid x value (too large)"}

  def lift_x(x) when is_integer(x) do
    case Secp256k1.get_y(x, false) do
      {:ok, y} ->
        {:ok, %__MODULE__{x: x, y: y}}

      err ->
        err
    end
  end

  # parse 32-byte binary
  def lift_x(<<x::binary-size(32)>>) do
    x
    |> :binary.decode_unsigned()
    |> lift_x
  end

  # attempt to parse x-only pubkey from hex
  def lift_x(x) when is_binary(x) do
    case Utils.hex_to_bin(x) do
      {:error, msg} ->
        {:error, msg}

      x_bytes ->
        lift_x(x_bytes)
    end
  end

  @doc """
  sec serializes a compressed public key to binary
  """
  @spec sec(t()) :: binary
  def sec(%__MODULE__{x: x, y: y}) do
    case rem(y, 2) do
      0 ->
        <<0x02>> <> Bitcoinex.Utils.pad(:binary.encode_unsigned(x), 32, :leading)

      1 ->
        <<0x03>> <> Bitcoinex.Utils.pad(:binary.encode_unsigned(x), 32, :leading)
    end
  end

  @doc """
    x_bytes returns the binary encoding of the x value of the point
  """
  @spec x_bytes(t()) :: binary
  def x_bytes(%__MODULE__{x: x}) do
    Bitcoinex.Utils.pad(:binary.encode_unsigned(x), 32, :leading)
  end

  @doc """
    x_hex returns the hex-encoded x value of the point
  """
  @spec x_hex(t()) :: String.t()
  def x_hex(p) do
    p
    |> x_bytes()
    |> Base.encode16(case: :lower)
  end

  @doc """
  serialize_public_key serializes a compressed public key to string
  """
  @spec serialize_public_key(t()) :: String.t()
  def serialize_public_key(pubkey) do
    pubkey
    |> sec()
    |> Base.encode16(case: :lower)
  end

  @doc """
    has_even_y returns true if y is
    even and false if y is odd
  """
  @spec has_even_y(t()) :: boolean
  def has_even_y(%__MODULE__{y: y}) do
    (y &&& 1) == 0
  end
end


text:
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: <msg>"
  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


text:
defmodule Bitcoinex.LightningNetwork.Invoice do
  @moduledoc """
  Includes BOLT#11 Invoice serialization.

  Reference: https://github.com/lightningnetwork/lightning-rfc/blob/master/11-payment-encoding.md
  """

  alias Bitcoinex.{Bech32, Network, Segwit}
  alias Bitcoinex.LightningNetwork.HopHint
  alias Decimal, as: D

  import Bitwise
  # consider using https://github.com/ejpcmac/typed_struct

  @default_min_final_cltv_expiry 18
  @default_expiry 3600

  @enforce_keys [:network, :destination, :payment_hash, :timestamp]
  defstruct [
    :network,
    :destination,
    :payment_hash,
    :amount_msat,
    :timestamp,
    :description,
    :description_hash,
    :fallback_address,
    route_hints: [],
    expiry: @default_expiry,
    min_final_cltv_expiry: @default_min_final_cltv_expiry
  ]

  @type t() :: %__MODULE__{
          network: Network.network_name(),
          destination: String.t(),
          payment_hash: String.t(),
          amount_msat: non_neg_integer | nil,
          timestamp: integer(),
          expiry: integer() | nil,
          # description and description_hash are either both non-nil or nil
          description: String.t() | nil,
          description_hash: String.t() | nil,
          fallback_address: String.t() | nil,
          min_final_cltv_expiry: non_neg_integer,
          route_hints: list(HopHint.t())
        }

  @prefix "ln"
  @valid_multipliers ~w(m u n p)
  # TODO move it to bitcoin asset?
  @milli_satoshi_per_bitcoin 100_000_000_000
  # the 512 bit signature + 8 bit recovery ID.
  @signature_base32_length 104
  @timestamp_base32_length 7
  @sha256_hash_base32_length 52
  @pubkey_base32_length 53
  @hop_hint_length 51
  @type error :: atom

  @doc """
   Decode accepts a Bech32 encoded string invoice and deserializes it.
  """
  @spec decode(String.t()) :: {:ok, t} | {:error, error}
  def decode(invoice) when is_binary(invoice) do
    with {:ok, {_encoding_type, hrp, data}} <- Bech32.decode(invoice, :infinity),
         {:ok, {network, amount_msat}} <- parse_hrp(hrp),
         {invoice_data, signature_data} = split_at(data, -@signature_base32_length),
         {:ok, parsed_data} <-
           parse_invoice_data(invoice_data, network),
         {:ok, destination} <-
           validate_and_parse_signature_data(
             Map.get(parsed_data, :destination),
             hrp,
             invoice_data,
             signature_data
           ) do
      __MODULE__
      |> struct(
        Map.merge(
          parsed_data,
          %{
            network: network,
            amount_msat: amount_msat,
            destination: destination
          }
        )
      )
      |> validate_invoice()
    end
  end

  def decode(invoice) when is_binary(invoice) do
    {:error, :no_ln_prefix}
  end

  @doc """
   Returns the expiry of the invoice.
  """
  @spec expires_at(Bitcoinex.LightningNetwork.Invoice.t()) :: DateTime.t()
  def expires_at(%__MODULE__{} = invoice) do
    expiry = invoice.expiry
    Timex.from_unix(invoice.timestamp + expiry, :second)
  end

  # checking some invariant for invoice
  # TODO Could we use ecto(without SQL) for this?
  defp validate_invoice(%__MODULE__{} = invoice) do
    cond do
      is_nil(invoice.network) ->
        {:error, :network_missing}

      !is_nil(invoice.amount_msat) && invoice.amount_msat < 0 ->
        {:error, :negative_amount_msat}

      is_nil(invoice.payment_hash) ->
        {:error, :payment_hash_missing}

      is_nil(invoice.description) && is_nil(invoice.description_hash) ->
        {:error, :both_description_and_description_hash_present}

      !is_nil(invoice.description) && !is_nil(invoice.description_hash) ->
        {:error, :both_description_and_description_hash_missing}

      # lnd have this but not in Bolt11. do we need to enforce this?
      Enum.count(invoice.route_hints) > 20 ->
        {:error, :too_many_private_routes}

      String.length(invoice.payment_hash) != 64 ->
        {:error, :invalid_payment_hash_length}

      !is_nil(invoice.description_hash) && String.length(invoice.description_hash) != 64 ->
        {:error, :invalid_payment_hash}

      # String.length(invoice.destination) != 64 ->
      #   {:error, :invalid_destination_length}

      true ->
        {:ok, invoice}
    end
  end

  defp validate_and_parse_signature_data(destination, hrp, invoice_data, signature_data)
       when is_list(invoice_data) and is_list(signature_data) do
    with {:ok, signature_data_in_byte} <- Bech32.convert_bits(signature_data, 5, 8),
         {signature, [recoveryId]} = split_at(signature_data_in_byte, -1),
         {:ok, invoice_data_in_byte} <- Bech32.convert_bits(invoice_data, 5, 8) do
      to_sign = (hrp |> :erlang.binary_to_list()) ++ invoice_data_in_byte
      signature = signature |> byte_list_to_binary
      hash = to_sign |> Bitcoinex.Utils.sha256()

      # TODO if destination exist from tagged field, we dun need to recover but to verify it with signature
      # but that require convert lg sig before using secp256k1 to verify it
      # TODO refactor too nested
      case Bitcoinex.Secp256k1.Ecdsa.ecdsa_recover_compact(hash, signature, recoveryId) do
        {:ok, pubkey} ->
          if is_nil(destination) or destination == pubkey do
            {:ok, pubkey}
          else
            {:error, :invalid_invoice_signature}
          end

        {:error, error} ->
          {:error, error}
      end
    end
  end

  defp parse_invoice_data(data, network) when is_list(data) do
    {timstamp_data, tagged_fields_data} = split_at(data, @timestamp_base32_length)

    with {:ok, timestamp} <- parse_timestamp(timstamp_data),
         {:ok, parsed_data} <-
           parse_tagged_fields(tagged_fields_data, network) do
      {:ok, Map.put(parsed_data, :timestamp, timestamp)}
    end
  end

  defp parse_tagged_fields(data, network) when is_list(data) do
    do_parse_tagged_fields(data, %{}, network)
  end

  defp do_parse_tagged_fields([type, data_length1, data_length2 | rest], acc, network) do
    data_length = data_length1 <<< 5 ||| data_length2

    if Enum.count(rest) < data_length do
      {:error, :invalid_field_length}
    else
      {data, new_rest} = split_at(rest, data_length)

      case(parse_tagged_field(type, data, acc, network)) do
        {:ok, acc} ->
          do_parse_tagged_fields(new_rest, acc, network)

        {:error, error} ->
          {:error, error}
      end
    end
  end

  defp do_parse_tagged_fields(_, acc, _network) do
    {:ok, acc}
  end

  defp parse_tagged_field(type, data, acc, network) do
    case type do
      1 ->
        if Map.has_key?(acc, :payment_hash) do
          {:ok, acc}
        else
          case parse_payment_hash(data) do
            {:ok, payment_hash} ->
              {:ok, Map.put(acc, :payment_hash, payment_hash)}

            {:error, error} ->
              {:error, error}
          end
        end

      # r field HopHints
      3 ->
        if Map.has_key?(acc, :route_hints) do
          {:ok, acc}
        else
          case parse_hop_hints(data) do
            {:ok, hop_hints} ->
              {:ok, Map.put(acc, :route_hints, hop_hints)}

            {:error, error} ->
              {:error, error}
          end
        end

      # x field
      6 ->
        if Map.has_key?(acc, :expiry) do
          {:ok, acc}
        else
          expiry = parse_expiry(data)
          {:ok, Map.put(acc, :expiry, expiry)}
        end

      # f field fallback address
      9 ->
        if Map.has_key?(acc, :fallback_address) do
          {:ok, acc}
        else
          case parse_fallback_address(data, network) do
            {:ok, fallback_address} ->
              {:ok, Map.put(acc, :fallback_address, fallback_address)}

            {:error, error} ->
              {:error, error}
          end
        end

      # d field
      13 ->
        if Map.has_key?(acc, :description) do
          {:ok, acc}
        else
          case parse_description(data) do
            {:ok, description} ->
              {:ok, Map.put(acc, :description, description)}

            {:error, error} ->
              {:error, error}
          end
        end

      # n field destination
      19 ->
        case acc do
          %{destination: destination} when destination != nil ->
            {:ok, acc}

          _ ->
            case parse_destination(data) do
              {:ok, destination} ->
                {:ok, Map.put(acc, :destination, destination)}

              {:error, error} ->
                {:error, error}
            end
        end

      # h field description hash
      23 ->
        if Map.has_key?(acc, :description_hash) do
          {:ok, acc}
        else
          case parse_description_hash(data) do
            {:ok, description_hash} ->
              {:ok, Map.put(acc, :description_hash, description_hash)}

            {:error, error} ->
              {:error, error}
          end
        end

      # c field MINIMUM Fianl CLTV Expiry
      24 ->
        if Map.has_key?(acc, :min_final_cltv_expiry) do
          {:ok, acc}
        else
          min_final_cltv_expiry = parse_min_final_cltv_expiry(data)
          {:ok, Map.put(acc, :min_final_cltv_expiry, min_final_cltv_expiry)}
        end

      _ ->
        {:ok, acc}
    end
  end

  defp parse_timestamp(data) do
    {:ok, base32_to_integer(data)}
  end

  defp parse_payment_hash(data) when is_list(data) do
    if Enum.count(data) == @sha256_hash_base32_length do
      case Bech32.convert_bits(data, 5, 8, false) do
        {:ok, converted_data} ->
          {:ok, converted_data |> :binary.list_to_bin() |> Base.encode16(case: :lower)}

        {:error, error} ->
          {:error, error}
      end
    else
      {:error, :invalid_payment_hash_length}
    end
  end

  defp parse_description(data) do
    case Bech32.convert_bits(data, 5, 8, false) do
      {:ok, description} ->
        {:ok, :binary.list_to_bin(description)}

      {:error, error} ->
        {:error, error}
    end
  end

  defp parse_expiry(data) do
    base32_to_integer(data)
  end

  @spec base32_to_integer(maybe_improper_list()) :: any()
  def base32_to_integer(data) when is_list(data) do
    Enum.reduce(data, 0, fn val, acc ->
      acc <<< 5 ||| val
    end)
  end

  defp parse_destination(data) when is_list(data) do
    if Enum.count(data) == @pubkey_base32_length do
      case Bech32.convert_bits(data, 5, 8, false) do
        {:ok, data_in_bytes} ->
          {:ok, bytes_to_hex_string(data_in_bytes)}

        {:error, error} ->
          {:error, error}
      end
    else
      {:ok, nil}
    end
  end

  defp parse_description_hash(data) when is_list(data) do
    if Enum.count(data) == @sha256_hash_base32_length do
      case Bech32.convert_bits(data, 5, 8, false) do
        {:ok, data_in_bytes} ->
          {:ok, data_in_bytes |> bytes_to_hex_string}

        {:error, error} ->
          {:error, error}
      end
    else
      {:ok, nil}
    end
  end

  defp parse_fallback_address([], _network) do
    {:error, :empty_fallback_address}
  end

  defp parse_fallback_address([version | rest], network) do
    case version do
      0 ->
        case Bech32.convert_bits(rest, 5, 8, false) do
          {:ok, witness} ->
            case Enum.count(witness) do
              witness_program_lenghh when witness_program_lenghh in [20, 32] ->
                Segwit.encode_address(network, 0, witness)

              _ ->
                {:error, :invalid_witness_program_length}
            end

          err ->
            err
        end

      17 ->
        case Bech32.convert_bits(rest, 5, 8, false) do
          {:ok, pubKeyHash} ->
            {:ok,
             Bitcoinex.Address.encode(
               pubKeyHash |> :binary.list_to_bin(),
               network,
               :p2pkh
             )}

          err ->
            err
        end

      18 ->
        case Bech32.convert_bits(rest, 5, 8, false) do
          {:ok, scriptHash} ->
            {:ok,
             Bitcoinex.Address.encode(
               scriptHash |> :binary.list_to_bin(),
               network,
               :p2sh
             )}

          err ->
            err
        end

      # ignore unknown version
      _ ->
        {:ok, nil}
    end
  end

  defp parse_hop_hints(data) when is_list(data) do
    with {:ok, data_in_byte} <- Bech32.convert_bits(data, 5, 8, false),
         {_, true} <-
           {:validate_hop_hint_data_length, rem(Enum.count(data_in_byte), @hop_hint_length) == 0} do
      hop_hints =
        data_in_byte
        |> Enum.chunk_every(@hop_hint_length)
        |> Enum.map(&parse_hop_hint/1)

      {:ok, hop_hints}
    else
      {:validate_hop_hint_data_length, false} ->
        {:error, :invalid_hop_hint_data_length}

      {:error, error} ->
        {:error, error}
    end
  end

  defp parse_integer_from_hex_str!(hex_str) do
    {hex_str, ""} = Integer.parse(hex_str, 16)
    hex_str
  end

  # exoect they are list of integer in byte
  defp parse_hop_hint(data) when is_list(data) do
    # 64 bits
    {node_id_data, rest} = data |> split_at(33)
    node_id = node_id_data |> bytes_to_hex_string
    # 64 bits
    {channel_id_data, rest} = rest |> split_at(8)
    channel_id = channel_id_data |> bytes_to_hex_string |> parse_integer_from_hex_str!
    # 32 bits
    {fee_base_m_sat_data, rest} = rest |> split_at(4)
    fee_base_m_sat = fee_base_m_sat_data |> bytes_to_hex_string |> parse_integer_from_hex_str!
    # 32 bits
    {fee_proportional_millionths_data, rest} = rest |> split_at(4)

    fee_proportional_millionths =
      fee_proportional_millionths_data |> bytes_to_hex_string |> parse_integer_from_hex_str!

    cltv_expiry_delta = rest |> bytes_to_hex_string |> parse_integer_from_hex_str!

    %HopHint{
      node_id: node_id,
      channel_id: channel_id,
      fee_base_m_sat: fee_base_m_sat,
      fee_proportional_millionths: fee_proportional_millionths,
      cltv_expiry_delta: cltv_expiry_delta
    }
  end

  defp parse_min_final_cltv_expiry(data) when is_list(data) do
    base32_to_integer(data)
  end

  # defp get_pubkey_to_address_magicbyte(network, script_type) do
  #   case {network, script_type} do
  #     {:mainnet, :p2pkh} ->
  #       0x00

  #     {:mainnet, :p2sh} ->
  #       0x05

  #     {network, :p2pkh} when network in [:testnet, :regtest] ->
  #       0x6F

  #     {network, :p2sh} when network in [:testnet, :regtest] ->
  #       0xC4
  #   end
  # end

  defp parse_network(@prefix <> rest_hrp) do
    case Enum.find(Network.supported_networks(), fn %{hrp_segwit_prefix: hrp_segwit_prefix} ->
           if String.starts_with?(rest_hrp, hrp_segwit_prefix) do
             size = bit_size(hrp_segwit_prefix)

             case rest_hrp do
               # without amount
               ^hrp_segwit_prefix ->
                 true

               # with amount. a valid segwit_prefix must be following with base10 digit
               # ?0..?9 means range of codepoint of 0 - 9
               # it shoudln't include 0 but that's not responsiblity of passing network function here
               <<_::size(size), i, _::binary>> when i in ?0..?9 ->
                 true

               _ ->
                 false
             end
           end
         end) do
      nil ->
        {:error, :invalid_network}

      network ->
        {:ok, network}
    end
  end

  defp parse_hrp(hrp) do
    with {_, @prefix <> rest_hrp} <- {:strip_prefix, hrp},
         {_, {:ok, %{name: network_name, hrp_segwit_prefix: hrp_segwit_prefix}}} <-
           {:parse_network, parse_network(hrp)} do
      hrp_segwit_prefix_size = byte_size(hrp_segwit_prefix)

      case rest_hrp do
        ^hrp_segwit_prefix ->
          {:ok, {network_name, nil}}

        _ ->
          amount_str = String.slice(rest_hrp, hrp_segwit_prefix_size..-1)

          case calculate_milli_satoshi(amount_str) do
            {:ok, amount} ->
              {:ok, {network_name, amount}}

            {:error, error} ->
              {:error, error}
          end
      end
    else
      {:strip_prefix, _} ->
        {:error, :no_ln_prefix}

      {:parse_network, error} ->
        {:error, error}
    end
  end

  defp calculate_milli_satoshi(amount_str) do
    if String.length(amount_str) > 1 and String.starts_with?(amount_str, "0") do
      {:error, :amount_with_leading_zero}
    else
      result =
        case Regex.run(~r/[munp]$/, amount_str) do
          [multiplier] when multiplier in @valid_multipliers ->
            case Integer.parse(String.slice(amount_str, 0..-2)) do
              {amount, ""} ->
                {:ok, to_bitcoin(amount, multiplier)}

              _ ->
                {:error, :invalid_amount}
            end

          _ ->
            case Integer.parse(amount_str) do
              {amount_in_bitcoin, ""} ->
                {:ok, amount_in_bitcoin}

              _ ->
                {:error, :invalid_amount}
            end
        end

      case result do
        {:ok, amount_in_bitcoin} ->
          amount_msat_dec = D.mult(amount_in_bitcoin, @milli_satoshi_per_bitcoin)
          rounded_amount_msat_dec = D.round(amount_msat_dec)

          case D.equal?(rounded_amount_msat_dec, amount_msat_dec) do
            true ->
              {:ok, D.to_integer(rounded_amount_msat_dec)}

            false ->
              {:error, :sub_msat_precision_amount}
          end

        {:error, error} ->
          {:error, error}
      end
    end
  end

  defp to_bitcoin(amount, multiplier_str) when is_integer(amount) do
    multiplier =
      case multiplier_str do
        "m" ->
          0.001

        "u" ->
          0.000001

        "n" ->
          0.000000001

        "p" ->
          0.000000000001
      end

    D.mult(amount, D.from_float(multiplier))
  end

  defp bytes_to_hex_string(bytes) when is_list(bytes) do
    bytes |> :binary.list_to_bin() |> Base.encode16(case: :lower)
  end

  defp byte_list_to_binary(bytes) when is_list(bytes) do
    bytes |> :binary.list_to_bin()
  end

  @spec split_at(Enum.t(), integer()) :: {list(Enum.t()), list(Enum.t())}
  defp split_at(xs, index) when index >= 0 do
    {Enum.take(xs, index), Enum.drop(xs, index)}
  end

  defp split_at(xs, index) when index < 0 do
    {Enum.drop(xs, index), Enum.take(xs, index)}
  end
end


text:
defmodule Bitcoinex.LightningNetwork.HopHint do
  @moduledoc """
   A hop hint is used to help the payer route a payment to the receiver.

   A hint is included in BOLT#11 Invoices.
  """

  @enforce_keys [
    :node_id,
    :channel_id,
    :fee_base_m_sat,
    :fee_proportional_millionths,
    :cltv_expiry_delta
  ]
  defstruct [
    :node_id,
    :channel_id,
    :fee_base_m_sat,
    :fee_proportional_millionths,
    :cltv_expiry_delta
  ]

  @type t() :: %__MODULE__{
          node_id: String.t(),
          channel_id: non_neg_integer,
          fee_base_m_sat: non_neg_integer,
          fee_proportional_millionths: non_neg_integer,
          cltv_expiry_delta: non_neg_integer
        }
end


text:
defmodule Bitcoinex.LightningNetwork do
  @moduledoc """
    Includes serialization and validation for Lightning Network BOLT#11 invoices.
  """

  alias Bitcoinex.LightningNetwork.Invoice

  # defdelegate encode_invoice(invoice), to: Invoice, as: :encode
  defdelegate decode_invoice(invoice), to: Invoice, as: :decode
end