nostr_ruby 0.2.0 → 0.3.1
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/Gemfile.lock +10 -8
- data/README.md +286 -161
- data/lib/bech32.rb +84 -0
- data/lib/client.rb +184 -0
- data/lib/context.rb +48 -0
- data/lib/event.rb +188 -0
- data/lib/event_wizard.rb +31 -0
- data/lib/filter.rb +57 -0
- data/lib/key.rb +15 -0
- data/lib/kind.rb +10 -0
- data/lib/message_handler.rb +107 -0
- data/lib/nostr_ruby.rb +13 -274
- data/lib/signer.rb +89 -0
- data/lib/version.rb +5 -0
- data/nostr_ruby.gemspec +8 -8
- metadata +74 -29
- data/lib/custom_addr.rb +0 -59
- data/lib/nostr_ruby/version.rb +0 -3
data/lib/nostr_ruby.rb
CHANGED
|
@@ -1,284 +1,23 @@
|
|
|
1
|
-
require_relative '
|
|
1
|
+
require_relative 'version'
|
|
2
2
|
require_relative 'crypto_tools'
|
|
3
|
+
|
|
3
4
|
require 'ecdsa'
|
|
4
5
|
require 'schnorr'
|
|
5
6
|
require 'json'
|
|
6
7
|
require 'base64'
|
|
7
8
|
require 'bech32'
|
|
8
9
|
require 'unicode/emoji'
|
|
9
|
-
require 'websocket-client-simple'
|
|
10
|
-
|
|
11
|
-
# * Ruby library to interact with the Nostr protocol
|
|
12
|
-
|
|
13
|
-
class Nostr
|
|
14
|
-
include CryptoTools
|
|
15
|
-
|
|
16
|
-
attr_reader :private_key, :public_key, :pow_difficulty_target, :nip26_delegation_tag
|
|
17
|
-
|
|
18
|
-
def initialize(key)
|
|
19
|
-
hex_private_key = if key[:private_key]&.include?('nsec')
|
|
20
|
-
Nostr.to_hex(key[:private_key])
|
|
21
|
-
else
|
|
22
|
-
key[:private_key]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
hex_public_key = if key[:public_key]&.include?('npub')
|
|
26
|
-
Nostr.to_hex(key[:public_key])
|
|
27
|
-
else
|
|
28
|
-
key[:public_key]
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
if hex_private_key
|
|
32
|
-
@private_key = hex_private_key
|
|
33
|
-
group = ECDSA::Group::Secp256k1
|
|
34
|
-
@public_key = group.generator.multiply_by_scalar(private_key.to_i(16)).x.to_s(16).rjust(64, '0')
|
|
35
|
-
elsif hex_public_key
|
|
36
|
-
@public_key = hex_public_key
|
|
37
|
-
else
|
|
38
|
-
raise 'Missing private or public key'
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def keys
|
|
43
|
-
keys = { public_key: @public_key }
|
|
44
|
-
keys[:private_key] = @private_key if @private_key
|
|
45
|
-
keys
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def bech32_keys
|
|
49
|
-
bech32_keys = { public_key: Nostr.to_bech32(@public_key, 'npub') }
|
|
50
|
-
bech32_keys[:private_key] = Nostr.to_bech32(@private_key, 'nsec') if @private_key
|
|
51
|
-
bech32_keys
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def self.to_hex(bech32_key)
|
|
55
|
-
public_addr = CustomAddr.new(bech32_key)
|
|
56
|
-
public_addr.to_scriptpubkey
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def self.to_bech32(hex_key, hrp)
|
|
60
|
-
custom_addr = CustomAddr.new
|
|
61
|
-
custom_addr.scriptpubkey = hex_key
|
|
62
|
-
custom_addr.hrp = hrp
|
|
63
|
-
custom_addr.addr
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def sign_event(event)
|
|
67
|
-
raise 'Invalid pubkey' unless event[:pubkey].is_a?(String) && event[:pubkey].size == 64
|
|
68
|
-
raise 'Invalid created_at' unless event[:created_at].is_a?(Integer)
|
|
69
|
-
raise 'Invalid kind' unless (0..29_999).include?(event[:kind])
|
|
70
|
-
raise 'Invalid tags' unless event[:tags].is_a?(Array)
|
|
71
|
-
raise 'Invalid content' unless event[:content].is_a?(String)
|
|
72
|
-
|
|
73
|
-
serialized_event = [
|
|
74
|
-
0,
|
|
75
|
-
event[:pubkey],
|
|
76
|
-
event[:created_at],
|
|
77
|
-
event[:kind],
|
|
78
|
-
event[:tags],
|
|
79
|
-
event[:content]
|
|
80
|
-
]
|
|
81
|
-
|
|
82
|
-
serialized_event_sha256 = nil
|
|
83
|
-
if @pow_difficulty_target
|
|
84
|
-
nonce = 1
|
|
85
|
-
loop do
|
|
86
|
-
nonce_tag = ['nonce', nonce.to_s, @pow_difficulty_target.to_s]
|
|
87
|
-
nonced_serialized_event = serialized_event.clone
|
|
88
|
-
nonced_serialized_event[4] = nonced_serialized_event[4] + [nonce_tag]
|
|
89
|
-
serialized_event_sha256 = Digest::SHA256.hexdigest(JSON.dump(nonced_serialized_event))
|
|
90
|
-
if match_pow_difficulty?(serialized_event_sha256)
|
|
91
|
-
event[:tags] << nonce_tag
|
|
92
|
-
break
|
|
93
|
-
end
|
|
94
|
-
nonce += 1
|
|
95
|
-
end
|
|
96
|
-
else
|
|
97
|
-
serialized_event_sha256 = Digest::SHA256.hexdigest(JSON.dump(serialized_event))
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
private_key = Array(@private_key).pack('H*')
|
|
101
|
-
message = Array(serialized_event_sha256).pack('H*')
|
|
102
|
-
event_signature = Schnorr.sign(message, private_key).encode.unpack('H*')[0]
|
|
103
|
-
|
|
104
|
-
event['id'] = serialized_event_sha256
|
|
105
|
-
event['sig'] = event_signature
|
|
106
|
-
event
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def build_event(payload)
|
|
110
|
-
if @nip26_delegation_tag
|
|
111
|
-
payload[:tags] = [] unless payload[:tags]
|
|
112
|
-
payload[:tags] << @nip26_delegation_tag
|
|
113
|
-
end
|
|
114
|
-
event = sign_event(payload)
|
|
115
|
-
['EVENT', event]
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def build_metadata_event(name, about, picture, nip05)
|
|
119
|
-
data = {}
|
|
120
|
-
data[:name] = name if name
|
|
121
|
-
data[:about] = about if about
|
|
122
|
-
data[:picture] = picture if picture
|
|
123
|
-
data[:nip05] = nip05 if nip05
|
|
124
|
-
event = {
|
|
125
|
-
"pubkey": @public_key,
|
|
126
|
-
"created_at": Time.now.utc.to_i,
|
|
127
|
-
"kind": 0,
|
|
128
|
-
"tags": [],
|
|
129
|
-
"content": data.to_json
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
build_event(event)
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def build_note_event(text, channel_key = nil)
|
|
136
|
-
event = {
|
|
137
|
-
"pubkey": @public_key,
|
|
138
|
-
"created_at": Time.now.utc.to_i,
|
|
139
|
-
"kind": channel_key ? 42 : 1,
|
|
140
|
-
"tags": channel_key ? [['e', channel_key]] : [],
|
|
141
|
-
"content": text
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
build_event(event)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def build_recommended_relay_event(relay)
|
|
148
|
-
raise 'Invalid relay' unless relay.start_with?('wss://') || relay.start_with?('ws://')
|
|
149
|
-
|
|
150
|
-
event = {
|
|
151
|
-
"pubkey": @public_key,
|
|
152
|
-
"created_at": Time.now.utc.to_i,
|
|
153
|
-
"kind": 2,
|
|
154
|
-
"tags": [],
|
|
155
|
-
"content": relay
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
build_event(event)
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def build_contact_list_event(contacts)
|
|
162
|
-
event = {
|
|
163
|
-
"pubkey": @public_key,
|
|
164
|
-
"created_at": Time.now.utc.to_i,
|
|
165
|
-
"kind": 3,
|
|
166
|
-
"tags": contacts.map { |c| ['p'] + c },
|
|
167
|
-
"content": ''
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
build_event(event)
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def build_dm_event(text, recipient_public_key)
|
|
174
|
-
encrypted_text = CryptoTools.aes_256_cbc_encrypt(@private_key, recipient_public_key, text)
|
|
175
|
-
|
|
176
|
-
event = {
|
|
177
|
-
"pubkey": @public_key,
|
|
178
|
-
"created_at": Time.now.utc.to_i,
|
|
179
|
-
"kind": 4,
|
|
180
|
-
"tags": [['p', recipient_public_key]],
|
|
181
|
-
"content": encrypted_text
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
build_event(event)
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
def build_deletion_event(events, reason = '')
|
|
188
|
-
event = {
|
|
189
|
-
"pubkey": @public_key,
|
|
190
|
-
"created_at": Time.now.utc.to_i,
|
|
191
|
-
"kind": 5,
|
|
192
|
-
"tags": events.map{ |e| ['e', e] },
|
|
193
|
-
"content": reason
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
build_event(event)
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def build_reaction_event(reaction, event, author)
|
|
200
|
-
raise 'Invalid reaction' unless ['+', '-'].include?(reaction) || reaction.match?(Unicode::Emoji::REGEX)
|
|
201
|
-
raise 'Invalid author' unless event.is_a?(String) && event.size == 64
|
|
202
|
-
raise 'Invalid event' unless author.is_a?(String) && author.size == 64
|
|
203
|
-
|
|
204
|
-
event = {
|
|
205
|
-
"pubkey": @public_key,
|
|
206
|
-
"created_at": Time.now.utc.to_i,
|
|
207
|
-
"kind": 7,
|
|
208
|
-
"tags": [['e', event], ['p', author]],
|
|
209
|
-
"content": reaction
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
build_event(event)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def decrypt_dm(event)
|
|
216
|
-
data = event[1]
|
|
217
|
-
sender_public_key = data[:pubkey] != @public_key ? data[:pubkey] : data[:tags][0][1]
|
|
218
|
-
encrypted = data[:content].split('?iv=')[0]
|
|
219
|
-
iv = data[:content].split('?iv=')[1]
|
|
220
|
-
CryptoTools.aes_256_cbc_decrypt(@private_key, sender_public_key, encrypted, iv)
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def get_delegation_tag(delegatee_pubkey, conditions)
|
|
224
|
-
delegation_message_sha256 = Digest::SHA256.hexdigest("nostr:delegation:#{delegatee_pubkey}:#{conditions}")
|
|
225
|
-
signature = Schnorr.sign(Array(delegation_message_sha256).pack('H*'), Array(@private_key).pack('H*')).encode.unpack('H*')[0]
|
|
226
|
-
[
|
|
227
|
-
"delegation",
|
|
228
|
-
@public_key,
|
|
229
|
-
conditions,
|
|
230
|
-
signature
|
|
231
|
-
]
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
def set_delegation(tag)
|
|
235
|
-
@nip26_delegation_tag = tag
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def reset_delegation
|
|
239
|
-
@nip26_delegation_tag = nil
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def self.verify_delegation_signature(delegatee_pubkey, tag)
|
|
243
|
-
delegation_message_sha256 = Digest::SHA256.hexdigest("nostr:delegation:#{delegatee_pubkey}:#{tag[2]}")
|
|
244
|
-
Schnorr.valid_sig?(Array(delegation_message_sha256).pack('H*'), Array(tag[1]).pack('H*'), Array(tag[3]).pack('H*'))
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
def build_req_event(filters)
|
|
248
|
-
['REQ', SecureRandom.random_number.to_s, filters]
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
def build_close_event(subscription_id)
|
|
252
|
-
['CLOSE', subscription_id]
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
def build_notice_event(message)
|
|
256
|
-
['NOTICE', message]
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
def match_pow_difficulty?(event_id)
|
|
260
|
-
@pow_difficulty_target.nil? || @pow_difficulty_target == [event_id].pack("H*").unpack("B*")[0].index('1')
|
|
261
|
-
end
|
|
262
10
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
11
|
+
require_relative 'bech32'
|
|
12
|
+
require_relative 'context'
|
|
13
|
+
require_relative 'kind'
|
|
14
|
+
require_relative 'key'
|
|
15
|
+
require_relative 'event'
|
|
16
|
+
require_relative 'filter'
|
|
17
|
+
require_relative 'signer'
|
|
18
|
+
require_relative 'client'
|
|
19
|
+
require_relative 'message_handler'
|
|
266
20
|
|
|
267
|
-
|
|
268
|
-
response = nil
|
|
269
|
-
ws = WebSocket::Client::Simple.connect relay
|
|
270
|
-
ws.on :message do |msg|
|
|
271
|
-
puts msg
|
|
272
|
-
response = JSON.parse(msg.data)
|
|
273
|
-
ws.close
|
|
274
|
-
end
|
|
275
|
-
ws.on :open do
|
|
276
|
-
ws.send event.to_json
|
|
277
|
-
end
|
|
278
|
-
while response.nil? do
|
|
279
|
-
sleep 0.1
|
|
280
|
-
end
|
|
281
|
-
response[0] == 'OK'
|
|
282
|
-
end
|
|
21
|
+
module Nostr
|
|
283
22
|
|
|
284
|
-
end
|
|
23
|
+
end
|
data/lib/signer.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Nostr
|
|
2
|
+
class Signer
|
|
3
|
+
|
|
4
|
+
attr_reader :private_key
|
|
5
|
+
attr_reader :public_key
|
|
6
|
+
|
|
7
|
+
def initialize(private_key:)
|
|
8
|
+
@private_key = private_key
|
|
9
|
+
unless @public_key
|
|
10
|
+
@public_key = Nostr::Key::get_public_key(@private_key)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def nsec
|
|
15
|
+
Nostr::Bech32.encode_nsec(@private_key)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def npub
|
|
19
|
+
Nostr::Bech32.encode_npub(@public_key)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def sign(event)
|
|
23
|
+
|
|
24
|
+
raise ArgumentError, "Event is not signable" unless event.signable?
|
|
25
|
+
|
|
26
|
+
event.pubkey = @public_key if event.pubkey.nil? || event.pubkey.empty?
|
|
27
|
+
|
|
28
|
+
raise ArgumentError, "Pubkey doesn't match the private key" unless event.pubkey == @public_key
|
|
29
|
+
|
|
30
|
+
if event.kind == Nostr::Kind::DIRECT_MESSAGE
|
|
31
|
+
dm_recipient = event.tags.select{|t| t[0] == "p"}.first[1]
|
|
32
|
+
event.content = CryptoTools.aes_256_cbc_encrypt(@private_key, dm_recipient, event.content)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if event.delegation
|
|
36
|
+
event.tags << event.delegation
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
event_sha256_digest = nil
|
|
40
|
+
if event.pow
|
|
41
|
+
nonce = 1
|
|
42
|
+
loop do
|
|
43
|
+
nonce_tag = ['nonce', nonce.to_s, event.pow.to_s]
|
|
44
|
+
nonced_serialized_event = event.serialize.clone
|
|
45
|
+
nonced_serialized_event[4] = nonced_serialized_event[4] + [nonce_tag]
|
|
46
|
+
event_sha256_digest = Digest::SHA256.hexdigest(JSON.dump(nonced_serialized_event))
|
|
47
|
+
if Nostr::Event.match_pow_difficulty?(event_sha256_digest, event.pow)
|
|
48
|
+
event.tags << nonce_tag
|
|
49
|
+
break
|
|
50
|
+
end
|
|
51
|
+
nonce += 1
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
event_sha256_digest = Digest::SHA256.hexdigest(JSON.dump(event.serialize))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
event.id = event_sha256_digest
|
|
58
|
+
binary_private_key = Array(@private_key).pack('H*')
|
|
59
|
+
binary_message = Array(event.id).pack('H*')
|
|
60
|
+
event.sig = Schnorr.sign(binary_message, binary_private_key).encode.unpack('H*')[0]
|
|
61
|
+
event
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def decrypt(event)
|
|
65
|
+
case event.kind
|
|
66
|
+
when Nostr::Kind::DIRECT_MESSAGE
|
|
67
|
+
data = event.content.split('?iv=')[0]
|
|
68
|
+
iv = event.content.split('?iv=')[1]
|
|
69
|
+
dm_recipient = event.tags.select{|t| t[0] == "p"}.first[1]
|
|
70
|
+
event.content = CryptoTools.aes_256_cbc_decrypt(@private_key, dm_recipient, data, iv)
|
|
71
|
+
event
|
|
72
|
+
else
|
|
73
|
+
raise "Unable to decrypt a kind #{event.kind} event"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def generate_delegation_tag(to:, conditions:)
|
|
78
|
+
delegation_message_sha256 = Digest::SHA256.hexdigest("nostr:delegation:#{to}:#{conditions}")
|
|
79
|
+
signature = Schnorr.sign(Array(delegation_message_sha256).pack('H*'), Array(@private_key).pack('H*')).encode.unpack('H*')[0]
|
|
80
|
+
[
|
|
81
|
+
"delegation",
|
|
82
|
+
@public_key,
|
|
83
|
+
conditions,
|
|
84
|
+
signature
|
|
85
|
+
]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
data/lib/version.rb
ADDED
data/nostr_ruby.gemspec
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
$:.unshift File.expand_path('../lib', __FILE__)
|
|
2
|
-
require '
|
|
2
|
+
require 'version'
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = 'nostr_ruby'
|
|
6
|
-
s.version =
|
|
6
|
+
s.version = Nostr::VERSION
|
|
7
7
|
s.summary = 'A Ruby library to interact with the Nostr protocol'
|
|
8
8
|
s.description = 'NostrRuby is a Ruby library to interact with the Nostr protocol. At this stage the focus is the creation of public events and private encrypted messages.'
|
|
9
9
|
s.authors = ['Daniele Tonon']
|
|
@@ -13,10 +13,10 @@ Gem::Specification.new do |s|
|
|
|
13
13
|
s.platform = Gem::Platform::RUBY
|
|
14
14
|
s.require_paths = ['lib']
|
|
15
15
|
|
|
16
|
-
s.add_dependency 'base64', '
|
|
17
|
-
s.add_dependency 'bech32', '
|
|
18
|
-
s.add_dependency 'bip-schnorr', '
|
|
19
|
-
s.add_dependency '
|
|
20
|
-
s.add_dependency '
|
|
21
|
-
s.add_dependency '
|
|
16
|
+
s.add_dependency 'base64', '>= 0.3.0', '< 0.4.0'
|
|
17
|
+
s.add_dependency 'bech32', '>= 1.4.0', '< 2.0'
|
|
18
|
+
s.add_dependency 'bip-schnorr', '>= 0.4.0', '< 1.0'
|
|
19
|
+
s.add_dependency 'faye-websocket', '>= 0.11', '< 1.0'
|
|
20
|
+
s.add_dependency 'json', '>= 2.6.2', '< 3.0'
|
|
21
|
+
s.add_dependency 'unicode-emoji', '>= 3.3.1', '< 5.0'
|
|
22
22
|
end
|
metadata
CHANGED
|
@@ -1,99 +1,135 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nostr_ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniele Tonon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2025-11-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.
|
|
19
|
+
version: 0.3.0
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 0.4.0
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.
|
|
29
|
+
version: 0.3.0
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.4.0
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: bech32
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
|
-
- - "
|
|
37
|
+
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.
|
|
39
|
+
version: 1.4.0
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.0'
|
|
34
43
|
type: :runtime
|
|
35
44
|
prerelease: false
|
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
46
|
requirements:
|
|
38
|
-
- - "
|
|
47
|
+
- - ">="
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: 1.4.0
|
|
50
|
+
- - "<"
|
|
39
51
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
52
|
+
version: '2.0'
|
|
41
53
|
- !ruby/object:Gem::Dependency
|
|
42
54
|
name: bip-schnorr
|
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
|
44
56
|
requirements:
|
|
45
|
-
- - "
|
|
57
|
+
- - ">="
|
|
46
58
|
- !ruby/object:Gem::Version
|
|
47
59
|
version: 0.4.0
|
|
60
|
+
- - "<"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '1.0'
|
|
48
63
|
type: :runtime
|
|
49
64
|
prerelease: false
|
|
50
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
66
|
requirements:
|
|
52
|
-
- - "
|
|
67
|
+
- - ">="
|
|
53
68
|
- !ruby/object:Gem::Version
|
|
54
69
|
version: 0.4.0
|
|
70
|
+
- - "<"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '1.0'
|
|
55
73
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
74
|
+
name: faye-websocket
|
|
57
75
|
requirement: !ruby/object:Gem::Requirement
|
|
58
76
|
requirements:
|
|
59
|
-
- - "
|
|
77
|
+
- - ">="
|
|
60
78
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
79
|
+
version: '0.11'
|
|
80
|
+
- - "<"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.0'
|
|
62
83
|
type: :runtime
|
|
63
84
|
prerelease: false
|
|
64
85
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
86
|
requirements:
|
|
66
|
-
- - "
|
|
87
|
+
- - ">="
|
|
67
88
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
89
|
+
version: '0.11'
|
|
90
|
+
- - "<"
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '1.0'
|
|
69
93
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
94
|
+
name: json
|
|
71
95
|
requirement: !ruby/object:Gem::Requirement
|
|
72
96
|
requirements:
|
|
73
|
-
- - "
|
|
97
|
+
- - ">="
|
|
74
98
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
99
|
+
version: 2.6.2
|
|
100
|
+
- - "<"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.0'
|
|
76
103
|
type: :runtime
|
|
77
104
|
prerelease: false
|
|
78
105
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
106
|
requirements:
|
|
80
|
-
- - "
|
|
107
|
+
- - ">="
|
|
81
108
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
109
|
+
version: 2.6.2
|
|
110
|
+
- - "<"
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '3.0'
|
|
83
113
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
114
|
+
name: unicode-emoji
|
|
85
115
|
requirement: !ruby/object:Gem::Requirement
|
|
86
116
|
requirements:
|
|
87
|
-
- - "
|
|
117
|
+
- - ">="
|
|
118
|
+
- !ruby/object:Gem::Version
|
|
119
|
+
version: 3.3.1
|
|
120
|
+
- - "<"
|
|
88
121
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
122
|
+
version: '5.0'
|
|
90
123
|
type: :runtime
|
|
91
124
|
prerelease: false
|
|
92
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
126
|
requirements:
|
|
94
|
-
- - "
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: 3.3.1
|
|
130
|
+
- - "<"
|
|
95
131
|
- !ruby/object:Gem::Version
|
|
96
|
-
version:
|
|
132
|
+
version: '5.0'
|
|
97
133
|
description: NostrRuby is a Ruby library to interact with the Nostr protocol. At this
|
|
98
134
|
stage the focus is the creation of public events and private encrypted messages.
|
|
99
135
|
email:
|
|
@@ -105,10 +141,19 @@ files:
|
|
|
105
141
|
- Gemfile.lock
|
|
106
142
|
- LICENSE.md
|
|
107
143
|
- README.md
|
|
144
|
+
- lib/bech32.rb
|
|
145
|
+
- lib/client.rb
|
|
146
|
+
- lib/context.rb
|
|
108
147
|
- lib/crypto_tools.rb
|
|
109
|
-
- lib/
|
|
148
|
+
- lib/event.rb
|
|
149
|
+
- lib/event_wizard.rb
|
|
150
|
+
- lib/filter.rb
|
|
151
|
+
- lib/key.rb
|
|
152
|
+
- lib/kind.rb
|
|
153
|
+
- lib/message_handler.rb
|
|
110
154
|
- lib/nostr_ruby.rb
|
|
111
|
-
- lib/
|
|
155
|
+
- lib/signer.rb
|
|
156
|
+
- lib/version.rb
|
|
112
157
|
- nostr_ruby.gemspec
|
|
113
158
|
homepage: https://github.com/dtonon/nostr-ruby
|
|
114
159
|
licenses:
|
data/lib/custom_addr.rb
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
class CustomAddr
|
|
2
|
-
|
|
3
|
-
attr_accessor :hrp # human-readable part
|
|
4
|
-
attr_accessor :prog # witness program
|
|
5
|
-
|
|
6
|
-
def initialize(addr = nil)
|
|
7
|
-
@hrp, @prog = parse_addr(addr) if addr
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def to_scriptpubkey
|
|
11
|
-
prog.map{|p|[p].pack("C")}.join.unpack('H*').first
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def scriptpubkey=(script)
|
|
15
|
-
values = [script].pack('H*').unpack("C*")
|
|
16
|
-
@prog = values
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def addr
|
|
20
|
-
spec = Bech32::Encoding::BECH32
|
|
21
|
-
Bech32.encode(hrp, convert_bits(prog, 8, 5), spec)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
private
|
|
25
|
-
|
|
26
|
-
def parse_addr(addr)
|
|
27
|
-
hrp, data, spec = Bech32.decode(addr)
|
|
28
|
-
raise 'Invalid address.' if hrp.nil? || data[0].nil?
|
|
29
|
-
# raise 'Invalid witness version' if ver > 16
|
|
30
|
-
prog = convert_bits(data, 5, 8, false)
|
|
31
|
-
# raise 'Invalid witness program' if prog.nil? || prog.length < 2 || prog.length > 40
|
|
32
|
-
# raise 'Invalid witness program with version 0' if ver == 0 && (prog.length != 20 && prog.length != 32)
|
|
33
|
-
[hrp, prog]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def convert_bits(data, from, to, padding=true)
|
|
37
|
-
acc = 0
|
|
38
|
-
bits = 0
|
|
39
|
-
ret = []
|
|
40
|
-
maxv = (1 << to) - 1
|
|
41
|
-
max_acc = (1 << (from + to - 1)) - 1
|
|
42
|
-
data.each do |v|
|
|
43
|
-
return nil if v < 0 || (v >> from) != 0
|
|
44
|
-
acc = ((acc << from) | v) & max_acc
|
|
45
|
-
bits += from
|
|
46
|
-
while bits >= to
|
|
47
|
-
bits -= to
|
|
48
|
-
ret << ((acc >> bits) & maxv)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
if padding
|
|
52
|
-
ret << ((acc << (to - bits)) & maxv) unless bits == 0
|
|
53
|
-
elsif bits >= from || ((acc << (to - bits)) & maxv) != 0
|
|
54
|
-
return nil
|
|
55
|
-
end
|
|
56
|
-
ret
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
end
|
data/lib/nostr_ruby/version.rb
DELETED