nostr_ruby 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|