lib/segwit_addr.ex

# Copyright (c) 2021 Łukasz Samosn
# Copyright (c) 2017 Adán Sánchez de Pedro Crespo
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

defmodule SegwitAddr do
  use Bitwise

  @moduledoc ~S"""
  Encode and decode BIP-0173 and BIP-0350 compliant SegWit addresses.
  """

  @typedoc """
  Segregated witness program version
  """
  @type segwit_version_t :: 0..16

  @doc ~S"""
  Encode a SegWit address.

  ## Examples

      iex> SegwitAddr.encode("bc", "0014751e76e8199196d454941c45d1b3a323f1433bd6")
      "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"

      iex> SegwitAddr.encode("bc", 0, [117, 30, 118, 232, 25, 145, 150, 212,
      ...> 84, 148, 28, 69, 209, 179, 163, 35, 241, 67, 59, 214])
      "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
  """
  @spec encode(String.t(), segwit_version_t, list(byte())) :: String.t()
  def encode(hrp, version, program) when is_list(program) do
    unless version in 0..16 do
      raise ArgumentError, "invalid witness version"
    end

    unless program_length_valid?(version, length(program)) do
      raise ArgumentError, "invalid witness program length"
    end

    encoding = get_encoding(version)
    Bech32.encode(hrp, [version] ++ convert_bits(program, 8, 5), encoding)
  end

  @spec encode(String.t(), String.t()) :: String.t()
  def encode(hrp, program) when is_binary(program) do
    <<version, _size, program::binary>> = Base.decode16!(program, case: :mixed)

    program
    |> :binary.bin_to_list()
    |> (&encode(hrp, version, &1)).()
  end

  @doc ~S"""
  Decode a SegWit address.

  ## Examples

      iex> SegwitAddr.decode("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
      {:ok, {"bc", 0, [117, 30, 118, 232, 25, 145, 150, 212,
      84, 148, 28, 69, 209, 179, 163, 35, 241, 67, 59, 214]}}
  """
  @spec decode(String.t()) ::
          {:ok, {String.t(), segwit_version_t, list(byte())}} | {:error, String.t()}
  def decode(addr) do
    case Bech32.decode(addr) do
      {:ok, {hrp, data, encoding}} ->
        case data do
          [version | encoded] when version in 0..16 ->
            program = convert_bits(encoded, 5, 8, false)

            cond do
              get_encoding(version) != encoding ->
                {:error, "Invalid encoding for witness version"}

              is_nil(program) or !program_length_valid?(version, length(program)) ->
                {:error, "Invalid program length for witness version"}

              true ->
                {:ok, {hrp, version, program}}
            end

          [_ | _] ->
            {:error, "Invalid witness version"}

          [] ->
            {:error, "Empty data section"}
        end

      error ->
        error
    end
  end

  @doc ~S"""
  Encode a witness program into a hexadecimal ScriptPubKey.

  ## Examples

      iex> SegwitAddr.to_script_pub_key(0, [117, 30, 118, 232, 25, 145, 150,
      ...> 212, 84, 148, 28, 69, 209, 179, 163, 35, 241, 67, 59, 214])
      "0014751e76e8199196d454941c45d1b3a323f1433bd6"
  """
  @spec to_script_pub_key(segwit_version_t, list(byte())) :: String.t()
  def to_script_pub_key(version, program) do
    [
      if version == 0 do
        0
      else
        version + 0x50
      end,
      Enum.count(program) | program
    ]
    |> :binary.list_to_bin()
    |> Base.encode16(case: :lower)
  end

  # Gets encoding for witness version
  defp get_encoding(0), do: :bech32
  defp get_encoding(v) when v in 1..16, do: :bech32m

  # General power-of-2 base conversion.
  defp convert_bits(data, from, to, pad \\ true) do
    max_v = (1 <<< to) - 1

    if Enum.find(data, fn c -> c < 0 || c >>> from != 0 end) do
      nil
    else
      {acc, bits, ret} =
        Enum.reduce(
          data,
          {0, 0, []},
          fn value, {acc, bits, ret} ->
            acc = acc <<< from ||| value
            bits = bits + from
            {bits, ret} = convert_bits_loop(to, max_v, acc, bits, ret)
            {acc, bits, ret}
          end
        )

      if pad && bits > 0 do
        ret ++ [acc <<< (to - bits) &&& max_v]
      else
        if bits > from || (acc <<< (to - bits) &&& max_v) > 0 do
          nil
        else
          ret
        end
      end
    end
  end

  # Recursive version of the inner loop of the convert_bits function
  defp convert_bits_loop(to, max_v, acc, bits, ret) do
    if bits >= to do
      bits = bits - to
      ret = ret ++ [acc >>> bits &&& max_v]
      convert_bits_loop(to, max_v, acc, bits, ret)
    else
      {bits, ret}
    end
  end

  # Validates witness program length
  # BIP-0141 defines witness program length must be between 2 and 40 inclusive
  # for witness version 0 only 20 and 32 are allowed
  # BIP-0341 specifies version 1 and length 32 but does not introduce any limits
  # litecoin mweb uses 66
  # future BIPs may introduce other requirements
  defp program_length_valid?(0, length), do: length in [20, 32, 66]
  defp program_length_valid?(1, length), do: length == 32 or length in 2..40
  defp program_length_valid?(_, length), do: length in 2..40
end