nostr_ruby 0.1.3 → 0.2.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/Gemfile.lock +1 -1
- data/README.md +38 -0
- data/lib/crypto_tools.rb +31 -0
- data/lib/nostr_ruby/version.rb +1 -1
- data/lib/nostr_ruby.rb +42 -38
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19e9c9f2d37d02877b0f4cdc83faeb46d09d707ee7b54d4ec938ee448fefc5bb
|
4
|
+
data.tar.gz: c01abc356c5bab165b6534eef10163c40be8c73632c2ce8b657c3ceb3e0cd1c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2021a0b34822aeb9eaa071fa96a6c48b8c3eb982eabbb2646daae076ddfc39957383dad49ecbde208e129d81af7a0489031afdd91ddfb8a69a011bba88178baa
|
7
|
+
data.tar.gz: 1959f99e9d18ca5fbe7fb0f5c41943f1981a980716c4871d94d9e23eb7d5c1e39cd0d0665f1bf51d2e21bd9f7b41c7630f7b15fcab583fa336cb4d0ab7d54adc
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -173,3 +173,41 @@ note = n.build_note_event("Hello Nostr!")
|
|
173
173
|
# "id"=>"0000fb0c4563274e742e56d7d6de08684a2a25dfb52b79cccdb49c649dccbf45",
|
174
174
|
# "sig"=>"838a1457c75084319e4723fbd9cbcf4c3311c466daf3908ffa114682094140e3b188996a73ae9fd3d3c6dbf08beecf9081b8d2bf0e60163b07cdf36a50dea1c0"}]
|
175
175
|
```
|
176
|
+
|
177
|
+
### Create events with a NIP-26 delegation
|
178
|
+
```ruby
|
179
|
+
from = Time.now.to_i
|
180
|
+
to = (Time.now + 60*60).to_i
|
181
|
+
delegatee_pubkey = "b1d8dfd69fe8795042dbbc4d3f85938a01d4740c54d2daf11088c75c50ff19d9"
|
182
|
+
conditions = "kind=1&created_at>#{from}&created_at<#{to}"
|
183
|
+
tag = n.get_delegation_tag(delegatee_pubkey, conditions)
|
184
|
+
n.set_delegation(tag)
|
185
|
+
n.build_note_event("I delegate someone to post this!")
|
186
|
+
#=>
|
187
|
+
#["EVENT",
|
188
|
+
# {:pubkey=>"1ed41a3ce33edfc580102abfbdc01d922f8c7697beee3e395aa7dcd7115a3372",
|
189
|
+
# :created_at=>1681503318,
|
190
|
+
# :kind=>1,
|
191
|
+
# :tags=>
|
192
|
+
# [["delegation",
|
193
|
+
# "1ed41a3ce33edfc580102abfbdc01d922f8c7697beee3e395aa7dcd7115a3372",
|
194
|
+
# "kind=1&created_at>1681503305&created_at<1681506905",
|
195
|
+
# "dbbdc7074a5c2d2a53ee174c43afb0bb03106ced866e0dfa7996fe2553a54aaa9af6affe915ec2e688e26357681bfcf0445d607ed85eca2217f79fb51094d816"]],
|
196
|
+
# :content=>"I delegate someone to post this!",
|
197
|
+
# "id"=>"f6fd20535db6748e0529052310c47dc788616b03f8cef20260ca6a2dfb5dedaf",
|
198
|
+
# "sig"=>"826d7bd1f369f6ab8330746c236437610f13205809d12bc94af287b7ceec180443a3a750267d5152323329a1b2d02d0702364fa3ee8228bab57016b39975e449"}]
|
199
|
+
```
|
200
|
+
|
201
|
+
### Verify a NIP-26 delegation
|
202
|
+
```ruby
|
203
|
+
delegatee_pubkey = "b1d8dfd69fe8795042dbbc4d3f85938a01d4740c54d2daf11088c75c50ff19d9"
|
204
|
+
tag = ["delegation", delegator_pubkey, conditions, signature]
|
205
|
+
Nostr.verify_delegation_signature(delegatee_pubkey, tag)
|
206
|
+
#=> true
|
207
|
+
```
|
208
|
+
|
209
|
+
### Reset the NIP-26 delegation
|
210
|
+
```ruby
|
211
|
+
n.reset_delegation
|
212
|
+
#=> nil
|
213
|
+
```
|
data/lib/crypto_tools.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module CryptoTools
|
2
|
+
|
3
|
+
def self.calculate_shared_key(priv_key_a, pub_key_b)
|
4
|
+
ec = OpenSSL::PKey::EC.new('secp256k1')
|
5
|
+
ec.private_key = OpenSSL::BN.new(priv_key_a, 16)
|
6
|
+
pub_key_hex = "02#{pub_key_b}"
|
7
|
+
pub_key_bn = OpenSSL::BN.new(pub_key_hex, 16)
|
8
|
+
secret_point = OpenSSL::PKey::EC::Point.new(ec.group, pub_key_bn)
|
9
|
+
ec.dh_compute_key(secret_point)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.aes_256_cbc_encrypt(priv_key_a, pub_key_b, payload)
|
13
|
+
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
|
14
|
+
cipher.encrypt
|
15
|
+
cipher.iv = iv = cipher.random_iv
|
16
|
+
cipher.key = calculate_shared_key(priv_key_a, pub_key_b)
|
17
|
+
encrypted_text = cipher.update(payload)
|
18
|
+
encrypted_text << cipher.final
|
19
|
+
encrypted_text = "#{Base64.encode64(encrypted_text)}?iv=#{Base64.encode64(iv)}"
|
20
|
+
encrypted_text.gsub("\n", '')
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.aes_256_cbc_decrypt(priv_key_a, pub_key_b, payload, iv)
|
24
|
+
cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
|
25
|
+
cipher.decrypt
|
26
|
+
cipher.iv = Base64.decode64(iv)
|
27
|
+
cipher.key = calculate_shared_key(priv_key_a, pub_key_b)
|
28
|
+
(cipher.update(Base64.decode64(payload)) + cipher.final).force_encoding('UTF-8')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/nostr_ruby/version.rb
CHANGED
data/lib/nostr_ruby.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require_relative 'custom_addr'
|
2
|
+
require_relative 'crypto_tools'
|
2
3
|
require 'ecdsa'
|
3
4
|
require 'schnorr'
|
4
5
|
require 'json'
|
@@ -10,7 +11,9 @@ require 'websocket-client-simple'
|
|
10
11
|
# * Ruby library to interact with the Nostr protocol
|
11
12
|
|
12
13
|
class Nostr
|
13
|
-
|
14
|
+
include CryptoTools
|
15
|
+
|
16
|
+
attr_reader :private_key, :public_key, :pow_difficulty_target, :nip26_delegation_tag
|
14
17
|
|
15
18
|
def initialize(key)
|
16
19
|
hex_private_key = if key[:private_key]&.include?('nsec')
|
@@ -60,15 +63,6 @@ class Nostr
|
|
60
63
|
custom_addr.addr
|
61
64
|
end
|
62
65
|
|
63
|
-
def calculate_shared_key(other_public_key)
|
64
|
-
ec = OpenSSL::PKey::EC.new('secp256k1')
|
65
|
-
ec.private_key = OpenSSL::BN.new(@private_key, 16)
|
66
|
-
recipient_key_hex = "02#{other_public_key}"
|
67
|
-
recipient_pub_bn = OpenSSL::BN.new(recipient_key_hex, 16)
|
68
|
-
secret_point = OpenSSL::PKey::EC::Point.new(ec.group, recipient_pub_bn)
|
69
|
-
ec.dh_compute_key(secret_point)
|
70
|
-
end
|
71
|
-
|
72
66
|
def sign_event(event)
|
73
67
|
raise 'Invalid pubkey' unless event[:pubkey].is_a?(String) && event[:pubkey].size == 64
|
74
68
|
raise 'Invalid created_at' unless event[:created_at].is_a?(Integer)
|
@@ -113,6 +107,10 @@ class Nostr
|
|
113
107
|
end
|
114
108
|
|
115
109
|
def build_event(payload)
|
110
|
+
if @nip26_delegation_tag
|
111
|
+
payload[:tags] = [] unless payload[:tags]
|
112
|
+
payload[:tags] << @nip26_delegation_tag
|
113
|
+
end
|
116
114
|
event = sign_event(payload)
|
117
115
|
['EVENT', event]
|
118
116
|
end
|
@@ -131,8 +129,7 @@ class Nostr
|
|
131
129
|
"content": data.to_json
|
132
130
|
}
|
133
131
|
|
134
|
-
|
135
|
-
['EVENT', event]
|
132
|
+
build_event(event)
|
136
133
|
end
|
137
134
|
|
138
135
|
def build_note_event(text, channel_key = nil)
|
@@ -144,8 +141,7 @@ class Nostr
|
|
144
141
|
"content": text
|
145
142
|
}
|
146
143
|
|
147
|
-
|
148
|
-
['EVENT', event]
|
144
|
+
build_event(event)
|
149
145
|
end
|
150
146
|
|
151
147
|
def build_recommended_relay_event(relay)
|
@@ -159,8 +155,7 @@ class Nostr
|
|
159
155
|
"content": relay
|
160
156
|
}
|
161
157
|
|
162
|
-
|
163
|
-
['EVENT', event]
|
158
|
+
build_event(event)
|
164
159
|
end
|
165
160
|
|
166
161
|
def build_contact_list_event(contacts)
|
@@ -172,19 +167,11 @@ class Nostr
|
|
172
167
|
"content": ''
|
173
168
|
}
|
174
169
|
|
175
|
-
|
176
|
-
['EVENT', event]
|
170
|
+
build_event(event)
|
177
171
|
end
|
178
172
|
|
179
173
|
def build_dm_event(text, recipient_public_key)
|
180
|
-
|
181
|
-
cipher.encrypt
|
182
|
-
cipher.iv = iv = cipher.random_iv
|
183
|
-
cipher.key = calculate_shared_key(recipient_public_key)
|
184
|
-
encrypted_text = cipher.update(text)
|
185
|
-
encrypted_text << cipher.final
|
186
|
-
encrypted_text = "#{Base64.encode64(encrypted_text)}?iv=#{Base64.encode64(iv)}"
|
187
|
-
encrypted_text = encrypted_text.gsub("\n", '')
|
174
|
+
encrypted_text = CryptoTools.aes_256_cbc_encrypt(@private_key, recipient_public_key, text)
|
188
175
|
|
189
176
|
event = {
|
190
177
|
"pubkey": @public_key,
|
@@ -194,8 +181,7 @@ class Nostr
|
|
194
181
|
"content": encrypted_text
|
195
182
|
}
|
196
183
|
|
197
|
-
|
198
|
-
['EVENT', event]
|
184
|
+
build_event(event)
|
199
185
|
end
|
200
186
|
|
201
187
|
def build_deletion_event(events, reason = '')
|
@@ -207,8 +193,7 @@ class Nostr
|
|
207
193
|
"content": reason
|
208
194
|
}
|
209
195
|
|
210
|
-
|
211
|
-
['EVENT', event]
|
196
|
+
build_event(event)
|
212
197
|
end
|
213
198
|
|
214
199
|
def build_reaction_event(reaction, event, author)
|
@@ -224,8 +209,7 @@ class Nostr
|
|
224
209
|
"content": reaction
|
225
210
|
}
|
226
211
|
|
227
|
-
|
228
|
-
['EVENT', event]
|
212
|
+
build_event(event)
|
229
213
|
end
|
230
214
|
|
231
215
|
def decrypt_dm(event)
|
@@ -233,11 +217,31 @@ class Nostr
|
|
233
217
|
sender_public_key = data[:pubkey] != @public_key ? data[:pubkey] : data[:tags][0][1]
|
234
218
|
encrypted = data[:content].split('?iv=')[0]
|
235
219
|
iv = data[:content].split('?iv=')[1]
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
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*'))
|
241
245
|
end
|
242
246
|
|
243
247
|
def build_req_event(filters)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nostr_ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniele Tonon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -105,6 +105,7 @@ files:
|
|
105
105
|
- Gemfile.lock
|
106
106
|
- LICENSE.md
|
107
107
|
- README.md
|
108
|
+
- lib/crypto_tools.rb
|
108
109
|
- lib/custom_addr.rb
|
109
110
|
- lib/nostr_ruby.rb
|
110
111
|
- lib/nostr_ruby/version.rb
|