module Cryptocurrency::Bech32

Constants

CHARSET
SEPARATOR

Public Class Methods

create_checksum(hrp, data) click to toggle source

Compute the checksum values given hrp and data.

# File lib/cryptocurrency/bech32.rb, line 44
def create_checksum(hrp, data)
  values = expand_hrp(hrp) + data
  polymod = polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
  (0..5).map { |i| (polymod >> 5 * (5 - i)) & 31 }
end
decode(bech) click to toggle source

Decode a Bech32 string and determine hrp and data

# File lib/cryptocurrency/bech32.rb, line 16
def decode(bech)
  # check invalid bytes
  return nil if bech.scrub('?').include?('?')

  # check uppercase/lowercase
  return nil if bech.downcase != bech && bech.upcase != bech

  bech.each_char { |c| return nil if c.ord < 33 || c.ord > 126 }
  bech = bech.downcase

  # check data length
  pos = bech.rindex(SEPARATOR)
  return nil if pos.nil? || pos < 1 || pos + 7 > bech.length || bech.length > 90

  # check valid charset
  bech[pos + 1..-1].each_char { |c| return nil unless CHARSET.include?(c) }

  # split hrp and data
  hrp = bech[0..pos - 1]
  data = bech[pos + 1..-1].each_char.map { |c| CHARSET.index(c) }

  # check checksum
  return nil unless verify_checksum(hrp, data)

  [hrp, join_5bits(data[0..-7])]
end
encode(hrp, data) click to toggle source

Encode Bech32 string

# File lib/cryptocurrency/bech32.rb, line 8
def encode(hrp, data)
  blocks = split_5bits(data)

  checksummed = blocks + create_checksum(hrp, blocks)
  hrp + SEPARATOR + checksummed.map { |i| CHARSET[i] }.join
end
join_5bits(data) click to toggle source
# File lib/cryptocurrency/bech32.rb, line 69
def join_5bits(data)
  n = 0
  in_prefix = true
  prefix = ''

  data.each do |i|
    if in_prefix && i == 0
      prefix += "\x00"
    else
      in_prefix = false
    end

    n = n * 32 + i
  end

  prefix + Utils.integer_to_bytes(n)
end
split_5bits(data) click to toggle source
# File lib/cryptocurrency/bech32.rb, line 55
def split_5bits(data)
  n = Utils.bytes_to_integer(data)

  chunks = []
  while n > 0
    chunks << n % 32
    n /= 32
  end

  zeroes = data.bytes.find_index { |b| b != 0 } || data.length

  [0] * zeroes + chunks.reverse
end
verify_checksum(hrp, data) click to toggle source

Verify a checksum given Bech32 string

# File lib/cryptocurrency/bech32.rb, line 51
def verify_checksum(hrp, data)
  polymod(expand_hrp(hrp) + data) == 1
end

Private Class Methods

expand_hrp(hrp) click to toggle source

Expand the hrp into values for checksum computation.

# File lib/cryptocurrency/bech32.rb, line 90
def expand_hrp(hrp)
  hrp.each_char.map { |c| c.ord >> 5 } + [0] + hrp.each_char.map { |c| c.ord & 31 }
end
polymod(values) click to toggle source

Compute Bech32 checksum

# File lib/cryptocurrency/bech32.rb, line 95
def polymod(values)
  generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
  chk = 1
  values.each do |v|
    top = chk >> 25
    chk = (chk & 0x1ffffff) << 5 ^ v
    (0..4).each { |i| chk ^= ((top >> i) & 1) == 0 ? 0 : generator[i] }
  end
  chk
end