bech32 1.3.0 → 1.4.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 +2 -3
- data/README.md +52 -14
- data/lib/bech32/nostr/entity.rb +147 -0
- data/lib/bech32/nostr/nip19.rb +41 -0
- data/lib/bech32/nostr.rb +8 -0
- data/lib/bech32/version.rb +1 -1
- data/lib/bech32.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e52ce0d1cc8ab59a97a220fc50e134c3c70fcc5beebd83b86631f4a3c6fc39d
|
4
|
+
data.tar.gz: cbf75c9c25a349c048ee2ad3503d3db8f6631babccdbfb8a7608dd7de9a60ce0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 399a572833d9f651037a7e49c17917bf1e82521fac556c8cc47e14422ef96b200797124e95d51ac2ac760947fdf6b10c39485cbd1228529b72ad3b46e063f5f0
|
7
|
+
data.tar.gz: 6cd811fad6a9a6da5853230ff4501be650afc3a1efeb8208b098774f9bd83676e79ad7be433911774f3005b61054f43671623176bfec1a37835d5f24557e1755
|
data/.github/workflows/main.yml
CHANGED
@@ -19,15 +19,14 @@ jobs:
|
|
19
19
|
runs-on: ubuntu-latest
|
20
20
|
strategy:
|
21
21
|
matrix:
|
22
|
-
ruby-version: ['
|
22
|
+
ruby-version: ['3.0', '3.1', '3.2']
|
23
23
|
|
24
24
|
steps:
|
25
25
|
- uses: actions/checkout@v2
|
26
26
|
- name: Set up Ruby
|
27
27
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
28
28
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
29
|
-
|
30
|
-
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
29
|
+
uses: ruby/setup-ruby@v1
|
31
30
|
with:
|
32
31
|
ruby-version: ${{ matrix.ruby-version }}
|
33
32
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Bech32 [](https://github.com/azuchi/bech32rb/actions/workflows/main.yml) [](https://badge.fury.io/rb/bech32) [](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
|
2
2
|
|
3
3
|
The implementation of the Bech32/Bech32m encoder and decoder for Ruby.
|
4
4
|
|
@@ -50,17 +50,6 @@ hrp, data, spec = Bech32.decode('BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4')
|
|
50
50
|
# spec is whether Bech32::Encoding::BECH32 or Bech32::Encoding::BECH32M
|
51
51
|
```
|
52
52
|
|
53
|
-
Decode Bech32-encoded Segwit address into `Bech32::SegwitAddr` instance.
|
54
|
-
|
55
|
-
```ruby
|
56
|
-
addr = 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'
|
57
|
-
segwit_addr = Bech32::SegwitAddr.new(addr)
|
58
|
-
|
59
|
-
# generate script pubkey
|
60
|
-
segwit_addr.to_script_pubkey
|
61
|
-
=> 0014751e76e8199196d454941c45d1b3a323f1433bd6
|
62
|
-
```
|
63
|
-
|
64
53
|
#### Advanced
|
65
54
|
|
66
55
|
The maximum number of characters of Bech32 defined in [BIP-173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki) is limited to 90 characters.
|
@@ -84,7 +73,20 @@ hrp = 'bc'
|
|
84
73
|
data = [0, 14, 20, 15, 7, 13, 26, 0, 25, 18, 6, 11, 13, 8, 21, 4, 20, 3, 17, 2, 29, 3, 12, 29, 3, 4, 15, 24, 20, 6, 14, 30, 22]
|
85
74
|
|
86
75
|
bech = Bech32.encode(hrp, data, Bech32::Encoding::BECH32)
|
87
|
-
=> bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
|
76
|
+
=> 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
|
77
|
+
```
|
78
|
+
|
79
|
+
### Segwit
|
80
|
+
|
81
|
+
Decode Bech32-encoded Segwit address into `Bech32::SegwitAddr` instance.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
addr = 'BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4'
|
85
|
+
segwit_addr = Bech32::SegwitAddr.new(addr)
|
86
|
+
|
87
|
+
# generate script pubkey
|
88
|
+
segwit_addr.to_script_pubkey
|
89
|
+
=> '0014751e76e8199196d454941c45d1b3a323f1433bd6'
|
88
90
|
```
|
89
91
|
|
90
92
|
Encode Segwit script into Bech32 Segwit address.
|
@@ -95,7 +97,43 @@ segwit_addr.script_pubkey = '0014751e76e8199196d454941c45d1b3a323f1433bd6'
|
|
95
97
|
|
96
98
|
# generate addr
|
97
99
|
segwit_addr.addr
|
98
|
-
=> bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4
|
100
|
+
=> 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
|
101
|
+
```
|
102
|
+
|
103
|
+
### Nostr
|
104
|
+
|
105
|
+
Supports encoding/decoding of Nostr's [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) entities.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# Decode bare entity
|
109
|
+
bech32 = 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
|
110
|
+
entity = Bech32::Nostr::NIP19.parse(bech32)
|
111
|
+
entity.hrp
|
112
|
+
=> 'npub'
|
113
|
+
entity.data
|
114
|
+
=> '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
|
115
|
+
|
116
|
+
# Decode tlv entity
|
117
|
+
bech32 = 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'
|
118
|
+
entity = Bech32::Nostr::NIP19.parse(bech32)
|
119
|
+
entity.hrp
|
120
|
+
=> 'nprofile'
|
121
|
+
entity.entries[0].value
|
122
|
+
=> '3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'
|
123
|
+
entity.entries[1].value
|
124
|
+
=> 'wss://r.x.com'
|
125
|
+
|
126
|
+
# Encode bare entity
|
127
|
+
entity = Bech32::Nostr::BareEntity.new('npub', '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
|
128
|
+
entity.encode
|
129
|
+
=> 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
|
130
|
+
|
131
|
+
# Encode tlv entity
|
132
|
+
entry_relay = Bech32::Nostr::TLVEntry.new(Bech32::Nostr::TLVEntity::TYPE_RELAY, 'wss://relay.nostr.example')
|
133
|
+
entry_author = Bech32::Nostr::TLVEntry.new(Bech32::Nostr::TLVEntity::TYPE_AUTHOR, '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322')
|
134
|
+
entity = Bech32::Nostr::TLVEntity.new(Bech32::Nostr::NIP19::HRP_EVENT, [entry_relay, entry_author])
|
135
|
+
entity.encode
|
136
|
+
=> 'nevent1qyvhwumn8ghj7un9d3shjtnwdaehgu3wv4uxzmtsd3jsygyhcu9ygdn2v56uz3dnx0uh865xmlwz675emfsccsxxguz6mx8rygstv78u'
|
99
137
|
```
|
100
138
|
|
101
139
|
### CLI
|
@@ -0,0 +1,147 @@
|
|
1
|
+
module Bech32
|
2
|
+
module Nostr
|
3
|
+
class BareEntity
|
4
|
+
attr_reader :hrp
|
5
|
+
attr_reader :data
|
6
|
+
|
7
|
+
# Initialize bare entity.
|
8
|
+
# @param [String] hrp human-readable part.
|
9
|
+
# @param [String] data Entity data(hex string).
|
10
|
+
def initialize(hrp, data)
|
11
|
+
raise ArgumentError, "HRP #{hrp} is unsupported." unless NIP19::BARE_PREFIXES.include?(hrp)
|
12
|
+
raise ArgumentError, "Data whose HRP is #{hrp} must be 32 bytes." unless [data].pack('H*').bytesize == 32
|
13
|
+
@hrp = hrp
|
14
|
+
@data = data
|
15
|
+
end
|
16
|
+
|
17
|
+
# Encode bare entity to bech32 string.
|
18
|
+
# @return [String] bech32 string.
|
19
|
+
def encode
|
20
|
+
Bech32.encode(hrp, Bech32.convert_bits([data].pack('H*').unpack('C*'), 8, 5), Bech32::Encoding::BECH32)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TLVEntry
|
25
|
+
|
26
|
+
attr_reader :type
|
27
|
+
attr_reader :label
|
28
|
+
attr_reader :value
|
29
|
+
|
30
|
+
def initialize(type, value, label = nil)
|
31
|
+
raise ArgumentError, "Type #{type} unsupported." unless TLVEntity::TYPES.include?(type)
|
32
|
+
|
33
|
+
@type = type
|
34
|
+
@value = value
|
35
|
+
@label = label
|
36
|
+
end
|
37
|
+
|
38
|
+
# Convert to binary data.
|
39
|
+
# @return [String] binary data.
|
40
|
+
def to_payload
|
41
|
+
data = if value.is_a?(Integer)
|
42
|
+
[value].pack('N')
|
43
|
+
else
|
44
|
+
hex_string?(value) ? [value].pack('H*') : value
|
45
|
+
end
|
46
|
+
len = data.bytesize
|
47
|
+
[type, len].pack('CC') + data
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Check whether +str+ is hex string or not.
|
53
|
+
# @param [String] str string.
|
54
|
+
# @return [Boolean]
|
55
|
+
def hex_string?(str)
|
56
|
+
return false if str.bytes.any? { |b| b > 127 }
|
57
|
+
return false if str.length % 2 != 0
|
58
|
+
hex_chars = str.chars.to_a
|
59
|
+
hex_chars.all? { |c| c =~ /[0-9a-fA-F]/ }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class TLVEntity
|
64
|
+
|
65
|
+
TYPE_SPECIAL = 0
|
66
|
+
TYPE_RELAY = 1
|
67
|
+
TYPE_AUTHOR = 2
|
68
|
+
TYPE_KIND = 3
|
69
|
+
|
70
|
+
TYPES = [TYPE_SPECIAL, TYPE_RELAY, TYPE_AUTHOR, TYPE_KIND]
|
71
|
+
|
72
|
+
attr_reader :hrp
|
73
|
+
attr_reader :entries
|
74
|
+
|
75
|
+
# Initialize TLV entity.
|
76
|
+
# @param [String] hrp human-readable part.
|
77
|
+
# @param [Array<TLVEntry>] entries TLV entries.
|
78
|
+
# @return [TLVEntity]
|
79
|
+
def initialize(hrp, entries)
|
80
|
+
raise ArgumentError, "HRP #{hrp} is unsupported." unless NIP19::TLV_PREFIXES.include?(hrp)
|
81
|
+
entries.each do |e|
|
82
|
+
raise ArgumentError, "Entries must be TLVEntry. #{e.class} given." unless e.is_a?(TLVEntry)
|
83
|
+
end
|
84
|
+
|
85
|
+
@hrp = hrp
|
86
|
+
@entries = entries
|
87
|
+
end
|
88
|
+
|
89
|
+
# Parse TLV entity from data.
|
90
|
+
# @param [String] hrp human-readable part.
|
91
|
+
# @param [String] data Entity data(binary format).
|
92
|
+
# @return [TLVEntity]
|
93
|
+
def self.parse(hrp, data)
|
94
|
+
buf = StringIO.new(data)
|
95
|
+
entries = []
|
96
|
+
until buf.eof?
|
97
|
+
type, len = buf.read(2).unpack('CC')
|
98
|
+
case type
|
99
|
+
when TYPE_SPECIAL # special
|
100
|
+
case hrp
|
101
|
+
when NIP19::HRP_PROFILE
|
102
|
+
entries << TLVEntry.new(type, buf.read(len).unpack1('H*'), 'pubkey')
|
103
|
+
when NIP19::HRP_RELAY
|
104
|
+
entries << TLVEntry.new(type, buf.read(len), 'relay')
|
105
|
+
when NIP19::HRP_EVENT
|
106
|
+
entries << TLVEntry.new(type, buf.read(len).unpack1('H*'), 'id')
|
107
|
+
when NIP19::HRP_EVENT_COORDINATE
|
108
|
+
entries << TLVEntry.new(type, buf.read(len), 'identifier')
|
109
|
+
end
|
110
|
+
when TYPE_RELAY # relay
|
111
|
+
case hrp
|
112
|
+
when NIP19::HRP_PROFILE, NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
113
|
+
entries << TLVEntry.new(type, buf.read(len), 'relay')
|
114
|
+
else
|
115
|
+
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
116
|
+
end
|
117
|
+
when TYPE_AUTHOR # author
|
118
|
+
case hrp
|
119
|
+
when NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
120
|
+
entries << TLVEntry.new(type, buf.read(len).unpack1('H*'), 'author')
|
121
|
+
else
|
122
|
+
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
123
|
+
end
|
124
|
+
when TYPE_KIND # kind
|
125
|
+
case hrp
|
126
|
+
when NIP19::HRP_EVENT, NIP19::HRP_EVENT_COORDINATE
|
127
|
+
entries << TLVEntry.new(type, buf.read(len).unpack1('H*').to_i(16), 'kind')
|
128
|
+
else
|
129
|
+
raise ArgumentError, "Type: #{type} does not supported for HRP: #{hrp}"
|
130
|
+
end
|
131
|
+
else
|
132
|
+
raise ArgumentError, "Unknown TLV type: #{type}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
TLVEntity.new(hrp, entries)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Encode tlv entity to bech32 string.
|
140
|
+
# @return [String] bech32 string.
|
141
|
+
def encode
|
142
|
+
data = entries.map(&:to_payload).join
|
143
|
+
Bech32.encode(hrp, Bech32.convert_bits(data.unpack('C*'), 8, 5), Bech32::Encoding::BECH32)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Bech32
|
2
|
+
module Nostr
|
3
|
+
module NIP19
|
4
|
+
|
5
|
+
HRP_PUBKEY = 'npub'
|
6
|
+
HRP_PRIVATE_KEY = 'nsec'
|
7
|
+
HRP_NOTE_ID = 'note'
|
8
|
+
HRP_PROFILE = 'nprofile'
|
9
|
+
HRP_EVENT = 'nevent'
|
10
|
+
HRP_RELAY = 'nrelay'
|
11
|
+
HRP_EVENT_COORDINATE = 'naddr'
|
12
|
+
|
13
|
+
BARE_PREFIXES = [HRP_PUBKEY, HRP_PRIVATE_KEY, HRP_NOTE_ID]
|
14
|
+
TLV_PREFIXES = [HRP_PROFILE, HRP_EVENT, HRP_RELAY, HRP_EVENT_COORDINATE]
|
15
|
+
ALL_PREFIXES = BARE_PREFIXES + TLV_PREFIXES
|
16
|
+
|
17
|
+
module_function
|
18
|
+
|
19
|
+
# Decode nip19 string.
|
20
|
+
# @param [String] string Bech32 string.
|
21
|
+
# @return [BareEntity | TLVEntity]
|
22
|
+
def decode(string)
|
23
|
+
hrp, data, spec = Bech32.decode(string, string.length)
|
24
|
+
|
25
|
+
raise ArgumentError, 'Invalid nip19 string.' if hrp.nil?
|
26
|
+
raise ArgumentError, 'Invalid bech32 spec.' unless spec == Bech32::Encoding::BECH32
|
27
|
+
|
28
|
+
entity = Bech32.convert_bits(data, 5, 8, false).pack('C*')
|
29
|
+
raise ArgumentError, "Data whose HRP is #{hrp} must be 32 bytes." if BARE_PREFIXES.include?(hrp) && entity.bytesize != 32
|
30
|
+
if BARE_PREFIXES.include?(hrp)
|
31
|
+
BareEntity.new(hrp, entity.unpack1('H*'))
|
32
|
+
elsif TLV_PREFIXES.include?(hrp)
|
33
|
+
TLVEntity.parse(hrp, entity)
|
34
|
+
else
|
35
|
+
raise ArgumentError, "HRP #{hrp} is unsupported."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/bech32/nostr.rb
ADDED
data/lib/bech32/version.rb
CHANGED
data/lib/bech32.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bech32
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shigeyuki Azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -75,6 +75,9 @@ files:
|
|
75
75
|
- bin/setup
|
76
76
|
- exe/bech32
|
77
77
|
- lib/bech32.rb
|
78
|
+
- lib/bech32/nostr.rb
|
79
|
+
- lib/bech32/nostr/entity.rb
|
80
|
+
- lib/bech32/nostr/nip19.rb
|
78
81
|
- lib/bech32/segwit_addr.rb
|
79
82
|
- lib/bech32/version.rb
|
80
83
|
homepage: https://github.com/azuchi/bech32rb
|