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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c07cd32c02398c0b91710a693bfa2307bcdaa85690690612580e923761cb61ce
4
- data.tar.gz: c875468f38ea2c1695724975f281f5b2619a2908993d267670bde22a2e83d145
3
+ metadata.gz: 19e9c9f2d37d02877b0f4cdc83faeb46d09d707ee7b54d4ec938ee448fefc5bb
4
+ data.tar.gz: c01abc356c5bab165b6534eef10163c40be8c73632c2ce8b657c3ceb3e0cd1c3
5
5
  SHA512:
6
- metadata.gz: 004524d5d93169b21931e1e0639124b9861b376894fb7f6d489018658925576de39781f699e59177a3e968061aba596e771de5d139fb40b9be97860566bc0110
7
- data.tar.gz: f82ae191c0908480d514a6c897c6dd9381adfa35df84df1dacd3118989e09ed0361950b82aa87495c35f2f3d22a9b4c9bfce12f2d1649eb295bdcdab58741530
6
+ metadata.gz: 2021a0b34822aeb9eaa071fa96a6c48b8c3eb982eabbb2646daae076ddfc39957383dad49ecbde208e129d81af7a0489031afdd91ddfb8a69a011bba88178baa
7
+ data.tar.gz: 1959f99e9d18ca5fbe7fb0f5c41943f1981a980716c4871d94d9e23eb7d5c1e39cd0d0665f1bf51d2e21bd9f7b41c7630f7b15fcab583fa336cb4d0ab7d54adc
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nostr_ruby (0.1.3)
4
+ nostr_ruby (0.2.0)
5
5
  base64 (~> 0.1.1)
6
6
  bech32 (~> 1.3.0)
7
7
  bip-schnorr (~> 0.4.0)
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
+ ```
@@ -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
@@ -1,3 +1,3 @@
1
1
  module NostrRuby
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/nostr_ruby.rb CHANGED
@@ -1,4 +1,5 @@
1
- require 'custom_addr'
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
- attr_reader :private_key, :public_key, :pow_difficulty_target
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
- event = sign_event(event)
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
- event = sign_event(event)
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
- event = sign_event(event)
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
- event = sign_event(event)
176
- ['EVENT', event]
170
+ build_event(event)
177
171
  end
178
172
 
179
173
  def build_dm_event(text, recipient_public_key)
180
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
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
- event = sign_event(event)
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
- event = sign_event(event)
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
- event = sign_event(event)
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
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
237
- cipher.decrypt
238
- cipher.iv = Base64.decode64(iv)
239
- cipher.key = calculate_shared_key(sender_public_key)
240
- (cipher.update(Base64.decode64(encrypted)) + cipher.final).force_encoding('UTF-8')
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.1.3
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-03-10 00:00:00.000000000 Z
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