bech32 1.2.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b6e0eb4edcadc4cbeeb612e0d085bb3caed330ed1c312c1d8465baec26a4923
4
- data.tar.gz: ea2abc8eae17e2b503e091c71402a6f9dbd3f2be7ecd416eed3dcc08be3fccb6
3
+ metadata.gz: 1e52ce0d1cc8ab59a97a220fc50e134c3c70fcc5beebd83b86631f4a3c6fc39d
4
+ data.tar.gz: cbf75c9c25a349c048ee2ad3503d3db8f6631babccdbfb8a7608dd7de9a60ce0
5
5
  SHA512:
6
- metadata.gz: 39d972ba715a32e90cda977d9a6c9a53f5648e760c0afcaf9edd83e1a94ce882a09d0d38d2781a691def7d8595d270d1293f96ae63df22dd981dc23ddaaf6e40
7
- data.tar.gz: a463c2478f923f6b8b27123e5e1af24e0f6ebee5c0593fa02243311b1ca364db7d82ceea1375d9f5078e14cc3adf3bfa6eced482ea86045fa5ec1fc726fcbe47
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
@@ -18,7 +18,7 @@ module Bech32
18
18
  # Returns segwit script pubkey which generated from witness version and witness program.
19
19
  def to_script_pubkey
20
20
  v = ver == 0 ? ver : ver + 0x50
21
- ([v, prog.length].pack("CC") + prog.map{|p|[p].pack("C")}.join).unpack('H*').first
21
+ ([v, prog.length] + prog).pack('C*').unpack("H*").first
22
22
  end
23
23
 
24
24
  # parse script pubkey into witness version and witness program
@@ -31,7 +31,7 @@ module Bech32
31
31
  # Returns segwit address string which generated from hrp, witness version and witness program.
32
32
  def addr
33
33
  spec = (ver == 0 ? Bech32::Encoding::BECH32 : Bech32::Encoding::BECH32M)
34
- Bech32.encode(hrp, [ver] + convert_bits(prog, 8, 5), spec)
34
+ Bech32.encode(hrp, [ver] + Bech32.convert_bits(prog, 8, 5), spec)
35
35
  end
36
36
 
37
37
  private
@@ -41,34 +41,11 @@ module Bech32
41
41
  raise 'Invalid address.' if hrp.nil? || data[0].nil? || ![HRP_MAINNET, HRP_TESTNET, HRP_REGTEST].include?(hrp)
42
42
  @ver = data[0]
43
43
  raise 'Invalid witness version' if @ver > 16
44
- @prog = convert_bits(data[1..-1], 5, 8, false)
44
+ @prog = Bech32.convert_bits(data[1..-1], 5, 8, false)
45
45
  raise 'Invalid witness program' if @prog.nil? || @prog.length < 2 || @prog.length > 40
46
46
  raise 'Invalid witness program with version 0' if @ver == 0 && (@prog.length != 20 && @prog.length != 32)
47
47
  raise 'Witness version and encoding spec do not match' if (@ver == 0 && spec != Bech32::Encoding::BECH32) || (@ver != 0 && spec != Bech32::Encoding::BECH32M)
48
48
  end
49
49
 
50
- def convert_bits(data, from, to, padding=true)
51
- acc = 0
52
- bits = 0
53
- ret = []
54
- maxv = (1 << to) - 1
55
- max_acc = (1 << (from + to - 1)) - 1
56
- data.each do |v|
57
- return nil if v < 0 || (v >> from) != 0
58
- acc = ((acc << from) | v) & max_acc
59
- bits += from
60
- while bits >= to
61
- bits -= to
62
- ret << ((acc >> bits) & maxv)
63
- end
64
- end
65
- if padding
66
- ret << ((acc << (to - bits)) & maxv) unless bits == 0
67
- elsif bits >= from || ((acc << (to - bits)) & maxv) != 0
68
- return nil
69
- end
70
- ret
71
- end
72
-
73
50
  end
74
51
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bech32
4
- VERSION = '1.2.1'
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
 
@@ -86,6 +87,35 @@ module Bech32
86
87
  hrp.each_char.map{|c|c.ord >> 5} + [0] + hrp.each_char.map{|c|c.ord & 31}
87
88
  end
88
89
 
90
+ # Convert a +data+ where each byte is encoding +from+ bits to a byte slice where each byte is encoding +to+ bits.
91
+ # @param [Array] data
92
+ # @param [Integer] from
93
+ # @param [Integer] to
94
+ # @param [Boolean] padding
95
+ # @return [Array]
96
+ def convert_bits(data, from, to, padding=true)
97
+ acc = 0
98
+ bits = 0
99
+ ret = []
100
+ maxv = (1 << to) - 1
101
+ max_acc = (1 << (from + to - 1)) - 1
102
+ data.each do |v|
103
+ return nil if v < 0 || (v >> from) != 0
104
+ acc = ((acc << from) | v) & max_acc
105
+ bits += from
106
+ while bits >= to
107
+ bits -= to
108
+ ret << ((acc >> bits) & maxv)
109
+ end
110
+ end
111
+ if padding
112
+ ret << ((acc << (to - bits)) & maxv) unless bits == 0
113
+ elsif bits >= from || ((acc << (to - bits)) & maxv) != 0
114
+ return nil
115
+ end
116
+ ret
117
+ end
118
+
89
119
  # Compute Bech32 checksum
90
120
  def polymod(values)
91
121
  generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
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.2.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: 2021-08-02 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