/ref/ruby/bech32.rb

https://github.com/sipa/bech32 · Ruby · 99 lines · 55 code · 14 blank · 30 comment · 12 complexity · 870e35d1685a1c22f4906fe9717d5078 MD5 · raw file

  1. # Copyright (c) 2017 Shigeyuki Azuchi
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy
  4. # of this software and associated documentation files (the "Software"), to deal
  5. # in the Software without restriction, including without limitation the rights
  6. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. # copies of the Software, and to permit persons to whom the Software is
  8. # furnished to do so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in
  11. # all copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. # THE SOFTWARE.
  20. module Bech32
  21. module Encoding
  22. BECH32 = 1
  23. BECH32M = 2
  24. end
  25. SEPARATOR = '1'
  26. 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)
  27. BECH32M_CONST = 0x2bc830a3
  28. module_function
  29. # Encode Bech32 string
  30. def encode(hrp, data, spec)
  31. checksummed = data + create_checksum(hrp, data, spec)
  32. hrp + SEPARATOR + checksummed.map{|i|CHARSET[i]}.join
  33. end
  34. # Decode a Bech32 string and determine hrp and data
  35. def decode(bech)
  36. # check uppercase/lowercase
  37. return nil if bech.bytes.index{|x| x < 33 || x > 126}
  38. return nil if (bech.downcase != bech && bech.upcase != bech)
  39. bech = bech.downcase
  40. # check data length
  41. pos = bech.rindex(SEPARATOR)
  42. return nil if pos.nil? || pos < 1 || pos + 7 > bech.length || bech.length > 90
  43. # check valid charset
  44. bech[pos+1..-1].each_char{|c|return nil unless CHARSET.include?(c)}
  45. # split hrp and data
  46. hrp = bech[0..pos-1]
  47. data = bech[pos+1..-1].each_char.map{|c|CHARSET.index(c)}
  48. # check checksum
  49. spec = verify_checksum(hrp, data)
  50. spec ? [hrp, data[0..-7], spec] : nil
  51. end
  52. # Compute the checksum values given hrp and data.
  53. def create_checksum(hrp, data, spec)
  54. values = expand_hrp(hrp) + data
  55. const = (spec == Bech32::Encoding::BECH32M ? Bech32::BECH32M_CONST : 1)
  56. polymod = polymod(values + [0, 0, 0, 0, 0, 0]) ^ const
  57. (0..5).map{|i|(polymod >> 5 * (5 - i)) & 31}
  58. end
  59. # Verify a checksum given Bech32 string
  60. def verify_checksum(hrp, data)
  61. const = polymod(expand_hrp(hrp) + data)
  62. case const
  63. when 1
  64. Encoding::BECH32
  65. when BECH32M_CONST
  66. Encoding::BECH32M
  67. end
  68. end
  69. # Expand the hrp into values for checksum computation.
  70. def expand_hrp(hrp)
  71. hrp.each_char.map{|c|c.ord >> 5} + [0] + hrp.each_char.map{|c|c.ord & 31}
  72. end
  73. # Compute Bech32 checksum
  74. def polymod(values)
  75. generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
  76. chk = 1
  77. values.each do |v|
  78. top = chk >> 25
  79. chk = (chk & 0x1ffffff) << 5 ^ v
  80. (0..4).each{|i|chk ^= ((top >> i) & 1) == 0 ? 0 : generator[i]}
  81. end
  82. chk
  83. end
  84. private_class_method :polymod, :expand_hrp
  85. end