Class: Sibit::Bech32

Inherits:
Object
  • Object
show all
Defined in:
lib/sibit/bech32.rb

Overview

Bech32 encoding and decoding for SegWit addresses.

Encodes witness programs to Bech32 addresses (bc1...) and decodes them back.

Author

Yegor Bugayenko (yegor256@gmail.com)

Copyright

Copyright (c) 2019-2026 Yegor Bugayenko

License

MIT

Constant Summary collapse

CHARSET =
'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
GENERATOR =
[0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(addr) ⇒ Bech32

Returns a new instance of Bech32.



63
64
65
# File 'lib/sibit/bech32.rb', line 63

def initialize(addr)
  @addr = addr.downcase
end

Class Method Details

.bits(data, frombits, tobits, pad) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/sibit/bech32.rb', line 46

def self.bits(data, frombits, tobits, pad)
  acc = 0
  num = 0
  result = []
  maxv = (1 << tobits) - 1
  data.each do |v|
    acc = (acc << frombits) | v
    num += frombits
    while num >= tobits
      num -= tobits
      result << ((acc >> num) & maxv)
    end
  end
  result << ((acc << (tobits - num)) & maxv) if pad && num.positive?
  result
end

.checksum(hrp, data) ⇒ Object



27
28
29
30
# File 'lib/sibit/bech32.rb', line 27

def self.checksum(hrp, data)
  poly = pm(expanded(hrp) + data + [0, 0, 0, 0, 0, 0]) ^ 1
  (0..5).map { |i| (poly >> (5 * (5 - i))) & 31 }
end

.encode(hrp, ver, prog) ⇒ Object



21
22
23
24
25
# File 'lib/sibit/bech32.rb', line 21

def self.encode(hrp, ver, prog)
  data = [ver] + bits([prog].pack('H*').bytes, 8, 5, true)
  chk = checksum(hrp, data)
  "#{hrp}1#{(data + chk).map { |d| CHARSET[d] }.join}"
end

.expanded(hrp) ⇒ Object



32
33
34
# File 'lib/sibit/bech32.rb', line 32

def self.expanded(hrp)
  hrp.chars.map { |c| c.ord >> 5 } + [0] + hrp.chars.map { |c| c.ord & 31 }
end

.pm(values) ⇒ Object



36
37
38
39
40
41
42
43
44
# File 'lib/sibit/bech32.rb', line 36

def self.pm(values)
  chk = 1
  values.each do |v|
    top = chk >> 25
    chk = ((chk & 0x1ffffff) << 5) ^ v
    5.times { |i| chk ^= GENERATOR[i] if (top >> i).allbits?(1) }
  end
  chk
end

Instance Method Details

#versionObject



84
85
86
87
# File 'lib/sibit/bech32.rb', line 84

def version
  _, data = parse
  data[0]
end

#witnessObject

Raises:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/sibit/bech32.rb', line 67

def witness
  hrp, data = parse
  ver = data[0]
  unless ver.between?(0, 16)
    raise(Sibit::Error, "Unsupported witness version #{ver} in '#{@addr}'")
  end
  raise(Sibit::Error, "Invalid Bech32 checksum in '#{@addr}'") unless verified?(hrp, data)
  prog = convert(data[1..-7], 5, 8)
  if ver.zero? && [20, 32].none?(prog.length)
    raise(Sibit::Error, "Witness v0 program in '#{@addr}' must be 20 or 32 bytes")
  end
  unless prog.length.between?(2, 40)
    raise(Sibit::Error, "Witness program in '#{@addr}' must be 2 to 40 bytes")
  end
  prog.pack('C*').unpack1('H*')
end