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 +4 -4
- data/README.md +2 -2
- data/exe/bech32 +12 -5
- data/lib/bech32/nostr/entity.rb +41 -7
- data/lib/bech32/nostr.rb +1 -0
- data/lib/bech32/segwit_addr.rb +26 -8
- data/lib/bech32/silent_payment_addr.rb +4 -0
- data/lib/bech32/version.rb +1 -1
- data/lib/bech32.rb +27 -1
- data/sig/bech32/bech32.rbs +5 -0
- data/sig/bech32/nostr/entity.rbs +6 -1
- data/sig/bech32/silent_payment_addr.rbs +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 535da278f3fe4aa746212b6dbefb7bc34f691fe35cc62af48d7445f20e14510f
|
|
4
|
+
data.tar.gz: d01f1e5560dc1efcfc9f362b103119d72f74cf04a7cd3b5bb6e1f236ffad2dc1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
10
|
+
unless ['bech32', 'bech32m'].include?(type)
|
|
11
11
|
puts 'Invalid type value. type must be bech32 or bech32m.'
|
|
12
|
-
|
|
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
|
data/lib/bech32/nostr/entity.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
data/lib/bech32/segwit_addr.rb
CHANGED
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
data/lib/bech32/version.rb
CHANGED
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
|
data/sig/bech32/bech32.rbs
CHANGED
|
@@ -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
|
data/sig/bech32/nostr/entity.rbs
CHANGED
|
@@ -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
|