bech32 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22cba51d19b0989743f7f679f187311e735ce018dec53880ddfefebee89f688f
4
- data.tar.gz: 1cd7be33ab83fd724054202a812f818ed02adb5eb7545baa27e27f6b3358da2e
3
+ metadata.gz: 535da278f3fe4aa746212b6dbefb7bc34f691fe35cc62af48d7445f20e14510f
4
+ data.tar.gz: d01f1e5560dc1efcfc9f362b103119d72f74cf04a7cd3b5bb6e1f236ffad2dc1
5
5
  SHA512:
6
- metadata.gz: dc71997f097a817224b687dc53baf048ba2954cd030f7db50523d7e45121632b7f2edf758ed78298102214c3e3407cf7c7da6b3469a3cde089ade10e8346088d
7
- data.tar.gz: 6c246c4107a40193d317738bfa4637ccf6452efb48f1d36532570cec1e09681cc17fd374f47361467017e6546f36c4513f2585ed2d4b767939dcef05047ede46
6
+ metadata.gz: 3adb0de41823618cded9061c3c7bb4802363eea877d6ca44cf56c02e5bb6f8a7829943d171a9c91419d99ce7ff2abe6652ef3de0c907ecfee8d4b06da87d641a
7
+ data.tar.gz: bf708f4783d906e79e03dfc4ba420a8a02ed985fe23dc62d1c4024045df9d42c746f421efc9576648eb2e27213e26539606aeca85a04572e4dc2417e0cf7b72f
data/README.md CHANGED
@@ -135,7 +135,7 @@ Supports encoding/decoding of Nostr's [NIP-19](https://github.com/nostr-protocol
135
135
  ```ruby
136
136
  # Decode bare entity
137
137
  bech32 = 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
138
- entity = Bech32::Nostr::NIP19.parse(bech32)
138
+ entity = Bech32::Nostr::NIP19.decode(bech32)
139
139
  entity.hrp
140
140
  => 'npub'
141
141
  entity.data
@@ -143,7 +143,7 @@ entity.data
143
143
 
144
144
  # Decode tlv entity
145
145
  bech32 = 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'
146
- entity = Bech32::Nostr::NIP19.parse(bech32)
146
+ entity = Bech32::Nostr::NIP19.decode(bech32)
147
147
  entity.hrp
148
148
  => 'nprofile'
149
149
  entity.entries[0].value
data/exe/bech32 CHANGED
@@ -7,13 +7,16 @@ class CLI < Thor
7
7
 
8
8
  desc 'encode hrp data bech32/bech32m', 'Encoding data to bech32/bech32m string.'
9
9
  def encode(hrp, data, type)
10
- if type && !['bech32', 'bech32m'].include?(type)
10
+ unless ['bech32', 'bech32m'].include?(type)
11
11
  puts 'Invalid type value. type must be bech32 or bech32m.'
12
- else
13
- spec = type.nil? || type == 'bech32' ? 1 : 2
14
- data = [data].pack('H*').unpack('C*')
15
- puts Bech32.encode(hrp, data, spec)
12
+ exit 1
16
13
  end
14
+ spec = type == 'bech32' ? Bech32::Encoding::BECH32 : Bech32::Encoding::BECH32M
15
+ data = [data].pack('H*').unpack('C*')
16
+ puts Bech32.encode(hrp, data, spec)
17
+ rescue ArgumentError => e
18
+ puts "Can not encode: #{e.message}"
19
+ exit 1
17
20
  end
18
21
 
19
22
  desc 'decode "bech32string"', 'Decoding bech32/bech32m string.'
@@ -54,7 +57,11 @@ class CLI < Thor
54
57
  end
55
58
  else
56
59
  puts 'Can not decode.'
60
+ exit 1
57
61
  end
62
+ rescue ArgumentError => e
63
+ puts "Can not decode: #{e.message}"
64
+ exit 1
58
65
  end
59
66
 
60
67
  end
@@ -1,3 +1,5 @@
1
+ require 'stringio'
2
+
1
3
  module Bech32
2
4
  module Nostr
3
5
  class BareEntity
@@ -35,15 +37,30 @@ module Bech32
35
37
  @label = label
36
38
  end
37
39
 
40
+ # Maximum byte length of a TLV value (the length field is 1 byte).
41
+ MAX_VALUE_LENGTH = 255
42
+
38
43
  # Convert to binary data.
44
+ # @param [String] hrp human-readable part of the entity this entry belongs to.
45
+ # It determines whether a special entry's value is a hex string (nprofile, nevent)
46
+ # or a raw string (nrelay, naddr). If omitted, a special value is treated as hex
47
+ # only when it looks like a hex string.
39
48
  # @return [String] binary data.
40
- def to_payload
49
+ # @raise [ArgumentError] if the value is out of range or longer than MAX_VALUE_LENGTH bytes.
50
+ def to_payload(hrp = nil)
41
51
  data = if value.is_a?(Integer)
52
+ unless value.between?(0, 0xffffffff)
53
+ raise ArgumentError, "Integer value must be between 0 and #{0xffffffff}."
54
+ end
42
55
  [value].pack('N')
56
+ elsif hex_value?(hrp)
57
+ raise ArgumentError, "Value of type #{type} must be a hex string." unless hex_string?(value)
58
+ [value].pack('H*')
43
59
  else
44
- hex_string?(value) ? [value].pack('H*') : value
60
+ value.b
45
61
  end
46
62
  len = data.bytesize
63
+ raise ArgumentError, "Value must be at most #{MAX_VALUE_LENGTH} bytes." if len > MAX_VALUE_LENGTH
47
64
  [type, len].pack('CC') + data
48
65
  end
49
66
 
@@ -65,6 +82,20 @@ module Bech32
65
82
 
66
83
  private
67
84
 
85
+ # Check whether the value of this entry is a hex-encoded string.
86
+ # @param [String] hrp human-readable part (may be nil).
87
+ # @return [Boolean]
88
+ def hex_value?(hrp)
89
+ case type
90
+ when TLVEntity::TYPE_AUTHOR
91
+ true
92
+ when TLVEntity::TYPE_SPECIAL
93
+ hrp.nil? ? hex_string?(value) : [NIP19::HRP_PROFILE, NIP19::HRP_EVENT].include?(hrp)
94
+ else
95
+ false
96
+ end
97
+ end
98
+
68
99
  # Check whether +str+ is hex string or not.
69
100
  # @param [String] str string.
70
101
  # @return [Boolean]
@@ -122,16 +153,18 @@ module Bech32
122
153
  when NIP19::HRP_PROFILE
123
154
  entries << TLVEntry.new(type, value.unpack1('H*'), 'pubkey')
124
155
  when NIP19::HRP_RELAY
125
- entries << TLVEntry.new(type, value, 'relay')
156
+ entries << TLVEntry.new(type, value.force_encoding(::Encoding::UTF_8), 'relay')
126
157
  when NIP19::HRP_EVENT
127
158
  entries << TLVEntry.new(type, value.unpack1('H*'), 'id')
128
159
  when NIP19::HRP_EVENT_COORDINATE
129
- entries << TLVEntry.new(type, value, 'identifier')
160
+ entries << TLVEntry.new(type, value.force_encoding(::Encoding::UTF_8), 'identifier')
161
+ else
162
+ raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
130
163
  end
131
164
  when TYPE_RELAY # relay
132
165
  case hrp
133
166
  when NIP19::HRP_PROFILE, NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
134
- entries << TLVEntry.new(type, value, 'relay')
167
+ entries << TLVEntry.new(type, value.force_encoding(::Encoding::UTF_8), 'relay')
135
168
  else
136
169
  raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
137
170
  end
@@ -145,7 +178,8 @@ module Bech32
145
178
  when TYPE_KIND # kind
146
179
  case hrp
147
180
  when NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
148
- entries << TLVEntry.new(type, value.unpack1('H*').to_s.to_i(16), 'kind')
181
+ raise ArgumentError, 'Invalid TLV: kind must be 4 bytes.' unless value.bytesize == 4
182
+ entries << TLVEntry.new(type, value.unpack1('N'), 'kind')
149
183
  else
150
184
  raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
151
185
  end
@@ -160,7 +194,7 @@ module Bech32
160
194
  # Encode tlv entity to bech32 string.
161
195
  # @return [String] bech32 string.
162
196
  def encode
163
- data = entries.map(&:to_payload).join
197
+ data = entries.map { |e| e.to_payload(hrp) }.join
164
198
  Bech32.encode(hrp, Bech32.convert_bits(data.unpack('C*'), 8, 5), Bech32::Encoding::BECH32)
165
199
  end
166
200
  end
data/lib/bech32/nostr.rb CHANGED
@@ -2,6 +2,7 @@ module Bech32
2
2
  module Nostr
3
3
  autoload :NIP19, 'bech32/nostr/nip19'
4
4
  autoload :BareEntity, 'bech32/nostr/entity'
5
+ autoload :TLVEntry, 'bech32/nostr/entity'
5
6
  autoload :TLVEntity, 'bech32/nostr/entity'
6
7
  end
7
8
 
@@ -10,6 +10,8 @@ module Bech32
10
10
  attr_accessor :ver # witness version
11
11
  attr_accessor :prog # witness program
12
12
 
13
+ # @param [String] addr segwit address. If given, it is parsed into hrp, witness version and program.
14
+ # @raise [ArgumentError] if addr is not a valid segwit address.
13
15
  def initialize(addr = nil)
14
16
  @hrp = HRP_MAINNET
15
17
  parse_addr(addr) if addr
@@ -22,10 +24,26 @@ module Bech32
22
24
  end
23
25
 
24
26
  # parse script pubkey into witness version and witness program
27
+ # @param [String] script_pubkey segwit script pubkey with hex format.
28
+ # @raise [ArgumentError] if the script is not a valid segwit script pubkey.
25
29
  def script_pubkey=(script_pubkey)
26
- values = [script_pubkey].pack('H*').unpack("C*")
27
- @ver = values[0] == 0 ? values[0] : values[0] - 0x50
28
- @prog = values[2..-1]
30
+ raise ArgumentError, 'script_pubkey must be String.' unless script_pubkey.is_a?(String)
31
+ values = [script_pubkey].pack('H*').unpack('C*')
32
+ raise ArgumentError, 'Invalid segwit script pubkey.' if values.length < 2
33
+ op, push_len = values[0], values[1]
34
+ prog = values[2..-1]
35
+ ver = if op == 0
36
+ 0
37
+ elsif op.between?(0x51, 0x60) # OP_1..OP_16
38
+ op - 0x50
39
+ else
40
+ raise ArgumentError, 'Invalid segwit script pubkey.'
41
+ end
42
+ raise ArgumentError, 'Invalid segwit script pubkey.' unless push_len == prog.length
43
+ raise ArgumentError, 'Invalid witness program' if prog.length < 2 || prog.length > 40
44
+ raise ArgumentError, 'Invalid witness program with version 0' if ver == 0 && prog.length != 20 && prog.length != 32
45
+ @ver = ver
46
+ @prog = prog
29
47
  end
30
48
 
31
49
  # Returns segwit address string which generated from hrp, witness version and witness program.
@@ -38,13 +56,13 @@ module Bech32
38
56
 
39
57
  def parse_addr(addr)
40
58
  @hrp, data, spec = Bech32.decode(addr)
41
- raise 'Invalid address.' if hrp.nil? || data[0].nil? || ![HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
59
+ raise ArgumentError, 'Invalid address.' if hrp.nil? || data[0].nil? || ![HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
42
60
  @ver = data[0]
43
- raise 'Invalid witness version' if @ver > 16
61
+ raise ArgumentError, 'Invalid witness version' if @ver > 16
44
62
  @prog = Bech32.convert_bits(data[1..-1], 5, 8, false)
45
- raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
46
- raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
47
- raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
63
+ raise ArgumentError, 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
64
+ raise ArgumentError, 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
65
+ raise ArgumentError, 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
48
66
  end
49
67
 
50
68
  end
@@ -5,6 +5,9 @@ module Bech32
5
5
  HRP_MAINNET = 'sp'
6
6
  HRP_TESTNET = 'tsp'
7
7
 
8
+ # Maximum valid version. Version 31 is reserved as invalid by BIP-352.
9
+ MAX_VERSION = 30
10
+
8
11
  attr_reader :hrp
9
12
  attr_reader :version
10
13
  attr_reader :scan_key
@@ -18,6 +21,7 @@ module Bech32
18
21
  def initialize(hrp, version, scan_key, spend_key)
19
22
  raise ArgumentError, "hrp must be sp or tsp." unless [HRP_MAINNET, HRP_TESTNET].include?(hrp)
20
23
  raise ArgumentError, "version must be Integer." unless version.is_a?(Integer)
24
+ raise ArgumentError, "version must be between 0 and #{MAX_VERSION}." unless version.between?(0, MAX_VERSION)
21
25
  raise ArgumentError, "scan_key must be String." unless scan_key.is_a?(String)
22
26
  raise ArgumentError, "spend_key must be String." unless spend_key.is_a?(String)
23
27
  raise ArgumentError, 'scan_key must be 33 bytes.' unless [scan_key].pack('H*').bytesize == 33
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bech32
4
- VERSION = '1.5.1'
4
+ VERSION = '1.6.0'
5
5
  end
data/lib/bech32.rb CHANGED
@@ -15,6 +15,9 @@ module Bech32
15
15
 
16
16
  BECH32M_CONST = 0x2bc830a3
17
17
 
18
+ # Maximum length of the human-readable part.
19
+ HRP_MAX_LENGTH = 83
20
+
18
21
  module_function
19
22
 
20
23
  # Returns the encoded Bech32 string.
@@ -26,7 +29,12 @@ module Bech32
26
29
  # <i>Generates:</i>
27
30
  # 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4' # bech
28
31
  #
32
+ # @param [String] hrp human-readable part (1 to 83 lowercase characters in the range 33-126).
33
+ # @param [Array<Integer>] data data values (each 0 to 31).
34
+ # @param [Integer] spec encoding spec, see Bech32::Encoding.
35
+ # @raise [ArgumentError] if hrp or data is invalid.
29
36
  def encode(hrp, data, spec)
37
+ validate_encode_args(hrp, data)
30
38
  checksummed = data + create_checksum(hrp, data, spec)
31
39
  hrp + SEPARATOR + checksummed.map{|i|CHARSET[i]}.join
32
40
  end
@@ -117,6 +125,24 @@ module Bech32
117
125
  ret
118
126
  end
119
127
 
128
+ # Validate arguments of encode.
129
+ # @param [String] hrp human-readable part.
130
+ # @param [Array<Integer>] data data values.
131
+ # @raise [ArgumentError]
132
+ def validate_encode_args(hrp, data)
133
+ raise ArgumentError, 'hrp must be String.' unless hrp.is_a?(String)
134
+ unless hrp.length.between?(1, HRP_MAX_LENGTH)
135
+ raise ArgumentError, "hrp must be between 1 and #{HRP_MAX_LENGTH} characters."
136
+ end
137
+ if hrp.bytes.any? { |b| b < 33 || b > 126 } || hrp != hrp.downcase
138
+ raise ArgumentError, 'hrp must consist of lowercase characters in the range 33-126.'
139
+ end
140
+ raise ArgumentError, 'data must be Array.' unless data.is_a?(Array)
141
+ unless data.all? { |v| v.is_a?(Integer) && v.between?(0, 31) }
142
+ raise ArgumentError, 'data values must be Integer between 0 and 31.'
143
+ end
144
+ end
145
+
120
146
  # Compute Bech32 checksum
121
147
  def polymod(values)
122
148
  generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
@@ -129,6 +155,6 @@ module Bech32
129
155
  chk
130
156
  end
131
157
 
132
- private_class_method :polymod, :expand_hrp
158
+ private_class_method :polymod, :expand_hrp, :validate_encode_args
133
159
 
134
160
  end
@@ -9,6 +9,7 @@ module Bech32
9
9
  SEPARATOR: String
10
10
  CHARSET: Array[String]
11
11
  BECH32M_CONST: Integer
12
+ HRP_MAX_LENGTH: Integer
12
13
 
13
14
  # Encode hrp and data to bech32/bech32m string.
14
15
  def self.encode: (String hrp, Array[Integer] data, Integer spec) -> String
@@ -40,6 +41,10 @@ module Bech32
40
41
  # Compute polymod checksum.
41
42
  def self.polymod: (Array[Integer] values) -> Integer
42
43
 
44
+ # Validate arguments of encode.
45
+ def self.validate_encode_args: (String hrp, Array[Integer] data) -> void
46
+
43
47
  def expand_hrp: (String hrp) -> Array[Integer]
44
48
  def polymod: (Array[Integer] values) -> Integer
49
+ def validate_encode_args: (String hrp, Array[Integer] data) -> void
45
50
  end
@@ -12,6 +12,8 @@ module Bech32
12
12
  end
13
13
 
14
14
  class TLVEntry
15
+ MAX_VALUE_LENGTH: Integer
16
+
15
17
  attr_reader type: Integer
16
18
  attr_reader label: String?
17
19
  attr_reader value: String | Integer
@@ -20,13 +22,16 @@ module Bech32
20
22
  def initialize: (Integer type, String | Integer value, ?String? label) -> void
21
23
 
22
24
  # Convert to binary payload.
23
- def to_payload: () -> String
25
+ def to_payload: (?String? hrp) -> String
24
26
 
25
27
  # String representation.
26
28
  def to_s: () -> String
27
29
 
28
30
  private
29
31
 
32
+ # Check if the value of this entry is hex encoded.
33
+ def hex_value?: (String? hrp) -> bool
34
+
30
35
  # Check if string is hex encoded.
31
36
  def hex_string?: (String str) -> bool
32
37
  end
@@ -3,6 +3,7 @@ module Bech32
3
3
  class SilentPaymentAddr
4
4
  HRP_MAINNET: String
5
5
  HRP_TESTNET: String
6
+ MAX_VERSION: Integer
6
7
 
7
8
  attr_reader hrp: String
8
9
  attr_reader version: Integer
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bech32
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shigeyuki Azuchi