module Bech32 module Encoding BECH32 = 1 BECH32M = 2 end autoload :SegwitAddr, 'bech32/segwit_addr' autoload :Nostr, 'bech32/nostr' SEPARATOR = '1' CHARSET = %w(q p z r y 9 x 8 g f 2 t v d w 0 s 3 j n 5 4 k h c e 6 m u a 7 l) BECH32M_CONST = 0x2bc830a3 module_function # Returns the encoded Bech32 string. # # require 'bech32' # # bech = Bech32.encode('bc', [], Bech32::Encoding::BECH32) # # Generates: # 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4' # bech # def encode(hrp, data, spec) checksummed = data + create_checksum(hrp, data, spec) hrp + SEPARATOR + checksummed.map{|i|CHARSET[i]}.join end # Returns the Bech32 decoded hrp and data. # # require 'bech32' # # addr = 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4' # hrp, data, spec = Bech32.decode(addr) # # Generates: # 'bc' # hrp # [0, 14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 12, 29, 3, 4, 15, 24, 20, 6, 14, 30, 22] # data # 1 # spec see Bech32::Encoding # def decode(bech, max_length = 90) # check uppercase/lowercase return nil if bech.bytes.index{|x| x < 33 || x > 126} return nil if (bech.downcase != bech && bech.upcase != bech) bech = bech.downcase # check data length pos = bech.rindex(SEPARATOR) return nil if pos.nil? || pos + 7 > bech.length || bech.length > max_length # 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 spec = verify_checksum(hrp, data) spec ? [hrp, data[0..-7], spec] : nil end # Returns computed checksum values of +hrp+ and +data+ def create_checksum(hrp, data, spec) values = expand_hrp(hrp) + data const = (spec == Bech32::Encoding::BECH32M ? Bech32::BECH32M_CONST : 1) polymod = polymod(values + [0, 0, 0, 0, 0, 0]) ^ const (0..5).map{|i|(polymod >> 5 * (5 - i)) & 31} end # Verify a checksum given Bech32 string # @param [String] hrp hrp part. # @param [Array[Integer]] data data array. # @return [Integer] spec def verify_checksum(hrp, data) const = polymod(expand_hrp(hrp) + data) case const when 1 Encoding::BECH32 when BECH32M_CONST Encoding::BECH32M end end # Expand the hrp into values for checksum computation. def expand_hrp(hrp) hrp.each_char.map{|c|c.ord >> 5} + [0] + hrp.each_char.map{|c|c.ord & 31} end # Convert a +data+ where each byte is encoding +from+ bits to a byte slice where each byte is encoding +to+ bits. # @param [Array] data # @param [Integer] from # @param [Integer] to # @param [Boolean] padding # @return [Array] def convert_bits(data, from, to, padding=true) acc = 0 bits = 0 ret = [] maxv = (1 << to) - 1 max_acc = (1 << (from + to - 1)) - 1 data.each do |v| return nil if v < 0 || (v >> from) != 0 acc = ((acc << from) | v) & max_acc bits += from while bits >= to bits -= to ret << ((acc >> bits) & maxv) end end if padding ret << ((acc << (to - bits)) & maxv) unless bits == 0 elsif bits >= from || ((acc << (to - bits)) & maxv) != 0 return nil end ret end # Compute Bech32 checksum 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 private_class_method :polymod, :expand_hrp end