bech32 1.5.0 → 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/.github/workflows/main.yml +14 -1
- data/.ruby-version +1 -1
- data/Gemfile +5 -0
- data/README.md +2 -2
- data/Steepfile +19 -0
- data/bech32.gemspec +2 -0
- data/exe/bech32 +12 -5
- data/lib/bech32/nostr/entity.rb +50 -11
- data/lib/bech32/nostr/nip19.rb +3 -1
- data/lib/bech32/nostr.rb +1 -0
- data/lib/bech32/segwit_addr.rb +26 -8
- data/lib/bech32/silent_payment_addr.rb +7 -1
- data/lib/bech32/version.rb +1 -1
- data/lib/bech32.rb +28 -2
- data/sig/bech32/bech32.rbs +50 -0
- data/sig/bech32/nostr/entity.rbs +59 -0
- data/sig/bech32/nostr/nip19.rbs +23 -0
- data/sig/bech32/segwit_addr.rbs +28 -0
- data/sig/bech32/silent_payment_addr.rbs +22 -0
- metadata +11 -4
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/.github/workflows/main.yml
CHANGED
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
strategy:
|
|
21
21
|
matrix:
|
|
22
|
-
ruby-version: ['3.
|
|
22
|
+
ruby-version: ['3.2', '3.3', '3.4', '4.0']
|
|
23
23
|
|
|
24
24
|
steps:
|
|
25
25
|
- uses: actions/checkout@v4
|
|
@@ -32,3 +32,16 @@ jobs:
|
|
|
32
32
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
33
33
|
- name: Run tests
|
|
34
34
|
run: bundle exec rake
|
|
35
|
+
|
|
36
|
+
typecheck:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
- name: Set up Ruby
|
|
42
|
+
uses: ruby/setup-ruby@v1
|
|
43
|
+
with:
|
|
44
|
+
ruby-version: '3.4'
|
|
45
|
+
bundler-cache: true
|
|
46
|
+
- name: Run type check
|
|
47
|
+
run: bundle exec steep check
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby-
|
|
1
|
+
ruby-4.0.0
|
data/Gemfile
CHANGED
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/Steepfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Steepfile
|
|
2
|
+
target :lib do
|
|
3
|
+
signature "sig"
|
|
4
|
+
|
|
5
|
+
check "lib"
|
|
6
|
+
|
|
7
|
+
library "stringio"
|
|
8
|
+
|
|
9
|
+
# Configure diagnostic severity
|
|
10
|
+
# These errors are from Ruby code patterns that are runtime-safe but hard to express in RBS
|
|
11
|
+
configure_code_diagnostics do |hash|
|
|
12
|
+
# Suppress errors for method calls on potentially nil values
|
|
13
|
+
# The actual code handles nil cases properly at runtime
|
|
14
|
+
hash[Steep::Diagnostic::Ruby::NoMethod] = :hint
|
|
15
|
+
hash[Steep::Diagnostic::Ruby::ArgumentTypeMismatch] = :hint
|
|
16
|
+
hash[Steep::Diagnostic::Ruby::MethodBodyTypeMismatch] = :hint
|
|
17
|
+
hash[Steep::Diagnostic::Ruby::UnannotatedEmptyCollection] = :hint
|
|
18
|
+
end
|
|
19
|
+
end
|
data/bech32.gemspec
CHANGED
|
@@ -13,8 +13,10 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.description = %q(The implementation of Bech32 encoder and decoder.)
|
|
14
14
|
spec.homepage = 'https://github.com/azuchi/bech32rb'
|
|
15
15
|
spec.license = 'MIT'
|
|
16
|
+
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
16
17
|
|
|
17
18
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
+
spec.files += Dir['sig/**/*.rbs']
|
|
18
20
|
spec.bindir = 'exe'
|
|
19
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
22
|
spec.require_paths = ['lib']
|
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]
|
|
@@ -110,37 +141,45 @@ module Bech32
|
|
|
110
141
|
buf = ::StringIO.new(data)
|
|
111
142
|
entries = []
|
|
112
143
|
until buf.eof?
|
|
113
|
-
|
|
144
|
+
header = buf.read(2)
|
|
145
|
+
raise ArgumentError, "Invalid TLV: truncated header." if header.nil? || header.bytesize < 2
|
|
146
|
+
type, len = header.unpack('CC')
|
|
147
|
+
value = buf.read(len)
|
|
148
|
+
raise ArgumentError, "Invalid TLV: truncated value." if value.nil? ? len > 0 : value.bytesize < len
|
|
149
|
+
value ||= ''
|
|
114
150
|
case type
|
|
115
151
|
when TYPE_SPECIAL # special
|
|
116
152
|
case hrp
|
|
117
153
|
when NIP19::HRP_PROFILE
|
|
118
|
-
entries << TLVEntry.new(type,
|
|
154
|
+
entries << TLVEntry.new(type, value.unpack1('H*'), 'pubkey')
|
|
119
155
|
when NIP19::HRP_RELAY
|
|
120
|
-
entries << TLVEntry.new(type,
|
|
156
|
+
entries << TLVEntry.new(type, value.force_encoding(::Encoding::UTF_8), 'relay')
|
|
121
157
|
when NIP19::HRP_EVENT
|
|
122
|
-
entries << TLVEntry.new(type,
|
|
158
|
+
entries << TLVEntry.new(type, value.unpack1('H*'), 'id')
|
|
123
159
|
when NIP19::HRP_EVENT_COORDINATE
|
|
124
|
-
entries << TLVEntry.new(type,
|
|
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}"
|
|
125
163
|
end
|
|
126
164
|
when TYPE_RELAY # relay
|
|
127
165
|
case hrp
|
|
128
166
|
when NIP19::HRP_PROFILE, NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
|
129
|
-
entries << TLVEntry.new(type,
|
|
167
|
+
entries << TLVEntry.new(type, value.force_encoding(::Encoding::UTF_8), 'relay')
|
|
130
168
|
else
|
|
131
169
|
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
|
132
170
|
end
|
|
133
171
|
when TYPE_AUTHOR # author
|
|
134
172
|
case hrp
|
|
135
173
|
when NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
|
136
|
-
entries << TLVEntry.new(type,
|
|
174
|
+
entries << TLVEntry.new(type, value.unpack1('H*'), 'author')
|
|
137
175
|
else
|
|
138
176
|
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
|
139
177
|
end
|
|
140
178
|
when TYPE_KIND # kind
|
|
141
179
|
case hrp
|
|
142
180
|
when NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
|
143
|
-
|
|
181
|
+
raise ArgumentError, 'Invalid TLV: kind must be 4 bytes.' unless value.bytesize == 4
|
|
182
|
+
entries << TLVEntry.new(type, value.unpack1('N'), 'kind')
|
|
144
183
|
else
|
|
145
184
|
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
|
146
185
|
end
|
|
@@ -155,7 +194,7 @@ module Bech32
|
|
|
155
194
|
# Encode tlv entity to bech32 string.
|
|
156
195
|
# @return [String] bech32 string.
|
|
157
196
|
def encode
|
|
158
|
-
data = entries.map(
|
|
197
|
+
data = entries.map { |e| e.to_payload(hrp) }.join
|
|
159
198
|
Bech32.encode(hrp, Bech32.convert_bits(data.unpack('C*'), 8, 5), Bech32::Encoding::BECH32)
|
|
160
199
|
end
|
|
161
200
|
end
|
data/lib/bech32/nostr/nip19.rb
CHANGED
|
@@ -28,7 +28,9 @@ module Bech32
|
|
|
28
28
|
raise ArgumentError, 'Invalid nip19 string.' if hrp.nil?
|
|
29
29
|
raise ArgumentError, 'Invalid bech32 spec.' unless spec == Bech32::Encoding::BECH32
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
converted = Bech32.convert_bits(data, 5, 8, false)
|
|
32
|
+
raise ArgumentError, 'Invalid data.' if converted.nil?
|
|
33
|
+
entity = converted.pack('C*')
|
|
32
34
|
raise ArgumentError, "Data whose HRP is #{hrp} must be 32 bytes." if BARE_PREFIXES.include?(hrp) && entity.bytesize != 32
|
|
33
35
|
if BARE_PREFIXES.include?(hrp)
|
|
34
36
|
BareEntity.new(hrp, entity.unpack1('H*'))
|
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
|
|
@@ -38,7 +42,9 @@ module Bech32
|
|
|
38
42
|
raise ArgumentError, "addr must be encoded with bech32m." unless spec == Bech32::Encoding::BECH32M
|
|
39
43
|
raise ArgumentError, 'Invalid hrp.' if hrp.nil? || data[0].nil? || ![HRP_MAINNET, HRP_TESTNET].include?(hrp)
|
|
40
44
|
version = data[0]
|
|
41
|
-
|
|
45
|
+
converted = Bech32.convert_bits(data[1..-1], 5, 8, false)
|
|
46
|
+
raise ArgumentError, "Invalid data." if converted.nil?
|
|
47
|
+
keys = converted.pack('C*').unpack1('H*')
|
|
42
48
|
raise ArgumentError, "Invalid key size." unless keys.length == 132
|
|
43
49
|
scan = keys[0...66]
|
|
44
50
|
spend = keys[66..-1]
|
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
|
|
@@ -50,7 +58,7 @@ module Bech32
|
|
|
50
58
|
bech = bech.downcase
|
|
51
59
|
# check data length
|
|
52
60
|
pos = bech.rindex(SEPARATOR)
|
|
53
|
-
return nil if pos.nil? || pos + 7 > bech.length || bech.length > max_length
|
|
61
|
+
return nil if pos.nil? || pos < 1 || pos + 7 > bech.length || bech.length > max_length
|
|
54
62
|
# check valid charset
|
|
55
63
|
bech[pos+1..-1].each_char{|c|return nil unless CHARSET.include?(c)}
|
|
56
64
|
# split hrp and data
|
|
@@ -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
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Bech32
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
module Encoding
|
|
5
|
+
BECH32: Integer
|
|
6
|
+
BECH32M: Integer
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
SEPARATOR: String
|
|
10
|
+
CHARSET: Array[String]
|
|
11
|
+
BECH32M_CONST: Integer
|
|
12
|
+
HRP_MAX_LENGTH: Integer
|
|
13
|
+
|
|
14
|
+
# Encode hrp and data to bech32/bech32m string.
|
|
15
|
+
def self.encode: (String hrp, Array[Integer] data, Integer spec) -> String
|
|
16
|
+
|
|
17
|
+
# Decode bech32/bech32m string. Returns nil if invalid.
|
|
18
|
+
def self.decode: (String bech, ?Integer max_length) -> [String, Array[Integer], Integer]?
|
|
19
|
+
|
|
20
|
+
# Convert bits from one base to another.
|
|
21
|
+
def self.convert_bits: (Array[Integer] data, Integer from, Integer to, ?bool padding) -> Array[Integer]?
|
|
22
|
+
|
|
23
|
+
# Create checksum for hrp and data.
|
|
24
|
+
def self.create_checksum: (String hrp, Array[Integer] data, Integer spec) -> Array[Integer]
|
|
25
|
+
|
|
26
|
+
# Verify checksum. Returns encoding spec if valid, nil otherwise.
|
|
27
|
+
def self.verify_checksum: (String hrp, Array[Integer] data) -> Integer?
|
|
28
|
+
|
|
29
|
+
# module_function methods are also available as instance methods
|
|
30
|
+
def encode: (String hrp, Array[Integer] data, Integer spec) -> String
|
|
31
|
+
def decode: (String bech, ?Integer max_length) -> [String, Array[Integer], Integer]?
|
|
32
|
+
def convert_bits: (Array[Integer] data, Integer from, Integer to, ?bool padding) -> Array[Integer]?
|
|
33
|
+
def create_checksum: (String hrp, Array[Integer] data, Integer spec) -> Array[Integer]
|
|
34
|
+
def verify_checksum: (String hrp, Array[Integer] data) -> Integer?
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Expand HRP for checksum computation.
|
|
39
|
+
def self.expand_hrp: (String hrp) -> Array[Integer]
|
|
40
|
+
|
|
41
|
+
# Compute polymod checksum.
|
|
42
|
+
def self.polymod: (Array[Integer] values) -> Integer
|
|
43
|
+
|
|
44
|
+
# Validate arguments of encode.
|
|
45
|
+
def self.validate_encode_args: (String hrp, Array[Integer] data) -> void
|
|
46
|
+
|
|
47
|
+
def expand_hrp: (String hrp) -> Array[Integer]
|
|
48
|
+
def polymod: (Array[Integer] values) -> Integer
|
|
49
|
+
def validate_encode_args: (String hrp, Array[Integer] data) -> void
|
|
50
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Bech32
|
|
2
|
+
module Nostr
|
|
3
|
+
class BareEntity
|
|
4
|
+
attr_reader hrp: String
|
|
5
|
+
attr_reader data: String
|
|
6
|
+
|
|
7
|
+
# Initialize bare entity.
|
|
8
|
+
def initialize: (String hrp, String data) -> void
|
|
9
|
+
|
|
10
|
+
# Encode to bech32 string.
|
|
11
|
+
def encode: () -> String
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class TLVEntry
|
|
15
|
+
MAX_VALUE_LENGTH: Integer
|
|
16
|
+
|
|
17
|
+
attr_reader type: Integer
|
|
18
|
+
attr_reader label: String?
|
|
19
|
+
attr_reader value: String | Integer
|
|
20
|
+
|
|
21
|
+
# Initialize TLV entry.
|
|
22
|
+
def initialize: (Integer type, String | Integer value, ?String? label) -> void
|
|
23
|
+
|
|
24
|
+
# Convert to binary payload.
|
|
25
|
+
def to_payload: (?String? hrp) -> String
|
|
26
|
+
|
|
27
|
+
# String representation.
|
|
28
|
+
def to_s: () -> String
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Check if the value of this entry is hex encoded.
|
|
33
|
+
def hex_value?: (String? hrp) -> bool
|
|
34
|
+
|
|
35
|
+
# Check if string is hex encoded.
|
|
36
|
+
def hex_string?: (String str) -> bool
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class TLVEntity
|
|
40
|
+
TYPE_SPECIAL: Integer
|
|
41
|
+
TYPE_RELAY: Integer
|
|
42
|
+
TYPE_AUTHOR: Integer
|
|
43
|
+
TYPE_KIND: Integer
|
|
44
|
+
TYPES: Array[Integer]
|
|
45
|
+
|
|
46
|
+
attr_reader hrp: String
|
|
47
|
+
attr_reader entries: Array[TLVEntry]
|
|
48
|
+
|
|
49
|
+
# Initialize TLV entity.
|
|
50
|
+
def initialize: (String hrp, Array[TLVEntry] entries) -> void
|
|
51
|
+
|
|
52
|
+
# Parse TLV entity from binary data.
|
|
53
|
+
def self.parse: (String hrp, String data) -> TLVEntity
|
|
54
|
+
|
|
55
|
+
# Encode to bech32 string.
|
|
56
|
+
def encode: () -> String
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Bech32
|
|
2
|
+
module Nostr
|
|
3
|
+
module NIP19
|
|
4
|
+
HRP_PUBKEY: String
|
|
5
|
+
HRP_PRIVATE_KEY: String
|
|
6
|
+
HRP_NOTE_ID: String
|
|
7
|
+
HRP_PROFILE: String
|
|
8
|
+
HRP_EVENT: String
|
|
9
|
+
HRP_RELAY: String
|
|
10
|
+
HRP_EVENT_COORDINATE: String
|
|
11
|
+
|
|
12
|
+
BARE_PREFIXES: Array[String]
|
|
13
|
+
TLV_PREFIXES: Array[String]
|
|
14
|
+
ALL_PREFIXES: Array[String]
|
|
15
|
+
|
|
16
|
+
# Decode NIP-19 bech32 string.
|
|
17
|
+
def self.decode: (String string) -> (BareEntity | TLVEntity)
|
|
18
|
+
|
|
19
|
+
# module_function methods are also available as instance methods
|
|
20
|
+
def decode: (String string) -> (BareEntity | TLVEntity)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Bech32
|
|
2
|
+
class SegwitAddr
|
|
3
|
+
HRP_MAINNET: String
|
|
4
|
+
HRP_TESTNET: String
|
|
5
|
+
HRP_REGTEST: String
|
|
6
|
+
|
|
7
|
+
attr_accessor hrp: String
|
|
8
|
+
attr_accessor ver: Integer?
|
|
9
|
+
attr_accessor prog: Array[Integer]?
|
|
10
|
+
|
|
11
|
+
# Initialize SegwitAddr. Optionally parse from address string.
|
|
12
|
+
def initialize: (?String? addr) -> void
|
|
13
|
+
|
|
14
|
+
# Generate script pubkey from witness version and program.
|
|
15
|
+
def to_script_pubkey: () -> String
|
|
16
|
+
|
|
17
|
+
# Parse script pubkey into witness version and program.
|
|
18
|
+
def script_pubkey=: (String script_pubkey) -> void
|
|
19
|
+
|
|
20
|
+
# Generate bech32/bech32m address string.
|
|
21
|
+
def addr: () -> String
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
# Parse address string into hrp, version and program.
|
|
26
|
+
def parse_addr: (String addr) -> void
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Bech32
|
|
2
|
+
# BIP-352 silent payment address.
|
|
3
|
+
class SilentPaymentAddr
|
|
4
|
+
HRP_MAINNET: String
|
|
5
|
+
HRP_TESTNET: String
|
|
6
|
+
MAX_VERSION: Integer
|
|
7
|
+
|
|
8
|
+
attr_reader hrp: String
|
|
9
|
+
attr_reader version: Integer
|
|
10
|
+
attr_reader scan_key: String
|
|
11
|
+
attr_reader spend_key: String
|
|
12
|
+
|
|
13
|
+
# Initialize SilentPaymentAddr.
|
|
14
|
+
def initialize: (String hrp, Integer version, String scan_key, String spend_key) -> void
|
|
15
|
+
|
|
16
|
+
# Parse silent payment address string.
|
|
17
|
+
def self.parse: (String addr) -> SilentPaymentAddr
|
|
18
|
+
|
|
19
|
+
# Generate silent payment address string.
|
|
20
|
+
def to_s: () -> String
|
|
21
|
+
end
|
|
22
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bech32
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shigeyuki Azuchi
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: thor
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- LICENSE.txt
|
|
70
70
|
- README.md
|
|
71
71
|
- Rakefile
|
|
72
|
+
- Steepfile
|
|
72
73
|
- bech32.gemspec
|
|
73
74
|
- bin/console
|
|
74
75
|
- bin/setup
|
|
@@ -80,10 +81,16 @@ files:
|
|
|
80
81
|
- lib/bech32/segwit_addr.rb
|
|
81
82
|
- lib/bech32/silent_payment_addr.rb
|
|
82
83
|
- lib/bech32/version.rb
|
|
84
|
+
- sig/bech32/bech32.rbs
|
|
85
|
+
- sig/bech32/nostr/entity.rbs
|
|
86
|
+
- sig/bech32/nostr/nip19.rbs
|
|
87
|
+
- sig/bech32/segwit_addr.rbs
|
|
88
|
+
- sig/bech32/silent_payment_addr.rbs
|
|
83
89
|
homepage: https://github.com/azuchi/bech32rb
|
|
84
90
|
licenses:
|
|
85
91
|
- MIT
|
|
86
|
-
metadata:
|
|
92
|
+
metadata:
|
|
93
|
+
rubygems_mfa_required: 'true'
|
|
87
94
|
rdoc_options: []
|
|
88
95
|
require_paths:
|
|
89
96
|
- lib
|
|
@@ -98,7 +105,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
98
105
|
- !ruby/object:Gem::Version
|
|
99
106
|
version: '0'
|
|
100
107
|
requirements: []
|
|
101
|
-
rubygems_version:
|
|
108
|
+
rubygems_version: 4.0.3
|
|
102
109
|
specification_version: 4
|
|
103
110
|
summary: The implementation of Bech32 encoder and decoder.
|
|
104
111
|
test_files: []
|