bech32 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9dfad105c52c27a05e99b0576a46de13aac736914b8172b59766e393531857e
4
- data.tar.gz: bdadf23d8b3ea58fbbc6c303ca657e6d151d4096cbb6dfc260d105c2d5b5869e
3
+ metadata.gz: 1e52ce0d1cc8ab59a97a220fc50e134c3c70fcc5beebd83b86631f4a3c6fc39d
4
+ data.tar.gz: cbf75c9c25a349c048ee2ad3503d3db8f6631babccdbfb8a7608dd7de9a60ce0
5
5
  SHA512:
6
- metadata.gz: 17c8acca310a4ebc24ba5e4b4f58132d79262ca73d6ebbe6b50952b2f0703c70b915b28500d866af86d3b9f715afdc614ddc2af9feb07d6de5fb16fff68dc16e
7
- data.tar.gz: ae1ad3dd861bed9849878241ae9edac81d26980a4cf8a6e911ecb36b7bdc547756404347232aff5608d4b9f24d2b0f6ecfc0e6476c6f37855bfbc2ff0410a8e9
6
+ metadata.gz: 399a572833d9f651037a7e49c17917bf1e82521fac556c8cc47e14422ef96b200797124e95d51ac2ac760947fdf6b10c39485cbd1228529b72ad3b46e063f5f0
7
+ data.tar.gz: 6cd811fad6a9a6da5853230ff4501be650afc3a1efeb8208b098774f9bd83676e79ad7be433911774f3005b61054f43671623176bfec1a37835d5f24557e1755
@@ -19,15 +19,14 @@ jobs:
19
19
  runs-on: ubuntu-latest
20
20
  strategy:
21
21
  matrix:
22
- ruby-version: ['2.6', '2.7', '3.0']
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
- # uses: ruby/setup-ruby@v1
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 [![Build Status](https://travis-ci.org/azuchi/bech32rb.svg?branch=master)](https://travis-ci.org/azuchi/bech32rb) [![Gem Version](https://badge.fury.io/rb/bech32.svg)](https://badge.fury.io/rb/bech32) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE) <img src="http://segwit.co/static/public/images/logo.png" width="100">
1
+ # Bech32 [![Build Status](https://github.com/azuchi/bech32rb/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/azuchi/bech32rb/actions/workflows/main.yml) [![Gem Version](https://badge.fury.io/rb/bech32.svg)](https://badge.fury.io/rb/bech32) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](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
@@ -0,0 +1,8 @@
1
+ module Bech32
2
+ module Nostr
3
+ autoload :NIP19, 'bech32/nostr/nip19'
4
+ autoload :BareEntity, 'bech32/nostr/entity'
5
+ autoload :TLVEntity, 'bech32/nostr/entity'
6
+ end
7
+
8
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bech32
4
- VERSION = '1.3.0'
4
+ VERSION = '1.4.0'
5
5
  end
data/lib/bech32.rb CHANGED
@@ -6,6 +6,7 @@ module Bech32
6
6
  end
7
7
 
8
8
  autoload :SegwitAddr, 'bech32/segwit_addr'
9
+ autoload :Nostr, 'bech32/nostr'
9
10
 
10
11
  SEPARATOR = '1'
11
12
 
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.3.0
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: 2022-10-28 00:00:00.000000000 Z
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