nostr 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.adr-dir +1 -0
  3. data/.rubocop.yml +1 -1
  4. data/.tool-versions +2 -2
  5. data/CHANGELOG.md +35 -1
  6. data/README.md +2 -1
  7. data/adr/0001-record-architecture-decisions.md +19 -0
  8. data/adr/0002-introduction-of-signature-class.md +27 -0
  9. data/docs/.vitepress/config.mjs +2 -0
  10. data/docs/common-use-cases/signing-and-verifying-events.md +50 -0
  11. data/docs/common-use-cases/signing-and-verifying-messages.md +43 -0
  12. data/docs/getting-started/overview.md +2 -1
  13. data/docs/index.md +0 -2
  14. data/lib/nostr/crypto.rb +82 -6
  15. data/lib/nostr/errors/invalid_key_format_error.rb +1 -1
  16. data/lib/nostr/errors/invalid_key_length_error.rb +1 -1
  17. data/lib/nostr/errors/invalid_key_type_error.rb +1 -1
  18. data/lib/nostr/errors/invalid_signature_format_error.rb +18 -0
  19. data/lib/nostr/errors/invalid_signature_length_error.rb +18 -0
  20. data/lib/nostr/errors/invalid_signature_type_error.rb +16 -0
  21. data/lib/nostr/errors/signature_validation_error.rb +6 -0
  22. data/lib/nostr/errors.rb +4 -0
  23. data/lib/nostr/event.rb +37 -9
  24. data/lib/nostr/event_kind.rb +1 -0
  25. data/lib/nostr/events/encrypted_direct_message.rb +4 -4
  26. data/lib/nostr/filter.rb +10 -7
  27. data/lib/nostr/key.rb +2 -2
  28. data/lib/nostr/key_pair.rb +26 -2
  29. data/lib/nostr/keygen.rb +1 -1
  30. data/lib/nostr/signature.rb +67 -0
  31. data/lib/nostr/version.rb +1 -1
  32. data/lib/nostr.rb +1 -0
  33. data/nostr.gemspec +8 -8
  34. data/sig/nostr/crypto.rbs +3 -0
  35. data/sig/nostr/errors/invalid_signature_format_error.rbs +5 -0
  36. data/sig/nostr/errors/invalid_signature_length_error.rbs +5 -0
  37. data/sig/nostr/errors/invalid_signature_type_error.rbs +5 -0
  38. data/sig/nostr/errors/signature_validation_error.rbs +4 -0
  39. data/sig/nostr/event.rbs +7 -6
  40. data/sig/nostr/key_pair.rbs +1 -0
  41. data/sig/nostr/signature.rbs +14 -0
  42. data/sig/vendor/schnorr/signature.rbs +16 -0
  43. data/sig/vendor/schnorr.rbs +3 -1
  44. metadata +34 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 168cbde3fb029345d4fa10043eead7c194a57fe3ee23c12b76bbaf79b1bdc71d
4
- data.tar.gz: ff1a1837e897a8c0ad233a61ed4526fe3e4087c04474e54778a00a0d4313e361
3
+ metadata.gz: dcfeb88b78cad383e565b6e908be58f58e6f1002083044957ee0f09cf63228f4
4
+ data.tar.gz: ca2229181586e8bf5f329e4d5ac60ad346076fc8e39281dd43690d57ac98fcb7
5
5
  SHA512:
6
- metadata.gz: e5549507ded84026a2295c3914324e8c74aff3c1ae80322db3cd449e7f65c3475186e9daaa76f7a22241e33b6759c2073da5a31a910a2082c8e49a99081dc5bf
7
- data.tar.gz: 19f69ed10ae3ba42113992541912bb18a4aa3ad8239b582b996e7c7845c4624c4a9ee73d800f51fbd883d2bdd9ea603eeb807ade147bcfc22db7183c1db30ce6
6
+ metadata.gz: 4511decc634548b75429db9166433060795295aea61cda64cd7b2b2647415db3808f4def27f8a4806d77b6fbaef88b11d08788254febd66bcfb7be318059cced
7
+ data.tar.gz: aef568e9fad07e0b387e3ae1437a50d08c81b59485c8aadd42cd9d7a035319b3da2bd5247e7d58fc890961eec5e0284d9299e6631c674cce3df3a5c210202bf2
data/.adr-dir ADDED
@@ -0,0 +1 @@
1
+ adr
data/.rubocop.yml CHANGED
@@ -5,7 +5,7 @@ require:
5
5
  - rubocop-rspec
6
6
 
7
7
  AllCops:
8
- TargetRubyVersion: 3.2
8
+ TargetRubyVersion: 3.3
9
9
  DisplayCopNames: true
10
10
  NewCops: enable
11
11
 
data/.tool-versions CHANGED
@@ -1,2 +1,2 @@
1
- ruby 3.2.2
2
- bun 1.0.11
1
+ ruby 3.3.0
2
+ bun 1.0.30
data/CHANGELOG.md CHANGED
@@ -4,13 +4,46 @@ 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.1.1/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.6.0] 2024-03-15
8
+
9
+ ### Added
10
+
11
+ - Added Architecture Decision Records (ADRs) to document architectural decisions
12
+ - Added the `Signature` class to fix the primitive obsession with signatures and to make it easier to work with them
13
+ - Added `valid_sig?` and `check_sig!` to the `Crypto` class to verify whether an event's signature is valid
14
+ - Added `sign_message` to the `Crypto` class to sign a message
15
+ - Added `verify_signature?` to the `Event` class to verify whether an event's signature is valid
16
+ - Added `#to_ary` to the `KeyPair` class to enable keypair destructuring
17
+ - Added RBS types for `schnorr`
18
+
19
+ ### Changed
20
+
21
+ - Updated the required Ruby version to `3.3.0` (was `3.2.0`)
22
+ - Updated the gem `dotenv` to version `3.1` (was `2.8`)
23
+ - Updated the gem `bip-schnorr` to version `0.7` (was `0.6`)
24
+ - Updated the gem `overcommit` to version `0.63` (was `0.59`)
25
+ - Updated the gem `rbs` to version `3.4` (was `3.3`)
26
+ - Updated the gem `rspec` to version `3.13` (was `3.12`)
27
+ - Updated the gem `rspec-rubocop` to version `2.27` (was `2.25`)
28
+ - Updated the gem `rubocop` to version `1.62` (was `1.57`)
29
+
30
+ ## Fixed
31
+
32
+ - Fixed a typo in the README (`generate_keypair` -> `generate_key_pair`)
33
+ - Fixed a typo in the YARD documentation of `Nostr::Key#initialize` (`ValidationError` -> `KeyValidationError`)
34
+
35
+ ### Fixed
36
+
37
+ - Fixed YARD example rendering issues in `InvalidKeyFormatError#initialize`, `InvalidKeyLengthError#initialize`,
38
+ `InvalidKeyTypeError#initialize`, `Event#initialize`, `EncryptedDirectMessage#initialize` and `Filter#to_h`
39
+
7
40
  ## [0.5.0] 2023-11-20
8
41
 
9
42
  ### Added
10
43
 
11
44
  - Added relay message type enums `Nostr::RelayMessageType`
12
45
  - Compliance with [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) - bech32-formatted strings
13
- - `Nostr::PrivateKey` and `Nostr::PublicKey` to represent private and public keys, respectively
46
+ - Added `Nostr::PrivateKey` and `Nostr::PublicKey` to represent private and public keys, respectively
14
47
  - Added a validation of private and public keys
15
48
  - Added an ability to convert keys to and from Bech32 format
16
49
  - Added RBS types for `faye-websocket` and `bech32`
@@ -82,6 +115,7 @@ principles of immutability and was a major source of internal complexity as I ne
82
115
 
83
116
  - Initial release
84
117
 
118
+ [0.6.0]: https://github.com/wilsonsilva/nostr/compare/v0.5.0...v0.6.0
85
119
  [0.5.0]: https://github.com/wilsonsilva/nostr/compare/v0.4.0...v0.5.0
86
120
  [0.4.0]: https://github.com/wilsonsilva/nostr/compare/v0.3.0...v0.4.0
87
121
  [0.3.0]: https://github.com/wilsonsilva/nostr/compare/v0.2.0...v0.3.0
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Nostr
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/nostr.svg)](https://badge.fury.io/rb/nostr)
4
+ ![Build](https://github.com/wilsonsilva/nostr/actions/workflows/main.yml/badge.svg)
4
5
  [![Maintainability](https://api.codeclimate.com/v1/badges/c7633eb2c89eb95ee7f2/maintainability)](https://codeclimate.com/github/wilsonsilva/nostr/maintainability)
5
6
  [![Test Coverage](https://api.codeclimate.com/v1/badges/c7633eb2c89eb95ee7f2/test_coverage)](https://codeclimate.com/github/wilsonsilva/nostr/test_coverage)
6
7
 
@@ -63,7 +64,7 @@ keypair = keygen.get_key_pair_from_private_key(
63
64
 
64
65
  # c) Or create a new keypair
65
66
  keygen = Nostr::Keygen.new
66
- keypair = keygen.generate_keypair
67
+ keypair = keygen.generate_key_pair
67
68
 
68
69
  # Create a user with the keypair
69
70
  user = Nostr::User.new(keypair: keypair)
@@ -0,0 +1,19 @@
1
+ # 1. Record architecture decisions
2
+
3
+ Date: 2024-03-13
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ We need to record the architectural decisions made on this project.
12
+
13
+ ## Decision
14
+
15
+ We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
16
+
17
+ ## Consequences
18
+
19
+ See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).
@@ -0,0 +1,27 @@
1
+ # 2. introduction-of-signature-class
2
+
3
+ Date: 2024-03-14
4
+
5
+ ## Status
6
+
7
+ Accepted
8
+
9
+ ## Context
10
+
11
+ I noticed significant overuse of primitive strings for signatures, which led to widespread and repetitive validation logic, increasing the potential for errors and making the system harder to manage and maintain.
12
+
13
+ ## Decision
14
+
15
+ I introduced the Nostr::Signature class, choosing to subclass String to leverage string-like behavior while embedding specific validation rules for signatures. This move was aimed at streamlining validation, ensuring consistency, and maintaining the usability of strings.
16
+
17
+ ## Consequences
18
+
19
+ ### Positive
20
+
21
+ - This design choice has made the codebase cleaner and more robust, reducing the chances of errors related to signature handling. It ensures that all signature instances are valid at creation, leveraging the familiarity and flexibility of string operations without sacrificing the integrity of the data. Moreover, it sets a strong foundation for extending signature-related functionality in the future.
22
+
23
+ ### Negative
24
+
25
+ - __Performance Concerns:__ Subclassing String might introduce slight performance overheads due to the additional validation logic executed upon instantiation of a Signature object.
26
+ - __Integration Challenges:__ Integrating this class into existing systems where strings were used indiscriminately for signatures requires careful refactoring to ensure compatibility. There's also the potential for issues when passing Nostr::Signature objects to libraries or APIs expecting plain strings without the additional constraints.
27
+ - __Learning Curve:__ For new team members or contributors, understanding the necessity and functionality of the Nostr::Signature class adds to the learning curve, potentially slowing down initial development efforts as they familiarize themselves with the custom implementation.
@@ -79,6 +79,8 @@ export default defineConfig(withMermaid({
79
79
  collapsed: false,
80
80
  items: [
81
81
  { text: 'Bech32 enc/decoding (NIP-19)', link: '/common-use-cases/bech32-encoding-and-decoding-(NIP-19)' },
82
+ { text: 'Signing/verifying messages', link: '/common-use-cases/signing-and-verifying-messages' },
83
+ { text: 'Signing/verifying events', link: '/common-use-cases/signing-and-verifying-events' },
82
84
  ]
83
85
  },
84
86
  {
@@ -0,0 +1,50 @@
1
+ # Signing and verifying events
2
+
3
+ Signing an event in Nostr proves it was sent by the owner of a specific private key.
4
+
5
+ ## Signing an event
6
+
7
+ To sign an event, use the private key associated with the event's creator. Here's how to sign a message using a
8
+ predefined keypair:
9
+
10
+ ```ruby{14}
11
+ require 'nostr'
12
+
13
+ private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
14
+ public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
15
+
16
+ event = Nostr::Event.new(
17
+ pubkey: public_key.to_s,
18
+ kind: Nostr::EventKind::TEXT_NOTE,
19
+ content: 'We did it with security, now we’re going to do it with the economy.',
20
+ created_at: Time.now.to_i,
21
+ )
22
+
23
+ # Sign the event with the private key
24
+ event.sign(private_key)
25
+
26
+ puts "Event ID: #{event.id}"
27
+ puts "Event Signature: #{event.sig}"
28
+ ```
29
+
30
+ ## Verifying an event's signature
31
+
32
+ To verify an event, you must ensure the event's signature is valid. This indicates the event was created by the owner
33
+ of the corresponding public key.
34
+
35
+ When the event was signed with the private key corresponding to the public key, the `verify_signature` method will
36
+ return `true`.
37
+
38
+ ```ruby
39
+ event.verify_signature # => true
40
+ ```
41
+
42
+ And when the event was not signed with the private key corresponding to the public key, the `verify_signature` method
43
+ will return `false`.
44
+
45
+ An event without an `id`, `pubkey`, `sig` is considered invalid and will return `false` when calling `verify_signature`.
46
+
47
+ ```ruby
48
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
49
+ event.verify_signature # => false
50
+ ```
@@ -0,0 +1,43 @@
1
+ # Signing and verifying messages
2
+
3
+ Signing a message in Nostr proves it was sent by the owner of a specific private key.
4
+
5
+ ## Signing a message
6
+
7
+ To sign a message, you'll need a private key. Here's how to sign a message using a predefined keypair:
8
+
9
+ ```ruby{9}
10
+ require 'nostr'
11
+
12
+ private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
13
+ public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
14
+
15
+ message = 'We did it with security, now we’re going to do it with the economy.' # The message you want to sign
16
+
17
+ crypto = Nostr::Crypto.new
18
+ signature = crypto.sign_message(message, private_key)
19
+ signature # => "d7a0aac1fadcddf1aa2949bedfcdf25ce0c1604e648e55d31431fdacbff8e8256f7c2166d98292f80bc5f79105a0b6e8a89236a47d97cf5d0e7cc1ebf34dea5c"
20
+ ```
21
+
22
+ ## Verifying a signature
23
+
24
+ To verify a signature, you need the original message, the public key of the signer, and the signature.
25
+
26
+ ```ruby
27
+ crypto.valid_sig?(message, public_key, signature) # => true
28
+ crypto.check_sig!(message, public_key, signature) # => true
29
+ ```
30
+
31
+ When the message was not signed with the private key corresponding to the public key, the `valid_sig?` method will return `false`.
32
+
33
+ ```ruby
34
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
35
+ crypto.valid_sig?(message, public_key, signature) # => false
36
+ ```
37
+
38
+ And when the message was not signed with the private key corresponding to the public key, the `check_sig!` method will raise an error.
39
+
40
+ ```ruby
41
+ other_public_key = Nostr::PublicKey.new('10be96d345ed58d923a734560680f1adfd2b1006c28ac93b8e1b032a9a32c6e9')
42
+ crypto.check_sig!(message, other_public_key, signature) # => Schnorr::InvalidSignatureError: signature verification failed
43
+ ```
@@ -35,6 +35,7 @@ classDiagram
35
35
  serialize()
36
36
  to_h()
37
37
  sign(private_key)
38
+ verify_signature()
38
39
  }
39
40
  class Subscription {
40
41
  id
@@ -110,7 +111,7 @@ keypair = keygen.get_key_pair_from_private_key(
110
111
 
111
112
  # c) Or create a new keypair
112
113
  keygen = Nostr::Keygen.new
113
- keypair = keygen.generate_keypair
114
+ keypair = keygen.generate_key_pair
114
115
 
115
116
  # Create a user with the keypair
116
117
  user = Nostr::User.new(keypair: keypair)
data/docs/index.md CHANGED
@@ -39,6 +39,4 @@ features:
39
39
  - title: Fully typed
40
40
  details: All code is typed with <a href="https://rubygems.org/gems/rbs" target="_blank">RBS</a> with the help of <a href="https://rubygems.org/gems/typeprof" target="_blank">TypeProf</a>. Type correctness is enforced by <a href="https://rubygems.org/gems/steep" target="_blank">Steep</a>.
41
41
  icon: ✅
42
-
43
42
  ---
44
-
data/lib/nostr/crypto.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Nostr
4
- # Performs cryptographic operations on a +Nostr::Event+.
4
+ # Performs cryptographic operations.
5
5
  class Crypto
6
6
  # Numeric base of the OpenSSL big number used in an event content's encryption.
7
7
  #
@@ -90,17 +90,93 @@ module Nostr
90
90
  #
91
91
  def sign_event(event, private_key)
92
92
  event_digest = hash_event(event)
93
-
94
- hex_private_key = Array(private_key).pack('H*')
95
- hex_message = Array(event_digest).pack('H*')
96
- event_signature = Schnorr.sign(hex_message, hex_private_key).encode.unpack1('H*')
93
+ signature = sign_message(event_digest, private_key)
97
94
 
98
95
  event.id = event_digest
99
- event.sig = event_signature
96
+ event.sig = signature
100
97
 
101
98
  event
102
99
  end
103
100
 
101
+ # Signs a message using the Schnorr signature algorithm
102
+ #
103
+ # @api public
104
+ #
105
+ # @example Signing a message
106
+ # crypto = Nostr::Crypto.new
107
+ # message = 'Viva la libertad carajo'
108
+ # private_key = Nostr::PrivateKey.new('7d1e4219a5e7d8342654c675085bfbdee143f0eb0f0921f5541ef1705a2b407d')
109
+ # signature = crypto.sign_message(message, private_key)
110
+ # signature # => 'b2115694a576f5bdcebf8c0951a3c7adcfbdb17b11cb9e6d6b7017691138bc6' \
111
+ # '38fee642a7bd26f71b313a7057181294198900a9770d1435e43f182acf3d34c26'
112
+ #
113
+ # @param [String] message The message to be signed
114
+ # @param [PrivateKey] private_key The private key used for signing
115
+ #
116
+ # @return [Signature] A signature object containing the signature as a 64-byte hexadecimal string.
117
+ #
118
+ def sign_message(message, private_key)
119
+ hex_private_key = Array(private_key).pack('H*')
120
+ hex_message = Array(message).pack('H*')
121
+ hex_signature = Schnorr.sign(hex_message, hex_private_key).encode.unpack1('H*')
122
+
123
+ Signature.new(hex_signature.to_s)
124
+ end
125
+
126
+ # Verifies the given {Signature} and returns true if it is valid
127
+ #
128
+ # @api public
129
+ #
130
+ # @example Checking a signature
131
+ # public_key = Nostr::PublicKey.new('15678d8fbc126fa326fac536acd5a6dcb5ef64b3d939abe31d6830cba6cd26d6')
132
+ # private_key = Nostr::PrivateKey.new('7d1e4219a5e7d8342654c675085bfbdee143f0eb0f0921f5541ef1705a2b407d')
133
+ # message = 'Viva la libertad carajo'
134
+ # crypto = Nostr::Crypto.new
135
+ # signature = crypto.sign_message(message, private_key)
136
+ # valid = crypto.valid_sig?(message, public_key, signature)
137
+ # valid # => true
138
+ #
139
+ # @see #check_sig!
140
+ #
141
+ # @param [String] message A message to be signed with binary format.
142
+ # @param [PublicKey] public_key The public key with binary format.
143
+ # @param [Signature] signature The signature with binary format.
144
+ #
145
+ # @return [Boolean] whether signature is valid.
146
+ #
147
+ def valid_sig?(message, public_key, signature)
148
+ signature = Schnorr::Signature.decode([signature].pack('H*'))
149
+ Schnorr.valid_sig?([message].pack('H*'), [public_key].pack('H*'), signature.encode)
150
+ end
151
+
152
+ # Verifies the given {Signature} and raises an +Schnorr::InvalidSignatureError+ if it is invalid
153
+ #
154
+ # @api public
155
+ #
156
+ # @example Checking a signature
157
+ # public_key = Nostr::PublicKey.new('15678d8fbc126fa326fac536acd5a6dcb5ef64b3d939abe31d6830cba6cd26d6')
158
+ # private_key = Nostr::PrivateKey.new('7d1e4219a5e7d8342654c675085bfbdee143f0eb0f0921f5541ef1705a2b407d')
159
+ # message = 'Viva la libertad carajo'
160
+ # crypto = Nostr::Crypto.new
161
+ # signature = crypto.sign_message(message, private_key)
162
+ # valid = crypto.valid_sig?(message, public_key, signature)
163
+ # valid # => true
164
+ #
165
+ # @see #valid_sig?
166
+ #
167
+ # @param [String] message A message to be signed with binary format.
168
+ # @param [PublicKey] public_key The public key with binary format.
169
+ # @param [Signature] signature The signature with binary format.
170
+ #
171
+ # @raise [Schnorr::InvalidSignatureError] if the signature is invalid.
172
+ #
173
+ # @return [Boolean] whether signature is valid.
174
+ #
175
+ def check_sig!(message, public_key, signature)
176
+ signature = Schnorr::Signature.decode([signature].pack('H*'))
177
+ Schnorr.check_sig!([message].pack('H*'), [public_key].pack('H*'), signature.encode)
178
+ end
179
+
104
180
  private
105
181
 
106
182
  # Finds a shared key between two keys
@@ -9,7 +9,7 @@ module Nostr
9
9
  # Initializes the error
10
10
  #
11
11
  # @example
12
- # InvalidKeyFormatError.new('private'')
12
+ # InvalidKeyFormatError.new('private')
13
13
  #
14
14
  # @param [String] key_kind The kind of key that is invalid (public or private)
15
15
  #
@@ -9,7 +9,7 @@ module Nostr
9
9
  # Initializes the error
10
10
  #
11
11
  # @example
12
- # InvalidKeyLengthError.new('private'')
12
+ # InvalidKeyLengthError.new('private')
13
13
  #
14
14
  # @param [String] key_kind The kind of key that is invalid (public or private)
15
15
  #
@@ -9,7 +9,7 @@ module Nostr
9
9
  # Initializes the error
10
10
  #
11
11
  # @example
12
- # InvalidKeyTypeError.new('private'')
12
+ # InvalidKeyTypeError.new('private')
13
13
  #
14
14
  # @param [String] key_kind The kind of key that is invalid (public or private)
15
15
  #
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the signature is in an invalid format
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidSignatureFormatError < SignatureValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidSignatureFormatError.new
13
+ #
14
+ def initialize
15
+ super('Only lowercase hexadecimal characters are allowed in signatures.')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the signature's length is not 128 characters
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidSignatureLengthError < SignatureValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidSignatureLengthError.new
13
+ #
14
+ def initialize
15
+ super('Invalid signature length. It should have 128 characters.')
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the signature is not a string
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidSignatureTypeError < SignatureValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidSignatureTypeError.new
13
+ #
14
+ def initialize = super('Invalid signature type')
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Base class for all signature validation errors
5
+ class SignatureValidationError < Error; end
6
+ end
data/lib/nostr/errors.rb CHANGED
@@ -6,3 +6,7 @@ require_relative 'errors/invalid_hrp_error'
6
6
  require_relative 'errors/invalid_key_type_error'
7
7
  require_relative 'errors/invalid_key_length_error'
8
8
  require_relative 'errors/invalid_key_format_error'
9
+ require_relative 'errors/signature_validation_error'
10
+ require_relative 'errors/invalid_signature_type_error'
11
+ require_relative 'errors/invalid_signature_length_error'
12
+ require_relative 'errors/invalid_signature_format_error'
data/lib/nostr/event.rb CHANGED
@@ -100,15 +100,15 @@ module Nostr
100
100
  #
101
101
  # @example Instantiating a new event
102
102
  # Nostr::Event.new(
103
- # id: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
104
- # pubkey: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
105
- # created_at: 1230981305,
106
- # kind: 1,
107
- # tags: [],
108
- # content: 'Your feedback is appreciated, now pay $8',
109
- # sig: '123ac2923b792ce730b3da34f16155470ab13c8f97f9c53eaeb334f1fb3a5dc9a7f643
110
- # 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
111
- # )
103
+ # id: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
104
+ # pubkey: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
105
+ # created_at: 1230981305,
106
+ # kind: 1,
107
+ # tags: [],
108
+ # content: 'Your feedback is appreciated, now pay $8',
109
+ # sig: '123ac2923b792ce730b3da34f16155470ab13c8f97f9c53eaeb334f1fb3a5dc9a7f643
110
+ # 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
111
+ # )
112
112
  #
113
113
  # @param id [String|nil] 32-bytes sha256 of the the serialized event data.
114
114
  # @param sig [String|nil] 64-bytes signature of the sha256 hash of the serialized event data, which is
@@ -181,6 +181,34 @@ module Nostr
181
181
  crypto.sign_event(self, private_key)
182
182
  end
183
183
 
184
+ # Verifies if the signature of the event is valid. A valid signature means that the event was signed by the owner
185
+ #
186
+ # @api public
187
+ #
188
+ # @example Verifying the signature of an event
189
+ # event = Nostr::Event.new(
190
+ # id: '90b75b78daf883ae57fbcc414d43faa028560b3211ee58e4ea82bf395bb82042',
191
+ # pubkey: Nostr::PublicKey.new('2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558'),
192
+ # created_at: 1667422587,
193
+ # kind: Nostr::EventKind::TEXT_NOTE,
194
+ # content: 'Your feedback is appreciated, now pay $8',
195
+ # sig: '32f18adebe942e19b171c1c7d2fb27ce794dfea4155e289dca7952b43ed1ec39' \
196
+ # '1d3dc198ba2761bc6d40c737a6eaf4edcc8963acabd3bfcebd04f16637025bdc'
197
+ # )
198
+ #
199
+ # event.verify_signature # => true
200
+ #
201
+ # @return [Boolean] Whether the signature is valid or not.
202
+ #
203
+ def verify_signature
204
+ crypto = Crypto.new
205
+
206
+ return false if id.nil? || pubkey.nil?
207
+ return false if sig.nil? # FIXME: See https://github.com/soutaro/steep/issues/1079
208
+
209
+ crypto.valid_sig?(id, pubkey, sig)
210
+ end
211
+
184
212
  # Serializes the event, to obtain a SHA256 digest of it
185
213
  #
186
214
  # @api public
@@ -21,6 +21,7 @@ module Nostr
21
21
  # The content is set to the URL (e.g., wss://somerelay.com) of a relay the event creator wants to
22
22
  # recommend to its followers.
23
23
  #
24
+ # @deprecated This event kind was removed in https://github.com/nostr-protocol/nips/pull/703/files#diff-39307f1617417657ee9874be314f13aabdc74401b124d0afe8217f2919c9c7d8L105
24
25
  # @return [Integer]
25
26
  #
26
27
  RECOMMEND_SERVER = 2
@@ -11,19 +11,19 @@ module Nostr
11
11
  # @api public
12
12
  #
13
13
  # @example Instantiating a new encrypted direct message
14
- # Nostr::Events::EncryptedDirectMessage.new(
14
+ # Nostr::Events::EncryptedDirectMessage.new(
15
15
  # sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
16
16
  # recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
17
17
  # plain_text: 'Your feedback is appreciated, now pay $8',
18
- # )
18
+ # )
19
19
  #
20
20
  # @example Instantiating a new encrypted direct message that references a previous direct message
21
- # Nostr::Events::EncryptedDirectMessage.new(
21
+ # Nostr::Events::EncryptedDirectMessage.new(
22
22
  # sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
23
23
  # recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
24
24
  # plain_text: 'Your feedback is appreciated, now pay $8',
25
25
  # previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
26
- # )
26
+ # )
27
27
  #
28
28
  # @param plain_text [String] The +content+ of the encrypted message.
29
29
  # @param sender_private_key [PrivateKey] 32-bytes hex-encoded private key of the message's author.
data/lib/nostr/filter.rb CHANGED
@@ -133,13 +133,16 @@ module Nostr
133
133
  # @api public
134
134
  #
135
135
  # @example
136
- # filter.to_h # => {:ids=>["c24881c305c5cfb7c1168be7e9b0e150"],
137
- # :authors=>["000000001c5c45196786e79f83d21fe801549fdc98e2c26f96dcef068a5dbcd7"],
138
- # :kinds=>[0, 1, 2],
139
- # :"#e"=>["7bdb422f254194ae4bb86d354c0bd5a888fce233ffc77dceb3e844ceec1fcfb2"],
140
- # :"#p"=>["000000001c5c45196786e79f83d21fe801549fdc98e2c26f96dcef068a5dbcd7"],
141
- # :since=>1230981305,
142
- # :until=>1292190341}
136
+ # filter.to_h # =>
137
+ # {
138
+ # ids: ['c24881c305c5cfb7c1168be7e9b0e150'],
139
+ # authors: ['000000001c5c45196786e79f83d21fe801549fdc98e2c26f96dcef068a5dbcd7'],
140
+ # kinds: [0, 1, 2],
141
+ # '#e': ['7bdb422f254194ae4bb86d354c0bd5a888fce233ffc77dceb3e844ceec1fcfb2'],
142
+ # '#p': ['000000001c5c45196786e79f83d21fe801549fdc98e2c26f96dcef068a5dbcd7'],
143
+ # since: 1230981305,
144
+ # until: 1292190341
145
+ # }
143
146
  #
144
147
  # @return [Hash] The filter as a hash.
145
148
  #
data/lib/nostr/key.rb CHANGED
@@ -18,14 +18,14 @@ module Nostr
18
18
  #
19
19
  LENGTH = 64
20
20
 
21
- # Instantiates a new key. Can't be used directly because this is an abstract class. Raises a +ValidationError+
21
+ # Instantiates a new key. Can't be used directly because this is an abstract class. Raises a +KeyValidationError+
22
22
  #
23
23
  # @see Nostr::PrivateKey
24
24
  # @see Nostr::PublicKey
25
25
  #
26
26
  # @param [String] hex_value Hex-encoded value of the key
27
27
  #
28
- # @raise [ValidationError]
28
+ # @raise [KeyValidationError]
29
29
  #
30
30
  def initialize(hex_value)
31
31
  validate_hex_value(hex_value)
@@ -38,8 +38,8 @@ module Nostr
38
38
  # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
39
39
  # @param public_key [PublicKey] 32-bytes hex-encoded public key.
40
40
  #
41
- # @raise ArgumentError when the private key is not a +PrivateKey+
42
- # @raise ArgumentError when the public key is not a +PublicKey+
41
+ # @raise ArgumentError when the private key is not a {PrivateKey}
42
+ # @raise ArgumentError when the public key is not a {PublicKey}
43
43
  #
44
44
  def initialize(private_key:, public_key:)
45
45
  validate_keys(private_key, public_key)
@@ -48,6 +48,30 @@ module Nostr
48
48
  @public_key = public_key
49
49
  end
50
50
 
51
+ # Allows array destructuring of the KeyPair, enabling the extraction of +PrivateKey+ and +PublicKey+ separately
52
+ #
53
+ # @api public
54
+ #
55
+ # @example Implicit usage of `to_ary` for destructuring
56
+ # keypair = Nostr::KeyPair.new(
57
+ # private_key: Nostr::PrivateKey.new('7d1e4219a5e7d8342654c675085bfbdee143f0eb0f0921f5541ef1705a2b407d'),
58
+ # public_key: Nostr::PublicKey.new('15678d8fbc126fa326fac536acd5a6dcb5ef64b3d939abe31d6830cba6cd26d6'),
59
+ # )
60
+ # # The `to_ary` method can be implicitly used for array destructuring:
61
+ # private_key, public_key = keypair
62
+ # # Now `private_key` and `public_key` hold the respective values.
63
+ #
64
+ # @example Explicit usage of `to_ary`
65
+ # array_representation = keypair.to_ary
66
+ # # array_representation is now an array: [PrivateKey, PublicKey]
67
+ # # where PrivateKey and PublicKey are the respective objects.
68
+ #
69
+ # @return [Array<PrivateKey, PublicKey>] An array containing the {PrivateKey} and {PublicKey} in that order
70
+ #
71
+ def to_ary
72
+ [private_key, public_key]
73
+ end
74
+
51
75
  private
52
76
 
53
77
  # Validates the keys
data/lib/nostr/keygen.rb CHANGED
@@ -22,7 +22,7 @@ module Nostr
22
22
  # @api public
23
23
  #
24
24
  # @example
25
- # keypair = keygen.generate_keypair
25
+ # keypair = keygen.generate_key_pair
26
26
  # keypair # #<Nostr::KeyPair:0x0000000107bd3550
27
27
  # @private_key="893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900",
28
28
  # @public_key="2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558">
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # 64-bytes lowercase hex of the signature of the sha256 hash of the serialized event data,
5
+ # which is the same as the "id" field
6
+ class Signature < String
7
+ # The regular expression for hexadecimal lowercase characters
8
+ #
9
+ # @return [Regexp] The regular expression for hexadecimal lowercase characters
10
+ #
11
+ FORMAT = /^[a-f0-9]+$/
12
+
13
+ # The length of the signature in hex format
14
+ #
15
+ # @return [Integer] The length of the signature in hex format
16
+ #
17
+ LENGTH = 128
18
+
19
+ # Instantiates a new Signature
20
+ #
21
+ # @api public
22
+ #
23
+ # @example Instantiating a new signature
24
+ # Nostr::Signature.new(
25
+ # 'f418c97b50cc68227e82f4f3a79d79eb2b7a0fa517859c86e1a8fa91e3741b7f' \
26
+ # '06e070c44129227b83fcbe93cecb02a346804a4080ce47685ecad60ab4f5f128'
27
+ # )
28
+ #
29
+ # @param [String] hex_value Hex-encoded value of the signature
30
+ #
31
+ # @raise [SignatureValidationError]
32
+ #
33
+ def initialize(hex_value)
34
+ validate(hex_value)
35
+
36
+ super(hex_value)
37
+ end
38
+
39
+ private
40
+
41
+ # Hex-encoded value of the signature
42
+ #
43
+ # @api private
44
+ #
45
+ # @return [String] hex_value Hex-encoded value of the signature
46
+ #
47
+ attr_reader :hex_value
48
+
49
+ # Validates the hex value of the signature
50
+ #
51
+ # @api private
52
+ #
53
+ # @param [String] hex_value The signature in hex format
54
+ #
55
+ # @raise InvalidSignatureTypeError when the signature is not a string
56
+ # @raise InvalidSignatureLengthError when the signature's length is not 128 characters
57
+ # @raise InvalidSignatureFormatError when the signature is in an invalid format
58
+ #
59
+ # @return [void]
60
+ #
61
+ def validate(hex_value)
62
+ raise InvalidSignatureTypeError unless hex_value.is_a?(String)
63
+ raise InvalidSignatureLengthError unless hex_value.size == LENGTH
64
+ raise InvalidSignatureFormatError unless hex_value.match(FORMAT)
65
+ end
66
+ end
67
+ end
data/lib/nostr/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Nostr
4
4
  # The version of the gem
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
data/lib/nostr.rb CHANGED
@@ -12,6 +12,7 @@ require_relative 'nostr/relay'
12
12
  require_relative 'nostr/relay_message_type'
13
13
  require_relative 'nostr/key_pair'
14
14
  require_relative 'nostr/event_kind'
15
+ require_relative 'nostr/signature'
15
16
  require_relative 'nostr/event'
16
17
  require_relative 'nostr/events/encrypted_direct_message'
17
18
  require_relative 'nostr/client'
data/nostr.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = 'Client and relay implementation of the Nostr protocol.'
13
13
  spec.homepage = 'https://nostr-ruby.com/'
14
14
  spec.license = 'MIT'
15
- spec.required_ruby_version = '>= 3.2.0'
15
+ spec.required_ruby_version = '>= 3.3.0'
16
16
  spec.metadata['rubygems_mfa_required'] = 'true'
17
17
 
18
18
  spec.metadata['homepage_uri'] = spec.homepage
@@ -32,29 +32,29 @@ Gem::Specification.new do |spec|
32
32
  spec.require_paths = ['lib']
33
33
 
34
34
  spec.add_dependency 'bech32', '~> 1.4'
35
- spec.add_dependency 'bip-schnorr', '~> 0.6'
35
+ spec.add_dependency 'bip-schnorr', '~> 0.7'
36
36
  spec.add_dependency 'ecdsa', '~> 1.2'
37
37
  spec.add_dependency 'event_emitter', '~> 0.2'
38
38
  spec.add_dependency 'faye-websocket', '~> 0.11'
39
39
  spec.add_dependency 'json', '~> 2.6'
40
40
 
41
41
  spec.add_development_dependency 'bundler-audit', '~> 0.9'
42
- spec.add_development_dependency 'dotenv', '~> 2.8'
42
+ spec.add_development_dependency 'dotenv', '~> 3.1'
43
43
  spec.add_development_dependency 'guard', '~> 2.18'
44
44
  spec.add_development_dependency 'guard-bundler', '~> 3.0'
45
45
  spec.add_development_dependency 'guard-bundler-audit', '~> 0.1'
46
46
  spec.add_development_dependency 'guard-rspec', '~> 4.7'
47
47
  spec.add_development_dependency 'guard-rubocop', '~> 1.5'
48
- spec.add_development_dependency 'overcommit', '~> 0.59'
48
+ spec.add_development_dependency 'overcommit', '~> 0.63'
49
49
  spec.add_development_dependency 'pry', '~> 0.14'
50
50
  spec.add_development_dependency 'puma', '~> 6.4'
51
51
  spec.add_development_dependency 'rack', '~> 3.0'
52
52
  spec.add_development_dependency 'rake', '~> 13.1'
53
- spec.add_development_dependency 'rbs', '~> 3.3'
54
- spec.add_development_dependency 'rspec', '~> 3.12'
55
- spec.add_development_dependency 'rubocop', '~> 1.57'
53
+ spec.add_development_dependency 'rbs', '~> 3.4'
54
+ spec.add_development_dependency 'rspec', '~> 3.13'
55
+ spec.add_development_dependency 'rubocop', '~> 1.62'
56
56
  spec.add_development_dependency 'rubocop-rake', '~> 0.6'
57
- spec.add_development_dependency 'rubocop-rspec', '2.25'
57
+ spec.add_development_dependency 'rubocop-rspec', '2.27'
58
58
  spec.add_development_dependency 'simplecov', '= 0.17'
59
59
  spec.add_development_dependency 'simplecov-console', '~> 0.9'
60
60
  spec.add_development_dependency 'steep', '~> 1.6'
data/sig/nostr/crypto.rbs CHANGED
@@ -7,6 +7,9 @@ module Nostr
7
7
  def encrypt_text: (PrivateKey, PublicKey, String) -> String
8
8
  def decrypt_text: (PrivateKey, PublicKey, String) -> String
9
9
  def sign_event: (Event, PrivateKey) -> Event
10
+ def sign_message: (String, PrivateKey) -> Signature
11
+ def valid_sig?: (String, PublicKey, Signature) -> bool
12
+ def check_sig!: (String, PublicKey, Signature) -> bool
10
13
 
11
14
  private
12
15
 
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidSignatureFormatError < SignatureValidationError
3
+ def initialize: -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidSignatureLengthError < SignatureValidationError
3
+ def initialize: -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidSignatureTypeError < SignatureValidationError
3
+ def initialize: -> void
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Nostr
2
+ class SignatureValidationError < Error
3
+ end
4
+ end
data/sig/nostr/event.rbs CHANGED
@@ -5,8 +5,8 @@ module Nostr
5
5
  attr_reader kind: Integer
6
6
  attr_reader tags: Array[Array[String]]
7
7
  attr_reader content: String
8
- attr_accessor id: String?|nil
9
- attr_accessor sig: String?|nil
8
+ attr_accessor id: String?
9
+ attr_accessor sig: Signature?
10
10
 
11
11
  def initialize: (
12
12
  pubkey: PublicKey,
@@ -14,24 +14,25 @@ module Nostr
14
14
  content: String,
15
15
  ?created_at: Integer,
16
16
  ?tags: Array[Array[String]],
17
- ?id: String|nil,
18
- ?sig: String|nil
17
+ ?id: String?,
18
+ ?sig: Signature?
19
19
  ) -> void
20
20
 
21
21
  def serialize: -> [Integer, String, Integer, Integer, Array[Array[String]], String]
22
22
 
23
23
  def to_h: -> {
24
- id: String?|nil,
24
+ id: String?,
25
25
  pubkey: String,
26
26
  created_at: Integer,
27
27
  kind: Integer,
28
28
  tags: Array[Array[String]],
29
29
  content: String,
30
- sig: String?|nil
30
+ sig: String?
31
31
  }
32
32
  def ==: (Event other) -> bool
33
33
 
34
34
  def sign:(PrivateKey) -> Event
35
+ def verify_signature: -> bool
35
36
 
36
37
  def add_event_reference: (String) -> Array[Array[String]]
37
38
  def add_pubkey_reference: (PublicKey) -> Array[Array[String]]
@@ -5,6 +5,7 @@ module Nostr
5
5
  attr_reader public_key: PublicKey
6
6
 
7
7
  def initialize: (private_key: PrivateKey, public_key: PublicKey) -> void
8
+ def to_ary: -> [PrivateKey, PublicKey]
8
9
 
9
10
  private
10
11
 
@@ -0,0 +1,14 @@
1
+ module Nostr
2
+ class Signature < String
3
+ FORMAT: Regexp
4
+ LENGTH: int
5
+
6
+ def initialize: (String) -> void
7
+
8
+ private
9
+
10
+ attr_reader hex_value: String
11
+
12
+ def validate: (String) -> nil
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # Added only to satisfy the Steep requirements. Not 100% reliable.
2
+ module Schnorr
3
+ class InvalidSignatureError < StandardError
4
+ end
5
+
6
+ class Signature
7
+ attr_reader r: Integer
8
+ attr_reader s: Integer
9
+
10
+ def self.decode: (String string) -> Signature
11
+
12
+ def initialize: (Integer r, Integer s) -> void
13
+ def encode: -> String
14
+ def ==: (untyped other) -> bool
15
+ end
16
+ end
@@ -1,4 +1,6 @@
1
1
  # Added only to satisfy the Steep requirements. Not 100% reliable.
2
2
  module Schnorr
3
- def self.sign: (String message, String private_key, ?String aux_rand) -> untyped
3
+ def self.sign: (String message, String private_key, ?String aux_rand) -> Signature
4
+ def self.valid_sig?: (String message, String public_key, String signature) -> bool
5
+ def self.check_sig!: (String message, String public_key, String signature) -> bool
4
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nostr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wilson Silva
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-20 00:00:00.000000000 Z
11
+ date: 2024-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bech32
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.6'
33
+ version: '0.7'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.6'
40
+ version: '0.7'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: ecdsa
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '2.8'
117
+ version: '3.1'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '2.8'
124
+ version: '3.1'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: guard
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -198,14 +198,14 @@ dependencies:
198
198
  requirements:
199
199
  - - "~>"
200
200
  - !ruby/object:Gem::Version
201
- version: '0.59'
201
+ version: '0.63'
202
202
  type: :development
203
203
  prerelease: false
204
204
  version_requirements: !ruby/object:Gem::Requirement
205
205
  requirements:
206
206
  - - "~>"
207
207
  - !ruby/object:Gem::Version
208
- version: '0.59'
208
+ version: '0.63'
209
209
  - !ruby/object:Gem::Dependency
210
210
  name: pry
211
211
  requirement: !ruby/object:Gem::Requirement
@@ -268,42 +268,42 @@ dependencies:
268
268
  requirements:
269
269
  - - "~>"
270
270
  - !ruby/object:Gem::Version
271
- version: '3.3'
271
+ version: '3.4'
272
272
  type: :development
273
273
  prerelease: false
274
274
  version_requirements: !ruby/object:Gem::Requirement
275
275
  requirements:
276
276
  - - "~>"
277
277
  - !ruby/object:Gem::Version
278
- version: '3.3'
278
+ version: '3.4'
279
279
  - !ruby/object:Gem::Dependency
280
280
  name: rspec
281
281
  requirement: !ruby/object:Gem::Requirement
282
282
  requirements:
283
283
  - - "~>"
284
284
  - !ruby/object:Gem::Version
285
- version: '3.12'
285
+ version: '3.13'
286
286
  type: :development
287
287
  prerelease: false
288
288
  version_requirements: !ruby/object:Gem::Requirement
289
289
  requirements:
290
290
  - - "~>"
291
291
  - !ruby/object:Gem::Version
292
- version: '3.12'
292
+ version: '3.13'
293
293
  - !ruby/object:Gem::Dependency
294
294
  name: rubocop
295
295
  requirement: !ruby/object:Gem::Requirement
296
296
  requirements:
297
297
  - - "~>"
298
298
  - !ruby/object:Gem::Version
299
- version: '1.57'
299
+ version: '1.62'
300
300
  type: :development
301
301
  prerelease: false
302
302
  version_requirements: !ruby/object:Gem::Requirement
303
303
  requirements:
304
304
  - - "~>"
305
305
  - !ruby/object:Gem::Version
306
- version: '1.57'
306
+ version: '1.62'
307
307
  - !ruby/object:Gem::Dependency
308
308
  name: rubocop-rake
309
309
  requirement: !ruby/object:Gem::Requirement
@@ -324,14 +324,14 @@ dependencies:
324
324
  requirements:
325
325
  - - '='
326
326
  - !ruby/object:Gem::Version
327
- version: '2.25'
327
+ version: '2.27'
328
328
  type: :development
329
329
  prerelease: false
330
330
  version_requirements: !ruby/object:Gem::Requirement
331
331
  requirements:
332
332
  - - '='
333
333
  - !ruby/object:Gem::Version
334
- version: '2.25'
334
+ version: '2.27'
335
335
  - !ruby/object:Gem::Dependency
336
336
  name: simplecov
337
337
  requirement: !ruby/object:Gem::Requirement
@@ -437,6 +437,7 @@ executables: []
437
437
  extensions: []
438
438
  extra_rdoc_files: []
439
439
  files:
440
+ - ".adr-dir"
440
441
  - ".editorconfig"
441
442
  - ".overcommit.yml"
442
443
  - ".rspec"
@@ -452,12 +453,16 @@ files:
452
453
  - README.md
453
454
  - Rakefile
454
455
  - Steepfile
456
+ - adr/0001-record-architecture-decisions.md
457
+ - adr/0002-introduction-of-signature-class.md
455
458
  - docs/.gitignore
456
459
  - docs/.vitepress/config.mjs
457
460
  - docs/README.md
458
461
  - docs/api-examples.md
459
462
  - docs/bun.lockb
460
463
  - docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md
464
+ - docs/common-use-cases/signing-and-verifying-events.md
465
+ - docs/common-use-cases/signing-and-verifying-messages.md
461
466
  - docs/core/client.md
462
467
  - docs/core/keys.md
463
468
  - docs/core/user.md
@@ -491,7 +496,11 @@ files:
491
496
  - lib/nostr/errors/invalid_key_format_error.rb
492
497
  - lib/nostr/errors/invalid_key_length_error.rb
493
498
  - lib/nostr/errors/invalid_key_type_error.rb
499
+ - lib/nostr/errors/invalid_signature_format_error.rb
500
+ - lib/nostr/errors/invalid_signature_length_error.rb
501
+ - lib/nostr/errors/invalid_signature_type_error.rb
494
502
  - lib/nostr/errors/key_validation_error.rb
503
+ - lib/nostr/errors/signature_validation_error.rb
495
504
  - lib/nostr/event.rb
496
505
  - lib/nostr/event_kind.rb
497
506
  - lib/nostr/events/encrypted_direct_message.rb
@@ -503,6 +512,7 @@ files:
503
512
  - lib/nostr/public_key.rb
504
513
  - lib/nostr/relay.rb
505
514
  - lib/nostr/relay_message_type.rb
515
+ - lib/nostr/signature.rb
506
516
  - lib/nostr/subscription.rb
507
517
  - lib/nostr/user.rb
508
518
  - lib/nostr/version.rb
@@ -517,7 +527,11 @@ files:
517
527
  - sig/nostr/errors/invalid_key_format_error.rbs
518
528
  - sig/nostr/errors/invalid_key_length_error.rbs
519
529
  - sig/nostr/errors/invalid_key_type_error.rbs
530
+ - sig/nostr/errors/invalid_signature_format_error.rbs
531
+ - sig/nostr/errors/invalid_signature_length_error.rbs
532
+ - sig/nostr/errors/invalid_signature_type_error.rbs
520
533
  - sig/nostr/errors/key_validation_error.rbs
534
+ - sig/nostr/errors/signature_validation_error.rbs
521
535
  - sig/nostr/event.rbs
522
536
  - sig/nostr/event_kind.rbs
523
537
  - sig/nostr/events/encrypted_direct_message.rbs
@@ -529,6 +543,7 @@ files:
529
543
  - sig/nostr/public_key.rbs
530
544
  - sig/nostr/relay.rbs
531
545
  - sig/nostr/relay_message_type.rbs
546
+ - sig/nostr/signature.rbs
532
547
  - sig/nostr/subscription.rbs
533
548
  - sig/nostr/user.rbs
534
549
  - sig/vendor/bech32.rbs
@@ -543,6 +558,7 @@ files:
543
558
  - sig/vendor/faye/websocket/api.rbs
544
559
  - sig/vendor/faye/websocket/client.rbs
545
560
  - sig/vendor/schnorr.rbs
561
+ - sig/vendor/schnorr/signature.rbs
546
562
  homepage: https://nostr-ruby.com/
547
563
  licenses:
548
564
  - MIT
@@ -559,14 +575,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
559
575
  requirements:
560
576
  - - ">="
561
577
  - !ruby/object:Gem::Version
562
- version: 3.2.0
578
+ version: 3.3.0
563
579
  required_rubygems_version: !ruby/object:Gem::Requirement
564
580
  requirements:
565
581
  - - ">="
566
582
  - !ruby/object:Gem::Version
567
583
  version: '0'
568
584
  requirements: []
569
- rubygems_version: 3.4.20
585
+ rubygems_version: 3.5.6
570
586
  signing_key:
571
587
  specification_version: 4
572
588
  summary: Client and relay implementation of the Nostr protocol.