module AdequateCryptoAddress::Utils::Bech32

Constants

CHARSET
CHARSET_REV

Public Class Methods

convert_bits(chunks, from_bits:, to_bits:, pad:) click to toggle source

Utility for converting bytes of data between bases. These is used for BIP 173 address encoding/decoding to convert between sequences of bytes representing 8-bit values and groups of 5 bits. Conversions may be padded with trailing 0 bits to the nearest byte boundary. Returns nil if conversion requires padding and pad is false.

For example:

convert_bits("\xFF\xFF", from_bits: 8, to_bits: 5, pad: true)
  => "\x1F\x1F\x1F\10"

See github.com/bitcoin/bitcoin/blob/595a7bab23bc21049526229054ea1fff1a29c0bf/src/utilstrencodings.h#L154

# File lib/adequate_crypto_address/utils/bech32.rb, line 142
def convert_bits(chunks, from_bits:, to_bits:, pad:)
  output_mask = (1 << to_bits) - 1
  buffer_mask = (1 << (from_bits + to_bits - 1)) - 1

  buffer = 0
  bits = 0

  output = []
  chunks.each do |chunk|
    buffer = ((buffer << from_bits) | chunk) & buffer_mask
    bits += from_bits
    while bits >= to_bits
      bits -= to_bits
      output << ((buffer >> bits) & output_mask)
    end
  end

  output << ((buffer << (to_bits - bits)) & output_mask) if pad && bits > 0

  return nil if !pad && (bits >= from_bits || ((buffer << (to_bits - bits)) & output_mask) != 0)

  output
end
decode(input) click to toggle source

rubocop:disable CyclomaticComplexity,PerceivedComplexity

# File lib/adequate_crypto_address/utils/bech32.rb, line 72
def decode(input)
  chk = 1
  input_len = input.bytesize
  have_lower = false
  have_upper = false

  return nil if input_len < 8 || input_len > 90

  data_len = 0
  data_len += 1 while data_len < input_len && input[(input_len - 1) - data_len] != '1'

  hrp_len = input_len - (1 + data_len)
  return nil if hrp_len < 1 || data_len < 6

  hrp = []
  hrp_len.times do |i|
    ch = input[i].ord
    return nil if ch < 33 || ch > 126

    if ch >= 'a'.ord && ch <= 'z'.ord
      have_lower = true
    elsif ch >= 'A'.ord && ch <= 'Z'.ord
      have_upper = true
      ch = (ch - 'A'.ord) + 'a'.ord
    end

    hrp << ch
    chk = polymod_step(chk) ^ (ch >> 5)
  end

  chk = polymod_step(chk)

  hrp_len.times do |i|
    chk = polymod_step(chk) ^ (input[i].ord & 0x1f)
  end

  data = []
  i = hrp_len + 1
  while i < input_len
    ch = input[i].ord
    v = (ch & 0x80) != 0 ? -1 : CHARSET_REV[ch]

    have_lower = true if ch >= 'a'.ord && ch <= 'z'.ord
    have_upper = true if ch >= 'A'.ord && ch <= 'Z'.ord
    return nil if v == -1

    chk = polymod_step(chk) ^ v
    data << v if (i + 6) < input_len
    i += 1
  end

  return nil if have_lower && have_upper
  return nil if chk != 1

  [hrp.pack('C*'), data]
end
polymod_step(pre) click to toggle source
# File lib/adequate_crypto_address/utils/bech32.rb, line 20
def polymod_step(pre)
  b = pre >> 25
  ((pre & 0x1FFFFFF) << 5) ^ \
    (-((b >> 0) & 1) & 0x3b6a57b2) ^ \
    (-((b >> 1) & 1) & 0x26508e6d) ^ \
    (-((b >> 2) & 1) & 0x1ea119fa) ^ \
    (-((b >> 3) & 1) & 0x3d4233dd) ^ \
    (-((b >> 4) & 1) & 0x2a1462b3)
end