defmodule Bech32 do @moduledoc false # @moduledoc """ # This is an implementation of BIP-0173 # Bech32 address format for native v0-16 witness outputs. # See https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki for details # """ @gen {0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3} use Bitwise char_table = [ {0, ~c(qpzry9x8)}, {8, ~c(gf2tvdw0)}, {16, ~c(s3jn54kh)}, {24, ~c(ce6mua7l)} ] |> Enum.map(fn {x, chars} -> Enum.zip(chars, 0..(length(chars) - 1)) |> Enum.map(fn {char, val} -> {char, val + x} end) end) |> Enum.reduce([], &++/2) |> Enum.sort() |> MapSet.new() # Generate a lookup function for {char, val} <- char_table do defp char_to_value(unquote(char)), do: unquote(val) # Uppercase too if char >= ?a and char <= ?z do char = char - ?a + ?A defp char_to_value(unquote(char)), do: unquote(val) end end defp char_to_value(_char) do nil end # Generate a lookup function for {char, val} <- char_table do defp value_to_char(unquote(val)), do: unquote(char) end defp value_to_char(_char) do nil end defp polymod(values) when is_list(values) do values |> Enum.reduce(1, fn v, chk -> b = chk >>> 25 chk = Bitwise.bxor((chk &&& 0x1FFFFFF) <<< 5, v) 0..4 |> Enum.reduce(chk, fn i, chk -> Bitwise.bxor(chk, if((b >>> i &&& 1) !== 0, do: @gen |> elem(i), else: 0)) end) end) end defp hrp_expand(s) when is_binary(s) do chars = String.to_charlist(s) for(c <- chars, do: c >>> 5) ++ [0] ++ for c <- chars, do: c &&& 31 end defp verify_checksum(hrp, data_string) when is_binary(hrp) and is_binary(data_string) do data = data_string |> String.to_charlist() |> Enum.map(&char_to_value/1) if data |> Enum.all?(&(&1 !== nil)) do if polymod(hrp_expand(hrp) ++ data) === 1 do :ok else {:error, :checksum_failed} end else {:error, :invalid_char} end end defp split_hrp_and_data_string(addr) do # Reversing is done here in case '1' is in the human readable part (hrp) # so we want to split on the last occurrence case String.split(addr |> String.reverse(), "1", parts: 2) do [data_string, hrp] -> {:ok, hrp |> String.reverse(), data_string |> String.reverse()} _ -> {:error, :not_bech32} end end @doc ~S""" Verify the checksum of the address report any errors. Note that this doesn't perform exhaustive validation of the address. If you need to make sure the address is well formed please use `decode/1` or `decode/2` instead. Returns `:ok` or an `{:error, reason}` tuple. ## Example iex> Bech32.verify("ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq") :ok """ @spec verify(String.t()) :: :ok | {:error, :checksum_failed | :invalid_char | :not_bech32} def verify(addr) when is_binary(addr) do case split_hrp_and_data_string(addr) do {:ok, hrp, data_string} -> verify_checksum(hrp, data_string) {:error, :not_bech32} -> {:error, :not_bech32} end end @doc ~S""" Verify the checksum of the address report success or failure. Note that this doesn't perform exhaustive validation of the address. If you need to make sure the address is well formed please use `decode/1` or `decode/2` instead. Returns `true` or `false`. ## Example iex> Bech32.verify_predicate("ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq") true """ @spec verify_predicate(String.t()) :: boolean def verify_predicate(addr) when is_binary(addr) do case verify(addr) do :ok -> true _ -> false end end @doc ~S""" Get the human readable part of the address. Very little validation is done here please use `decode/1` or `decode/2` if you need to validate the address. Returns `{:ok, hrp :: String.t()}` or an `{:error, reason}` tuple. ## Example iex> Bech32.get_hrp("ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq") {:ok, "ckb"} """ @spec get_hrp(addr :: String.t()) :: {:ok, hrp :: String.t()} | {:error, :not_bech32} def get_hrp(addr) when is_binary(addr) do case split_hrp_and_data_string(addr) do {:ok, hrp, _data_string} -> {:ok, hrp} {:error, :not_bech32} -> {:error, :not_bech32} end end @doc ~S""" Create a checksum from the human readable part plus the data part. Returns a binary that represents the checksum. ## Example iex> Bech32.create_checksum("ckb", <<1, 0, 221, 231, 128, 28, 7, 61, 251, 52, 100, 199, 177, 240, 91, 128, 107, 178, 187, 184, 78, 153>>) <<4, 5, 2, 7, 25, 10>> """ @spec create_checksum(String.t(), binary) :: binary def create_checksum(hrp, data) when is_binary(hrp) and is_binary(data) do data = :erlang.binary_to_list(data) values = hrp_expand(hrp) ++ data pmod = Bitwise.bxor(polymod(values ++ [0, 0, 0, 0, 0, 0]), 1) for(i <- 0..5, do: pmod >>> (5 * (5 - i)) &&& 31) |> :erlang.list_to_binary() end @doc ~S""" Encode a bech32 address from the hrp and data directly (data is a raw binary with no pre-processing). Returns a bech32 address as a string. ## Example iex> Bech32.encode("ckb", <<1, 0, 221, 231, 128, 28, 7, 61, 251, 52, 100, 199, 177, 240, 91, 128, 107, 178, 187, 184, 78, 153>>) "ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq" """ @spec encode(String.t(), binary) :: String.t() def encode(hrp, data) when is_binary(hrp) and is_binary(data) do encode_from_5bit(hrp, convertbits(data)) end @doc ~S""" Encode address from 5 bit encoded values in each byte. In other words bytes should have a value between `0` and `31`. Returns a bech32 address as a string. ## Example iex> Bech32.encode_from_5bit("ckb", Bech32.convertbits(<<1, 0, 221, 231, 128, 28, 7, 61, 251, 52, 100, 199, 177, 240, 91, 128, 107, 178, 187, 184, 78, 153>>)) "ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq" """ @spec encode_from_5bit(String.t(), binary) :: String.t() def encode_from_5bit(hrp, data) when is_binary(hrp) and is_binary(data) do hrp <> "1" <> :erlang.list_to_binary( for < create_checksum(hrp, data)>>, do: value_to_char(d) ) end @doc ~S""" Convert raw binary to 5 bit per byte encoded byte string. Returns a binary that uses 5 bits per byte. ## Example iex> Bech32.convertbits(<<1, 0, 221, 231, 128, 28, 7, 61, 251, 52, 100, 199, 177, 240, 91, 128, 107, 178, 187, 184, 78, 153>>) <<0, 4, 0, 13, 27, 25, 28, 0, 3, 16, 3, 19, 27, 30, 25, 20, 12, 19, 3, 27, 3, 28, 2, 27, 16, 1, 21, 27, 5, 14, 29, 24, 9, 26, 12, 16>> """ @spec convertbits(binary, pos_integer, pos_integer, boolean) :: binary def convertbits(data, frombits \\ 8, tobits \\ 5, pad \\ true) def convertbits(data, frombits, tobits, pad) when is_binary(data) and is_integer(frombits) and is_integer(tobits) and is_boolean(pad) and frombits >= tobits and frombits > 0 and tobits > 0 do num_data_bits = bit_size(data) num_tail_bits = rem(num_data_bits, tobits) data = if pad do missing_bits = 8 - num_tail_bits <> else data end :erlang.list_to_binary(for <>, do: x) end def convertbits(data, frombits, tobits, pad) when is_binary(data) and is_integer(frombits) and is_integer(tobits) and is_boolean(pad) and frombits <= tobits and frombits > 0 and tobits > 0 do data = data |> :erlang.binary_to_list() |> Enum.reverse() |> Enum.reduce("", fn v, acc -> <> end) data = if pad do leftover_bits = bit_size(data) |> rem(tobits) padding_bits = tobits - leftover_bits <> else data end for(<>, do: c) |> :erlang.list_to_binary() end @doc ~S""" Decode a bech32 address. You can also pass the `:ignore_length` keyword into the opts if you want to allow more than 90 chars for currencies like Nervos CKB. Returns `{:ok, hrp :: String.t(), data :: binary}` or an `{:error, reason}` tuple. Note that we return 8 bits per byte here not 5 bits per byte. ## Example iex> Bech32.decode("ckb1qyq036wytncnfv0ekfjqrch7s5hzr4hkjl4qs54f7e") {:ok, "ckb", <<1, 0, 248, 233, 196, 92, 241, 52, 177, 249, 178, 100, 1, 226, 254, 133, 46, 33, 214, 246, 151, 234>>} """ @spec decode(String.t(), keyword) :: {:ok, hrp :: String.t(), data :: binary} | {:error, :no_separator | :no_hrp | :checksum_too_short | :too_long | :not_in_charset | :checksum_failed | :invalid_char | :mixed_case_char} def decode(addr, opts \\ []) when is_binary(addr) do unless Enum.any?(:erlang.binary_to_list(addr), fn c -> c < ?! or c > ?~ end) do unless String.downcase(addr) !== addr and String.upcase(addr) !== addr do addr = String.downcase(addr) data_part = ~r/.+(1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)$/ |> Regex.run(addr) case ~r/.+(1.+)$/ |> Regex.run(addr, return: :index) do nil -> {:error, :no_separator} [_, {last_one_pos, _tail_size_including_one}] -> cond do last_one_pos === 0 -> {:error, :no_hrp} last_one_pos + 7 > byte_size(addr) -> {:error, :checksum_too_short} byte_size(addr) > 90 and Keyword.get(opts, :ignore_length, false) {:error, :too_long} data_part === nil -> {:error, :not_in_charset} true -> <> = addr case verify_checksum(hrp, data_with_checksum) do :ok -> checksum_bits = 6 * 8 data_bits = bit_size(data_with_checksum) - checksum_bits <> = data_with_checksum data = data |> :erlang.binary_to_list() |> Enum.map(&char_to_value/1) |> Enum.reverse() |> Enum.reduce( "", fn v, acc -> <> end ) data_bitlen = bit_size(data) data_bytes = div(data_bitlen, 8) data = case rem(data_bitlen, 8) do 0 -> data n when n < 5 -> data_bitlen = data_bytes * 8 <> = data data n -> missing_bits = 8 - n <> end {:ok, hrp, data} {:error, reason} -> {:error, reason} end end end else {:error, :mixed_case_char} end else {:error, :invalid_char} end end end