nostr 0.2.0 → 0.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: 90770e269f0c966f76013797314b9e3b4b0743fa57f5b84f2460371b413bc5f1
4
- data.tar.gz: 7a4a908a6c1b086781d26dc6f45c289a85859e12e1e854451a0e9f8e75f5fa06
3
+ metadata.gz: cd56d59d68235fe8fa4bceb67df4a7e83d4b6a8295ed31cc0152002800ad7d23
4
+ data.tar.gz: 56b70ada7f9fd6cd29be1c7bc23b7a23adc49bfe2f8d8de48300f70709626a3e
5
5
  SHA512:
6
- metadata.gz: 35a4401f078587bed79d6063f4051cca3f8dc170c074c552a202fee7872b75668eb8204a5f6db09f810ed0c5bb84026438d7e942f48a4825d63ba64552d032c7
7
- data.tar.gz: b9400efc3228dea17e23e40803e2e7b94ce9966e6a729fcf5ea0b79ffcea07b7e51f7fd9703d01af1d287ffdaf85b6b8d49d325813b8e32518a0be240537e86e
6
+ metadata.gz: be113a67cd651739cc3bac1deb4bba30e3598cfd787e8971a764e82d58c03ad1c918d3edd14630bbb1a5e2c6504c3c344859f0449a1774df7bb7bf0d3c38f537
7
+ data.tar.gz: c80bbf1f4d3caa25f48e1630b650587a2319197d7edba33e824b17e93f6355d8593c41364ad1fdb86a834d8cae63ad9f15e8feacae953b0ff8d7294f09c2c765
data/.rubocop.yml CHANGED
@@ -31,6 +31,9 @@ Metrics/BlockLength:
31
31
  - '**/*_spec.rb'
32
32
  - nostr.gemspec
33
33
 
34
+ Metrics/ParameterLists:
35
+ CountKeywordArgs: false
36
+
34
37
  # ----------------------- RSpec -----------------------
35
38
 
36
39
  RSpec/ExampleLength:
data/CHANGELOG.md CHANGED
@@ -4,6 +4,41 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.0] - 2023-02-25
8
+
9
+ ### Removed
10
+
11
+ - Removed `EventFragment` class. The `Event` class is no longer a Value Object. In other words, it is no longer
12
+ immutable and it may be invalid by not having attributes `id` or `sig`. The `EventFragment` abstraction, along with the
13
+ principles of immutability and was a major source of internal complexity as I needed to scale the codebase.
14
+
15
+ ### Added
16
+
17
+ - Client compliance with [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) (encrypted direct messages)
18
+ - Extracted the cryptographic concerns into a `Crypto` class.
19
+ - Added the setters `Event#id=` and `Event#sig=`
20
+ - Added a method on the event class to sign events (`Event#sign`)
21
+ - Added a missing test for `EventKind::CONTACT_LIST`
22
+ - Added two convenience methods to append event and pubkey references to an event's tags `add_event_reference` and
23
+ `add_pubkey_reference`
24
+
25
+ ### Fixed
26
+
27
+ - Fixed the generation of public keys
28
+ - Fixed the RBS signature of `User#create_event`
29
+
30
+ ## [0.3.0] - 2023-02-15
31
+
32
+ ### Added
33
+
34
+ - Client compliance wth [NIP-02](https://github.com/nostr-protocol/nips/blob/master/02.md) (manage contact lists)
35
+ - RBS type checking using Steep and TypeProf
36
+
37
+ ## Fixed
38
+
39
+ - Fixed a documentation typo
40
+ - Fixed a documentation error regarding the receiving of messages via websockets
41
+
7
42
  ## [0.2.0] - 2023-01-12
8
43
 
9
44
  ### Added
@@ -14,5 +49,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
14
49
 
15
50
  - Initial release
16
51
 
52
+ [0.3.0]: https://github.com/wilsonsilva/nostr/compare/v0.2.0...v0.3.0
17
53
  [0.2.0]: https://github.com/wilsonsilva/nostr/compare/v0.1.0...v0.2.0
18
54
  [0.1.0]: https://github.com/wilsonsilva/nostr/compare/7fded5...v0.1.0
data/README.md CHANGED
@@ -7,6 +7,27 @@
7
7
  Asynchronous Nostr client. Please note that the API is likely to change as the gem is still in development and
8
8
  has not yet reached a stable release. Use with caution.
9
9
 
10
+ ## Table of contents
11
+
12
+ - [Installation](#installation)
13
+ - [Usage](#usage)
14
+ * [Requiring the gem](#requiring-the-gem)
15
+ * [Generating a keypair](#generating-a-keypair)
16
+ * [Generating a private key and a public key](#generating-a-private-key-and-a-public-key)
17
+ * [Connecting to a Relay](#connecting-to-a-relay)
18
+ * [WebSocket events](#websocket-events)
19
+ * [Requesting for events / creating a subscription](#requesting-for-events--creating-a-subscription)
20
+ * [Stop previous subscriptions](#stop-previous-subscriptions)
21
+ * [Publishing an event](#publishing-an-event)
22
+ * [Creating/updating your contact list](#creatingupdating-your-contact-list)
23
+ * [Sending an encrypted direct message](#sending-an-encrypted-direct-message)
24
+ - [Implemented NIPs](#implemented-nips)
25
+ - [Development](#development)
26
+ * [Type checking](#type-checking)
27
+ - [Contributing](#contributing)
28
+ - [License](#license)
29
+ - [Code of Conduct](#code-of-conduct)
30
+
10
31
  ## Installation
11
32
 
12
33
  Install the gem and add to the application's Gemfile by executing:
@@ -27,12 +48,11 @@ All examples below assume that the gem has been required.
27
48
  require 'nostr'
28
49
  ```
29
50
 
30
-
31
51
  ### Generating a keypair
32
52
 
33
53
  ```ruby
34
54
  keygen = Nostr::Keygen.new
35
- keypair = keygen.generate_keypair
55
+ keypair = keygen.generate_key_pair
36
56
 
37
57
  keypair.private_key
38
58
  keypair.public_key
@@ -55,7 +75,7 @@ You may instantiate multiple Clients and multiple Relays.
55
75
 
56
76
  ```ruby
57
77
  client = Nostr::Client.new
58
- relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
78
+ relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
59
79
 
60
80
  client.connect(relay)
61
81
  ```
@@ -82,14 +102,28 @@ end
82
102
  # > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
83
103
  ```
84
104
 
85
- The `:message` event is fired whenwhen data is received through a WebSocket.
105
+ The `:message` event is fired when data is received through a WebSocket.
86
106
 
87
107
  ```ruby
88
108
  client.on :message do |message|
89
109
  puts message
90
110
  end
91
111
 
92
- # > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
112
+ # [
113
+ # "EVENT",
114
+ # "d34107357089bfc9882146d3bfab0386",
115
+ # {
116
+ # "content":"",
117
+ # "created_at":1676456512,
118
+ # "id":"18f63550da74454c5df7caa2a349edc5b2a6175ea4c5367fa4b4212781e5b310",
119
+ # "kind":3,
120
+ # "pubkey":"117a121fa41dc2caa0b3d6c5b9f42f90d114f1301d39f9ee96b646ebfee75e36",
121
+ # "sig":"d171420bd62cf981e8f86f2dd8f8f86737ea2bbe2d98da88db092991d125535860d982139a3c4be39886188613a9912ef380be017686a0a8b74231dc6e0b03cb",
122
+ # "tags":[
123
+ # ["p","1cc821cc2d47191b15fcfc0f73afed39a86ac6fb34fbfa7993ee3e0f0186ef7c"]
124
+ # ]
125
+ # }
126
+ # ]
93
127
  ```
94
128
 
95
129
  The `:close` event is fired when a connection with a WebSocket is closed.
@@ -179,10 +213,66 @@ event = user.create_event(
179
213
  client.publish(event)
180
214
  ```
181
215
 
182
- ## NIPS
216
+ ### Creating/updating your contact list
217
+
218
+ Every new contact list that gets published overwrites the past ones, so it should contain all entries.
219
+
220
+ ```ruby
221
+ # Creating a contact list event with 2 contacts
222
+ update_contacts_event = user.create_event(
223
+ kind: Nostr::EventKind::CONTACT_LIST,
224
+ tags: [
225
+ [
226
+ "p", # mandatory
227
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", # public key of the user to add to the contacts
228
+ "wss://alicerelay.com/", # can be an empty string or can be omitted
229
+ "alice" # can be an empty string or can be omitted
230
+ ],
231
+ [
232
+ "p", # mandatory
233
+ "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", # public key of the user to add to the contacts
234
+ "wss://bobrelay.com/nostr", # can be an empty string or can be omitted
235
+ "bob" # can be an empty string or can be omitted
236
+ ],
237
+ ],
238
+ )
239
+
240
+ # Send it to the Relay
241
+ client.publish(update_contacts_event)
242
+ ```
243
+
244
+ ### Sending an encrypted direct message
245
+
246
+ ```ruby
247
+ sender_private_key = '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
248
+
249
+ encrypted_direct_message = Nostr::Events::EncryptedDirectMessage.new(
250
+ sender_private_key: sender_private_key,
251
+ recipient_public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
252
+ plain_text: 'Your feedback is appreciated, now pay $8',
253
+ previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460' # optional
254
+ )
255
+
256
+ encrypted_direct_message.sign(sender_private_key)
257
+
258
+ # #<Nostr::Events::EncryptedDirectMessage:0x0000000104c9fa68
259
+ # @content="mjIFNo1sSP3KROE6QqhWnPSGAZRCuK7Np9X+88HSVSwwtFyiZ35msmEVoFgRpKx4?iv=YckChfS2oWCGpMt1uQ4GbQ==",
260
+ # @created_at=1676456512,
261
+ # @id="daac98826d5eb29f7c013b6160986c4baf4fe6d4b995df67c1b480fab1839a9b",
262
+ # @kind=4,
263
+ # @pubkey="8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca",
264
+ # @sig="028bb5f5bab0396e2065000c84a4bcce99e68b1a79bb1b91a84311546f49c5b67570b48d4a328a1827e7a8419d74451347d4f55011a196e71edab31aa3d6bdac",
265
+ # @tags=[["p", "6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0"], ["e", "ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460"]]>
266
+
267
+ # Send it to the Relay
268
+ client.publish(encrypted_direct_message)
269
+ ````
270
+
271
+ ## Implemented NIPs
183
272
 
184
273
  - [x] [NIP-01 - Client](https://github.com/nostr-protocol/nips/blob/master/01.md)
185
- - [ ] [NIP-01 - Relay](https://github.com/nostr-protocol/nips/blob/master/01.md)
274
+ - [x] [NIP-02 - Client](https://github.com/nostr-protocol/nips/blob/master/02.md)
275
+ - [x] [NIP-04 - Client](https://github.com/nostr-protocol/nips/blob/master/04.md)
186
276
 
187
277
  ## Development
188
278
 
@@ -200,17 +290,34 @@ The health and maintainability of the codebase is ensured through a set of
200
290
  Rake tasks to test, lint and audit the gem for security vulnerabilities and documentation:
201
291
 
202
292
  ```
203
- rake bundle:audit # Checks for vulnerable versions of gems
204
- rake qa # Test, lint and perform security and documentation audits
205
- rake rubocop # Lint the codebase with RuboCop
206
- rake rubocop:auto_correct # Auto-correct RuboCop offenses
207
- rake spec # Run RSpec code examples
208
- rake verify_measurements # Verify that yardstick coverage is at least 100%
209
- rake yard # Generate YARD Documentation
210
- rake yard:junk # Check the junk in your YARD Documentation
211
- rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
293
+ rake build # Build nostr.gem into the pkg directory
294
+ rake build:checksum # Generate SHA512 checksum if nostr.gem into the checksums directory
295
+ rake bundle:audit:check # Checks the Gemfile.lock for insecure dependencies
296
+ rake bundle:audit:update # Updates the bundler-audit vulnerability database
297
+ rake clean # Remove any temporary products
298
+ rake clobber # Remove any generated files
299
+ rake coverage # Run spec with coverage
300
+ rake install # Build and install nostr.gem into system gems
301
+ rake install:local # Build and install nostr.gem into system gems without network access
302
+ rake qa # Test, lint and perform security and documentation audits
303
+ rake release[remote] # Create a tag, build and push nostr.gem to rubygems.org
304
+ rake rubocop # Run RuboCop
305
+ rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
306
+ rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
307
+ rake spec # Run RSpec code examples
308
+ rake verify_measurements # Verify that yardstick coverage is at least 100%
309
+ rake yard # Generate YARD Documentation
310
+ rake yard:junk # Check the junk in your YARD Documentation
311
+ rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
212
312
  ```
213
313
 
314
+ ### Type checking
315
+
316
+ This gem leverages [RBS](https://github.com/ruby/rbs), a language to describe the structure of Ruby programs. It is
317
+ used to provide type checking and autocompletion in your editor. Run `bundle exec typeprof FILENAME` to generate
318
+ an RBS definition for the given Ruby file. And validate all definitions using [Steep](https://github.com/soutaro/steep)
319
+ with the command `bundle exec steep check`.
320
+
214
321
  ## Contributing
215
322
 
216
323
  Bug reports and pull requests are welcome on GitHub at https://github.com/wilsonsilva/nostr.
data/Steepfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature 'sig'
5
+
6
+ check 'lib'
7
+
8
+ # Core libraries
9
+ library 'base64'
10
+ library 'digest'
11
+ library 'openssl'
12
+ library 'securerandom'
13
+
14
+ # Gems
15
+ library 'json'
16
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Performs cryptographic operations on a +Nostr::Event+.
5
+ class Crypto
6
+ # Numeric base of the OpenSSL big number used in an event content's encryption.
7
+ #
8
+ # @return [Integer]
9
+ #
10
+ BN_BASE = 16
11
+
12
+ # Name of the cipher curve used in an event content's encryption.
13
+ #
14
+ # @return [String]
15
+ #
16
+ CIPHER_CURVE = 'secp256k1'
17
+
18
+ # Name of the cipher algorithm used in an event content's encryption.
19
+ #
20
+ # @return [String]
21
+ #
22
+ CIPHER_ALGORITHM = 'aes-256-cbc'
23
+
24
+ # Encrypts a piece of text
25
+ #
26
+ # @api public
27
+ #
28
+ # @example Encrypting an event's content
29
+ # crypto = Nostr::Crypto.new
30
+ # encrypted = crypto.encrypt_text(sender_private_key, recipient_public_key, 'Feedback appreciated. Now pay $8')
31
+ # encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
32
+ #
33
+ # @param sender_private_key [String] 32-bytes hex-encoded private key of the creator.
34
+ # @param recipient_public_key [String] 32-bytes hex-encoded public key of the recipient.
35
+ # @param plain_text [String] The text to be encrypted
36
+ #
37
+ # @return [String] Encrypted text.
38
+ #
39
+ def encrypt_text(sender_private_key, recipient_public_key, plain_text)
40
+ cipher = OpenSSL::Cipher.new(CIPHER_ALGORITHM).encrypt
41
+ cipher.iv = iv = cipher.random_iv
42
+ cipher.key = compute_shared_key(sender_private_key, recipient_public_key)
43
+ encrypted_text = cipher.update(plain_text) + cipher.final
44
+ encrypted_text = "#{Base64.encode64(encrypted_text)}?iv=#{Base64.encode64(iv)}"
45
+ encrypted_text.gsub("\n", '')
46
+ end
47
+
48
+ # Decrypts a piece of text
49
+ #
50
+ # @api public
51
+ #
52
+ # @example Encrypting an event's content
53
+ # crypto = Nostr::Crypto.new
54
+ # encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
55
+ # decrypted = crypto.decrypt_text(recipient_private_key, sender_public_key, encrypted)
56
+ #
57
+ # @param sender_public_key [String] 32-bytes hex-encoded public key of the message creator.
58
+ # @param recipient_private_key [String] 32-bytes hex-encoded public key of the recipient.
59
+ # @param encrypted_text [String] The text to be decrypted
60
+ #
61
+ # @return [String] Decrypted text.
62
+ #
63
+ def decrypt_text(recipient_private_key, sender_public_key, encrypted_text)
64
+ base64_encoded_text, iv = encrypted_text.split('?iv=')
65
+ cipher = OpenSSL::Cipher.new(CIPHER_ALGORITHM).decrypt
66
+ cipher.iv = Base64.decode64(iv)
67
+ cipher.key = compute_shared_key(recipient_private_key, sender_public_key)
68
+ plain_text = cipher.update(Base64.decode64(base64_encoded_text)) + cipher.final
69
+ plain_text.force_encoding('UTF-8')
70
+ end
71
+
72
+ # Uses the private key to generate an event id and sign the event
73
+ #
74
+ # @api public
75
+ #
76
+ # @example Signing an event
77
+ # crypto = Nostr::Crypto.new
78
+ # crypto.sign(event, private_key)
79
+ # event.id # => an id
80
+ # event.sig # => a signature
81
+ #
82
+ # @param event [Event] The event to be signed
83
+ # @param private_key [String] 32-bytes hex-encoded private key.
84
+ #
85
+ # @return [Event] An unsigned event.
86
+ #
87
+ def sign_event(event, private_key)
88
+ event_digest = hash_event(event)
89
+
90
+ hex_private_key = Array(private_key).pack('H*')
91
+ hex_message = Array(event_digest).pack('H*')
92
+ event_signature = Schnorr.sign(hex_message, hex_private_key).encode.unpack1('H*')
93
+
94
+ event.id = event_digest
95
+ event.sig = event_signature
96
+
97
+ event
98
+ end
99
+
100
+ private
101
+
102
+ # Finds a shared key between two keys
103
+ #
104
+ # @api private
105
+ #
106
+ # @param private_key [String] 32-bytes hex-encoded private key.
107
+ # @param public_key [String] 32-bytes hex-encoded public key.
108
+ #
109
+ # @return [String] A shared key used in the event's content encryption and decryption.
110
+ #
111
+ def compute_shared_key(private_key, public_key)
112
+ group = OpenSSL::PKey::EC::Group.new(CIPHER_CURVE)
113
+
114
+ private_key_bn = OpenSSL::BN.new(private_key, BN_BASE)
115
+ public_key_bn = OpenSSL::BN.new("02#{public_key}", BN_BASE)
116
+ public_key_point = OpenSSL::PKey::EC::Point.new(group, public_key_bn)
117
+
118
+ asn1 = OpenSSL::ASN1::Sequence(
119
+ [
120
+ OpenSSL::ASN1::Integer.new(1),
121
+ OpenSSL::ASN1::OctetString(private_key_bn.to_s(2)),
122
+ OpenSSL::ASN1::ObjectId(CIPHER_CURVE, 0, :EXPLICIT),
123
+ OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1, :EXPLICIT)
124
+ ]
125
+ )
126
+
127
+ pkey = OpenSSL::PKey::EC.new(asn1.to_der)
128
+ pkey.dh_compute_key(public_key_point)
129
+ end
130
+
131
+ # Generates a SHA256 hash of a +Nostr::Event+
132
+ #
133
+ # @api private
134
+ #
135
+ # @param event [Event] The event to be hashed
136
+ #
137
+ # @return [String] A SHA256 digest of the event
138
+ #
139
+ def hash_event(event)
140
+ Digest::SHA256.hexdigest(JSON.dump(event.serialize))
141
+ end
142
+ end
143
+ end
data/lib/nostr/event.rb CHANGED
@@ -2,7 +2,65 @@
2
2
 
3
3
  module Nostr
4
4
  # The only object type that exists in Nostr is an event. Events are immutable.
5
- class Event < EventFragment
5
+ class Event
6
+ # 32-bytes hex-encoded public key of the event creator
7
+ #
8
+ # @api public
9
+ #
10
+ # @example
11
+ # event.pubkey # => '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e'
12
+ #
13
+ # @return [String]
14
+ #
15
+ attr_reader :pubkey
16
+
17
+ # Date of the creation of the vent. A UNIX timestamp, in seconds
18
+ #
19
+ # @api public
20
+ #
21
+ # @example
22
+ # event.created_at # => 1230981305
23
+ #
24
+ # @return [Integer]
25
+ #
26
+ attr_reader :created_at
27
+
28
+ # The kind of the event. An integer from 0 to 3
29
+ #
30
+ # @api public
31
+ #
32
+ # @example
33
+ # event.kind # => 1
34
+ #
35
+ # @return [Integer]
36
+ #
37
+ attr_reader :kind
38
+
39
+ # An array of tags. Each tag is an array of strings
40
+ #
41
+ # @api public
42
+ #
43
+ # @example Tags referencing an event
44
+ # event.tags #=> [["e", "event_id", "relay URL"]]
45
+ #
46
+ # @example Tags referencing a key
47
+ # event.tags #=> [["p", "event_id", "relay URL"]]
48
+ #
49
+ # @return [Array<Array>]
50
+ #
51
+ attr_reader :tags
52
+
53
+ # An arbitrary string
54
+ #
55
+ # @api public
56
+ #
57
+ # @example
58
+ # event.content # => 'Your feedback is appreciated, now pay $8'
59
+ #
60
+ # @return [String]
61
+ #
62
+ attr_reader :content
63
+
6
64
  # 32-bytes sha256 of the the serialized event data.
7
65
  # To obtain the event.id, we sha256 the serialized event. The serialization is done over the UTF-8 JSON-serialized
8
66
  # string (with no white space or line breaks)
@@ -12,9 +70,13 @@ module Nostr
12
70
  # @example Getting the event id
13
71
  # event.id # => 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
14
72
  #
15
- # @return [String]
73
+ # @example Setting the event id
74
+ # event.id = 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
75
+ # event.id # => 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
76
+ #
77
+ # @return [String|nil]
16
78
  #
17
- attr_reader :id
79
+ attr_accessor :id
18
80
 
19
81
  # 64-bytes signature of the sha256 hash of the serialized event data, which is
20
82
  # the same as the "id" field
@@ -24,9 +86,13 @@ module Nostr
24
86
  # @example Getting the event signature
25
87
  # event.sig # => ''
26
88
  #
27
- # @return [String]
89
+ # @example Setting the event signature
90
+ # event.sig = '058613f8d34c053294cc28b7f9e1f8f0e80fd1ac94fb20f2da6ca514e7360b39'
91
+ # event.sig # => '058613f8d34c053294cc28b7f9e1f8f0e80fd1ac94fb20f2da6ca514e7360b39'
92
+ #
93
+ # @return [String|nil]
28
94
  #
29
- attr_reader :sig
95
+ attr_accessor :sig
30
96
 
31
97
  # Instantiates a new Event
32
98
  #
@@ -44,16 +110,96 @@ module Nostr
44
110
  # 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
45
111
  # )
46
112
  #
47
- #
48
- # @param id [String] 32-bytes sha256 of the the serialized event data.
49
- # @param sig [String] 64-bytes signature of the sha256 hash of the serialized event data, which is
113
+ # @param id [String|nil] 32-bytes sha256 of the the serialized event data.
114
+ # @param sig [String|nil] 64-bytes signature of the sha256 hash of the serialized event data, which is
50
115
  # the same as the "id" field
51
- #
52
- def initialize(id:, sig:, **kwargs)
53
- super(**kwargs)
116
+ # @param pubkey [String] 32-bytes hex-encoded public key of the event creator.
117
+ # @param created_at [Integer] Date of the creation of the vent. A UNIX timestamp, in seconds.
118
+ # @param kind [Integer] The kind of the event. An integer from 0 to 3.
119
+ # @param tags [Array<Array>] An array of tags. Each tag is an array of strings.
120
+ # @param content [String] Arbitrary string.
121
+ #
122
+ def initialize(
123
+ pubkey:,
124
+ kind:,
125
+ content:,
126
+ created_at: Time.now.to_i,
127
+ tags: [],
128
+ id: nil,
129
+ sig: nil
130
+ )
54
131
 
55
132
  @id = id
56
133
  @sig = sig
134
+ @pubkey = pubkey
135
+ @created_at = created_at
136
+ @kind = kind
137
+ @tags = tags
138
+ @content = content
139
+ end
140
+
141
+ # Adds a reference to an event id as an 'e' tag
142
+ #
143
+ # @api public
144
+ #
145
+ # @example Adding a reference to a pubkey
146
+ # event_id = '189df012cfff8a075785b884bd702025f4a7a37710f581c4ac9d33e24b585408'
147
+ # event.add_event_reference(event_id)
148
+ #
149
+ # @param event_id [String] 32-bytes hex-encoded event id.
150
+ #
151
+ # @return [Array<String>] The event's updated list of tags
152
+ #
153
+ def add_event_reference(event_id) = tags.push(['e', event_id])
154
+
155
+ # Adds a reference to a pubkey as a 'p' tag
156
+ #
157
+ # @api public
158
+ #
159
+ # @example Adding a reference to a pubkey
160
+ # pubkey = '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e'
161
+ # event.add_pubkey_reference(pubkey)
162
+ #
163
+ # @param pubkey [String] 32-bytes hex-encoded public key.
164
+ #
165
+ # @return [Array<String>] The event's updated list of tags
166
+ #
167
+ def add_pubkey_reference(pubkey) = tags.push(['p', pubkey])
168
+
169
+ # Signs an event with the user's private key
170
+ #
171
+ # @api public
172
+ #
173
+ # @example Signing an event
174
+ # event.sign(private_key)
175
+ #
176
+ # @param private_key [String] 32-bytes hex-encoded private key.
177
+ #
178
+ # @return [Event] A signed event.
179
+ #
180
+ def sign(private_key)
181
+ crypto = Crypto.new
182
+ crypto.sign_event(self, private_key)
183
+ end
184
+
185
+ # Serializes the event, to obtain a SHA256 digest of it
186
+ #
187
+ # @api public
188
+ #
189
+ # @example Converting the event to a digest
190
+ # event.serialize
191
+ #
192
+ # @return [Array] The event as an array.
193
+ #
194
+ def serialize
195
+ [
196
+ 0,
197
+ pubkey,
198
+ created_at,
199
+ kind,
200
+ tags,
201
+ content
202
+ ]
57
203
  end
58
204
 
59
205
  # Converts the event to a hash
@@ -24,5 +24,20 @@ module Nostr
24
24
  # @return [Integer]
25
25
  #
26
26
  RECOMMEND_SERVER = 2
27
+
28
+ # A special event with kind 3, meaning "contact list" is defined as having a list of p tags, one for each of
29
+ # the followed/known profiles one is following.
30
+ #
31
+ # @return [Integer]
32
+ #
33
+ CONTACT_LIST = 3
34
+
35
+ # A special event with kind 4, meaning "encrypted direct message". An event of this kind has its +content+
36
+ # equal to the base64-encoded, aes-256-cbc encrypted string of anything a user wants to write, encrypted using a
37
+ # shared cipher generated by combining the recipient's public-key with the sender's private-key.
38
+ #
39
+ # @return [Integer]
40
+ #
41
+ ENCRYPTED_DIRECT_MESSAGE = 4
27
42
  end
28
43
  end