nostr 0.4.0 → 0.5.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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +1 -1
  3. data/.rubocop.yml +23 -0
  4. data/.tool-versions +2 -1
  5. data/CHANGELOG.md +36 -1
  6. data/README.md +92 -228
  7. data/docs/.gitignore +4 -0
  8. data/docs/.vitepress/config.mjs +112 -0
  9. data/docs/README.md +44 -0
  10. data/docs/api-examples.md +49 -0
  11. data/docs/bun.lockb +0 -0
  12. data/docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md +190 -0
  13. data/docs/core/client.md +108 -0
  14. data/docs/core/keys.md +136 -0
  15. data/docs/core/user.md +43 -0
  16. data/docs/events/contact-list.md +29 -0
  17. data/docs/events/encrypted-direct-message.md +28 -0
  18. data/docs/events/recommend-server.md +32 -0
  19. data/docs/events/set-metadata.md +20 -0
  20. data/docs/events/text-note.md +15 -0
  21. data/docs/events.md +11 -0
  22. data/docs/getting-started/installation.md +21 -0
  23. data/docs/getting-started/overview.md +170 -0
  24. data/docs/implemented-nips.md +9 -0
  25. data/docs/index.md +44 -0
  26. data/docs/markdown-examples.md +85 -0
  27. data/docs/package.json +12 -0
  28. data/docs/relays/connecting-to-a-relay.md +21 -0
  29. data/docs/relays/publishing-events.md +29 -0
  30. data/docs/relays/receiving-events.md +6 -0
  31. data/docs/subscriptions/creating-a-subscription.md +49 -0
  32. data/docs/subscriptions/deleting-a-subscription.md +10 -0
  33. data/docs/subscriptions/filtering-subscription-events.md +115 -0
  34. data/docs/subscriptions/updating-a-subscription.md +4 -0
  35. data/lib/nostr/bech32.rb +203 -0
  36. data/lib/nostr/client.rb +2 -1
  37. data/lib/nostr/crypto.rb +11 -7
  38. data/lib/nostr/errors/error.rb +7 -0
  39. data/lib/nostr/errors/invalid_hrp_error.rb +21 -0
  40. data/lib/nostr/errors/invalid_key_format_error.rb +20 -0
  41. data/lib/nostr/errors/invalid_key_length_error.rb +20 -0
  42. data/lib/nostr/errors/invalid_key_type_error.rb +18 -0
  43. data/lib/nostr/errors/key_validation_error.rb +6 -0
  44. data/lib/nostr/errors.rb +8 -0
  45. data/lib/nostr/event.rb +3 -4
  46. data/lib/nostr/events/encrypted_direct_message.rb +4 -3
  47. data/lib/nostr/filter.rb +4 -4
  48. data/lib/nostr/key.rb +100 -0
  49. data/lib/nostr/key_pair.rb +30 -6
  50. data/lib/nostr/keygen.rb +43 -4
  51. data/lib/nostr/private_key.rb +36 -0
  52. data/lib/nostr/public_key.rb +36 -0
  53. data/lib/nostr/relay_message_type.rb +18 -0
  54. data/lib/nostr/subscription.rb +2 -2
  55. data/lib/nostr/user.rb +17 -8
  56. data/lib/nostr/version.rb +1 -1
  57. data/lib/nostr.rb +6 -0
  58. data/nostr.gemspec +9 -9
  59. data/sig/nostr/bech32.rbs +14 -0
  60. data/sig/nostr/client.rbs +5 -5
  61. data/sig/nostr/crypto.rbs +5 -5
  62. data/sig/nostr/errors/error.rbs +4 -0
  63. data/sig/nostr/errors/invalid_hrb_error.rbs +6 -0
  64. data/sig/nostr/errors/invalid_key_format_error.rbs +5 -0
  65. data/sig/nostr/errors/invalid_key_length_error.rbs +5 -0
  66. data/sig/nostr/errors/invalid_key_type_error.rbs +5 -0
  67. data/sig/nostr/errors/key_validation_error.rbs +4 -0
  68. data/sig/nostr/event.rbs +4 -4
  69. data/sig/nostr/events/encrypted_direct_message.rbs +2 -2
  70. data/sig/nostr/filter.rbs +3 -12
  71. data/sig/nostr/key.rbs +16 -0
  72. data/sig/nostr/key_pair.rbs +7 -3
  73. data/sig/nostr/keygen.rbs +5 -2
  74. data/sig/nostr/private_key.rbs +4 -0
  75. data/sig/nostr/public_key.rbs +4 -0
  76. data/sig/nostr/relay_message_type.rbs +8 -0
  77. data/sig/nostr/user.rbs +4 -8
  78. data/sig/vendor/bech32/nostr/entity.rbs +41 -0
  79. data/sig/vendor/bech32/nostr/nip19.rbs +20 -0
  80. data/sig/vendor/bech32/segwit_addr.rbs +21 -0
  81. data/sig/vendor/bech32.rbs +25 -0
  82. data/sig/vendor/event_emitter.rbs +10 -3
  83. data/sig/vendor/event_machine/channel.rbs +1 -1
  84. data/sig/vendor/faye/websocket/api.rbs +45 -0
  85. data/sig/vendor/faye/websocket/client.rbs +43 -0
  86. data/sig/vendor/faye/websocket.rbs +30 -0
  87. metadata +79 -21
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bech32'
4
+ require 'bech32/nostr'
5
+ require 'bech32/nostr/entity'
6
+
7
+ module Nostr
8
+ # Bech32 encoding and decoding
9
+ #
10
+ # @api public
11
+ #
12
+ module Bech32
13
+ # Decodes a bech32-encoded string
14
+ #
15
+ # @api public
16
+ #
17
+ # @example
18
+ # bech32_value = 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
19
+ # Nostr::Bech32.decode(bech32_value) # => ['npub', '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d8...']
20
+ #
21
+ # @param [String] bech32_value The bech32-encoded string to decode
22
+ #
23
+ # @return [Array<String, String>] The human readable part and the data
24
+ #
25
+ def self.decode(bech32_value)
26
+ entity = ::Bech32::Nostr::NIP19.decode(bech32_value)
27
+
28
+ case entity
29
+ in ::Bech32::Nostr::BareEntity
30
+ [entity.hrp, entity.data]
31
+ in ::Bech32::Nostr::TLVEntity
32
+ [entity.hrp, entity.entries]
33
+ end
34
+ end
35
+
36
+ # Encodes data into a bech32 string
37
+ #
38
+ # @api public
39
+ #
40
+ # @example
41
+ # Nostr::Bech32.encode(hrp: 'npub', data: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
42
+ # # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
43
+ #
44
+ # @param [String] hrp The human readable part (npub, nsec, nprofile, nrelay, nevent, naddr, etc)
45
+ # @param [String] data The data to encode
46
+ #
47
+ # @return [String] The bech32-encoded string
48
+ #
49
+ def self.encode(hrp:, data:)
50
+ ::Bech32::Nostr::BareEntity.new(hrp, data).encode
51
+ end
52
+
53
+ # Encodes a hex-encoded public key into a bech32 string
54
+ #
55
+ # @api public
56
+ #
57
+ # @example
58
+ # Nostr::Bech32.npub_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
59
+ # # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
60
+ #
61
+ # @param [String] npub The public key to encode
62
+ #
63
+ # @see Nostr::Bech32#encode
64
+ # @see Nostr::PublicKey#to_bech32
65
+ # @see Nostr::PrivateKey#to_bech32
66
+ #
67
+ # @return [String] The bech32-encoded string
68
+ #
69
+ def self.npub_encode(npub)
70
+ encode(hrp: 'npub', data: npub)
71
+ end
72
+
73
+ # Encodes a hex-encoded private key into a bech32 string
74
+ #
75
+ # @api public
76
+ #
77
+ # @example
78
+ # Nostr::Bech32.nsec_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
79
+ # # => 'nsec10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
80
+ #
81
+ # @param [String] nsec The private key to encode
82
+ #
83
+ # @see Nostr::Bech32#encode
84
+ # @see Nostr::PrivateKey#to_bech32
85
+ # @see Nostr::PublicKey#to_bech32
86
+ #
87
+ # @return [String] The bech32-encoded string
88
+ #
89
+ def self.nsec_encode(nsec)
90
+ encode(hrp: 'nsec', data: nsec)
91
+ end
92
+
93
+ # Encodes an address into a bech32 string
94
+ #
95
+ # @api public
96
+ #
97
+ # @example
98
+ # naddr = Nostr::Bech32.naddr_encode(
99
+ # pubkey: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e',
100
+ # relays: ['wss://relay.damus.io', 'wss://nos.lol'],
101
+ # kind: Nostr::EventKind::TEXT_NOTE,
102
+ # identifier: 'damus'
103
+ # )
104
+ # naddr # => 'naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7ns...'
105
+ #
106
+ # @param [PublicKey] pubkey The public key to encode
107
+ # @param [Array<String>] relays The relays to encode
108
+ # @param [String] kind The kind of address to encode
109
+ # @param [String] identifier The identifier of the address to encode
110
+ #
111
+ # @return [String] The bech32-encoded string
112
+ #
113
+ def self.naddr_encode(pubkey:, relays: [], kind: nil, identifier: nil)
114
+ entry_relays = relays.map do |relay_url|
115
+ ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
116
+ end
117
+
118
+ pubkey_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_AUTHOR, pubkey)
119
+ kind_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_KIND, kind)
120
+ identifier_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, identifier)
121
+
122
+ entries = [pubkey_entry, *entry_relays, kind_entry, identifier_entry].compact
123
+ entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_EVENT_COORDINATE, entries)
124
+ entity.encode
125
+ end
126
+
127
+ # Encodes an event into a bech32 string
128
+ #
129
+ # @api public
130
+ #
131
+ # @example
132
+ # nevent = Nostr::Bech32.nevent_encode(
133
+ # id: '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3',
134
+ # relays: ['wss://relay.damus.io', 'wss://nos.lol'],
135
+ # kind: Nostr::EventKind::TEXT_NOTE,
136
+ # )
137
+ # nevent # => 'nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0pra...'
138
+ #
139
+ # @param [PublicKey] id The id the event to encode
140
+ # @param [Array<String>] relays The relays to encode
141
+ # @param [String] kind The kind of event to encode
142
+ #
143
+ # @return [String] The bech32-encoded string
144
+ #
145
+ def self.nevent_encode(id:, relays: [], kind: nil)
146
+ entry_relays = relays.map do |relay_url|
147
+ ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
148
+ end
149
+
150
+ id_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_AUTHOR, id)
151
+ kind_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_KIND, kind)
152
+
153
+ entries = [id_entry, *entry_relays, kind_entry].compact
154
+ entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_EVENT, entries)
155
+ entity.encode
156
+ end
157
+
158
+ # Encodes a profile into a bech32 string
159
+ #
160
+ # @api public
161
+ #
162
+ # @example
163
+ # nprofile = Nostr::Bech32.nprofile_encode(
164
+ # pubkey: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e',
165
+ # relays: ['wss://relay.damus.io', 'wss://nos.lol']
166
+ # )
167
+ #
168
+ # @param [PublicKey] pubkey The public key to encode
169
+ # @param [Array<String>] relays The relays to encode
170
+ #
171
+ # @return [String] The bech32-encoded string
172
+ #
173
+ def self.nprofile_encode(pubkey:, relays: [])
174
+ entry_relays = relays.map do |relay_url|
175
+ ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_RELAY, relay_url)
176
+ end
177
+
178
+ pubkey_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, pubkey)
179
+ entries = [pubkey_entry, *entry_relays].compact
180
+ entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_PROFILE, entries)
181
+ entity.encode
182
+ end
183
+
184
+ # Encodes a relay URL into a bech32 string
185
+ #
186
+ # @api public
187
+ #
188
+ # @example
189
+ # nrelay = Nostr::Bech32.nrelay_encode('wss://relay.damus.io')
190
+ # nrelay # => 'nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x'
191
+ #
192
+ # @param [String] relay_url The relay url to encode
193
+ #
194
+ # @return [String] The bech32-encoded string
195
+ #
196
+ def self.nrelay_encode(relay_url)
197
+ relay_entry = ::Bech32::Nostr::TLVEntry.new(::Bech32::Nostr::TLVEntity::TYPE_SPECIAL, relay_url)
198
+
199
+ entity = ::Bech32::Nostr::TLVEntity.new(::Bech32::Nostr::NIP19::HRP_RELAY, [relay_entry])
200
+ entity.encode
201
+ end
202
+ end
203
+ end
data/lib/nostr/client.rb CHANGED
@@ -74,7 +74,8 @@ module Nostr
74
74
  # @example Subscribing to all events created after a certain time
75
75
  # subscription = client.subscribe(filter: Nostr::Filter.new(since: 1230981305))
76
76
  #
77
- # @param subscription_id [String] The subscription id. A random string.
77
+ # @param subscription_id [String] The subscription id. An arbitrary, non-empty string of max length 64
78
+ # chars used to represent a subscription.
78
79
  # @param filter [Filter] A set of attributes that represent the events that the client is interested in.
79
80
  #
80
81
  # @return [Subscription] The subscription object
data/lib/nostr/crypto.rb CHANGED
@@ -30,8 +30,8 @@ module Nostr
30
30
  # encrypted = crypto.encrypt_text(sender_private_key, recipient_public_key, 'Feedback appreciated. Now pay $8')
31
31
  # encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
32
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.
33
+ # @param sender_private_key [PrivateKey] 32-bytes hex-encoded private key of the creator.
34
+ # @param recipient_public_key [PublicKey] 32-bytes hex-encoded public key of the recipient.
35
35
  # @param plain_text [String] The text to be encrypted
36
36
  #
37
37
  # @return [String] Encrypted text.
@@ -54,14 +54,18 @@ module Nostr
54
54
  # encrypted # => "wrYQaHDfpOEvyJELSCg1vzsywmlJTz8NqH03eFW44s8iQs869jtSb26Lr4s23gmY?iv=v38vAJ3LlJAGZxbmWU4qAg=="
55
55
  # decrypted = crypto.decrypt_text(recipient_private_key, sender_public_key, encrypted)
56
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.
57
+ # @param sender_public_key [PublicKey] 32-bytes hex-encoded public key of the message creator.
58
+ # @param recipient_private_key [PrivateKey] 32-bytes hex-encoded public key of the recipient.
59
59
  # @param encrypted_text [String] The text to be decrypted
60
60
  #
61
61
  # @return [String] Decrypted text.
62
62
  #
63
63
  def decrypt_text(recipient_private_key, sender_public_key, encrypted_text)
64
64
  base64_encoded_text, iv = encrypted_text.split('?iv=')
65
+
66
+ # Ensure iv and base64_encoded_text are not nil
67
+ return '' unless iv && base64_encoded_text
68
+
65
69
  cipher = OpenSSL::Cipher.new(CIPHER_ALGORITHM).decrypt
66
70
  cipher.iv = Base64.decode64(iv)
67
71
  cipher.key = compute_shared_key(recipient_private_key, sender_public_key)
@@ -80,7 +84,7 @@ module Nostr
80
84
  # event.sig # => a signature
81
85
  #
82
86
  # @param event [Event] The event to be signed
83
- # @param private_key [String] 32-bytes hex-encoded private key.
87
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
84
88
  #
85
89
  # @return [Event] An unsigned event.
86
90
  #
@@ -103,8 +107,8 @@ module Nostr
103
107
  #
104
108
  # @api private
105
109
  #
106
- # @param private_key [String] 32-bytes hex-encoded private key.
107
- # @param public_key [String] 32-bytes hex-encoded public key.
110
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
111
+ # @param public_key [PublicKey] 32-bytes hex-encoded public key.
108
112
  #
109
113
  # @return [String] A shared key used in the event's content encryption and decryption.
110
114
  #
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Base error class
5
+ class Error < StandardError
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the human readable part of a Bech32 string is invalid
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidHRPError < KeyValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidHRPError.new('example wrong hrp', 'nsec')
13
+ #
14
+ # @param given_hrp [String] The given human readable part of the Bech32 string
15
+ # @param allowed_hrp [String] The allowed human readable part of the Bech32 string
16
+ #
17
+ def initialize(given_hrp, allowed_hrp)
18
+ super("Invalid hrp: #{given_hrp}. The allowed hrp value for this kind of entity is '#{allowed_hrp}'.")
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the private key is in an invalid format
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidKeyFormatError < KeyValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidKeyFormatError.new('private'')
13
+ #
14
+ # @param [String] key_kind The kind of key that is invalid (public or private)
15
+ #
16
+ def initialize(key_kind)
17
+ super("Only lowercase hexadecimal characters are allowed in #{key_kind} keys.")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the private key's length is not 64 characters
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidKeyLengthError < KeyValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidKeyLengthError.new('private'')
13
+ #
14
+ # @param [String] key_kind The kind of key that is invalid (public or private)
15
+ #
16
+ def initialize(key_kind)
17
+ super("Invalid #{key_kind} key length. It should have 64 characters.")
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Raised when the private key is not a string
5
+ #
6
+ # @api public
7
+ #
8
+ class InvalidKeyTypeError < KeyValidationError
9
+ # Initializes the error
10
+ #
11
+ # @example
12
+ # InvalidKeyTypeError.new('private'')
13
+ #
14
+ # @param [String] key_kind The kind of key that is invalid (public or private)
15
+ #
16
+ def initialize(key_kind) = super("Invalid #{key_kind} key type")
17
+ end
18
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Base class for all key validation errors
5
+ class KeyValidationError < Error; end
6
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/error'
4
+ require_relative 'errors/key_validation_error'
5
+ require_relative 'errors/invalid_hrp_error'
6
+ require_relative 'errors/invalid_key_type_error'
7
+ require_relative 'errors/invalid_key_length_error'
8
+ require_relative 'errors/invalid_key_format_error'
data/lib/nostr/event.rb CHANGED
@@ -128,7 +128,6 @@ module Nostr
128
128
  id: nil,
129
129
  sig: nil
130
130
  )
131
-
132
131
  @id = id
133
132
  @sig = sig
134
133
  @pubkey = pubkey
@@ -160,11 +159,11 @@ module Nostr
160
159
  # pubkey = '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e'
161
160
  # event.add_pubkey_reference(pubkey)
162
161
  #
163
- # @param pubkey [String] 32-bytes hex-encoded public key.
162
+ # @param pubkey [PublicKey] 32-bytes hex-encoded public key.
164
163
  #
165
164
  # @return [Array<String>] The event's updated list of tags
166
165
  #
167
- def add_pubkey_reference(pubkey) = tags.push(['p', pubkey])
166
+ def add_pubkey_reference(pubkey) = tags.push(['p', pubkey.to_s])
168
167
 
169
168
  # Signs an event with the user's private key
170
169
  #
@@ -173,7 +172,7 @@ module Nostr
173
172
  # @example Signing an event
174
173
  # event.sign(private_key)
175
174
  #
176
- # @param private_key [String] 32-bytes hex-encoded private key.
175
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
177
176
  #
178
177
  # @return [Event] A signed event.
179
178
  #
@@ -26,8 +26,9 @@ module Nostr
26
26
  # )
27
27
  #
28
28
  # @param plain_text [String] The +content+ of the encrypted message.
29
- # @param sender_private_key [String] 32-bytes hex-encoded private key of the message's author.
30
- # @param recipient_public_key [String] 32-bytes hex-encoded public key of the recipient of the encrypted message.
29
+ # @param sender_private_key [PrivateKey] 32-bytes hex-encoded private key of the message's author.
30
+ # @param recipient_public_key [PublicKey] 32-bytes hex-encoded public key of the recipient of the encrypted
31
+ # message.
31
32
  # @param previous_direct_message [String] 32-bytes hex-encoded id identifying the previous message in a
32
33
  # conversation or a message we are explicitly replying to (such that contextual, more organized conversations
33
34
  # may happen
@@ -43,7 +44,7 @@ module Nostr
43
44
  pubkey: sender_public_key,
44
45
  kind: Nostr::EventKind::ENCRYPTED_DIRECT_MESSAGE,
45
46
  content: encrypted_content,
46
- )
47
+ )
47
48
 
48
49
  add_pubkey_reference(recipient_public_key)
49
50
  add_event_reference(previous_direct_message) if previous_direct_message
data/lib/nostr/filter.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Nostr
4
4
  # A filter determines what events will be sent in a subscription.
5
5
  class Filter
6
- # A list of event ids or prefixes
6
+ # A list of event ids
7
7
  #
8
8
  # @api public
9
9
  #
@@ -14,7 +14,7 @@ module Nostr
14
14
  #
15
15
  attr_reader :ids
16
16
 
17
- # A list of pubkeys or prefixes, the pubkey of an event must be one of these
17
+ # A list of pubkeys, the pubkey of an event must be one of these
18
18
  #
19
19
  # @api public
20
20
  #
@@ -107,8 +107,8 @@ module Nostr
107
107
  # )
108
108
  #
109
109
  # @param kwargs [Hash]
110
- # @option kwargs [Array<String>, nil] ids A list of event ids or prefixes
111
- # @option kwargs [Array<String>, nil] authors A list of pubkeys or prefixes, the pubkey of an event must be one
110
+ # @option kwargs [Array<String>, nil] ids A list of event ids
111
+ # @option kwargs [Array<String>, nil] authors A list of pubkeys, the pubkey of an event must be one
112
112
  # of these
113
113
  # @option kwargs [Array<Integer>, nil] kinds A list of a kind numbers
114
114
  # @option kwargs [Array<String>, nil] e A list of event ids that are referenced in an "e" tag
data/lib/nostr/key.rb ADDED
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Abstract class for all keys
5
+ #
6
+ # @api private
7
+ #
8
+ class Key < String
9
+ # The regular expression for hexadecimal lowercase characters
10
+ #
11
+ # @return [Regexp] The regular expression for hexadecimal lowercase characters
12
+ #
13
+ FORMAT = /^[a-f0-9]+$/
14
+
15
+ # The length of the key in hex format
16
+ #
17
+ # @return [Integer] The length of the key in hex format
18
+ #
19
+ LENGTH = 64
20
+
21
+ # Instantiates a new key. Can't be used directly because this is an abstract class. Raises a +ValidationError+
22
+ #
23
+ # @see Nostr::PrivateKey
24
+ # @see Nostr::PublicKey
25
+ #
26
+ # @param [String] hex_value Hex-encoded value of the key
27
+ #
28
+ # @raise [ValidationError]
29
+ #
30
+ def initialize(hex_value)
31
+ validate_hex_value(hex_value)
32
+
33
+ super(hex_value)
34
+ end
35
+
36
+ # Instantiates a key from a bech32 string
37
+ #
38
+ # @api public
39
+ #
40
+ # @example
41
+ # bech32_key = 'nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'
42
+ # bech32_key.to_key # => #<Nostr::PublicKey:0x000000010601e3c8 @hex_value="...">
43
+ #
44
+ # @raise [Nostr::InvalidHRPError] if the bech32 string is invalid.
45
+ #
46
+ # @param [String] bech32_value The bech32 string representation of the key.
47
+ #
48
+ # @return [Key] the key.
49
+ #
50
+ def self.from_bech32(bech32_value)
51
+ type, data = Bech32.decode(bech32_value)
52
+
53
+ raise InvalidHRPError.new(type, hrp) unless type == hrp
54
+
55
+ new(data)
56
+ end
57
+
58
+ # Abstract method to be implemented by subclasses to provide the HRP (npub, nsec)
59
+ #
60
+ # @api private
61
+ #
62
+ # @return [String] The HRP
63
+ #
64
+ def self.hrp
65
+ raise 'Subclasses must implement this method'
66
+ end
67
+
68
+ # Converts the key to a bech32 string representation
69
+ #
70
+ # @api public
71
+ #
72
+ # @example Converting a private key to a bech32 string
73
+ # public_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa')
74
+ # public_key.to_bech32 # => 'nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'
75
+ #
76
+ # @example Converting a public key to a bech32 string
77
+ # public_key = Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
78
+ # public_key.to_bech32 # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
79
+ #
80
+ # @return [String] The bech32 string representation of the key
81
+ #
82
+ def to_bech32 = Bech32.encode(hrp: self.class.hrp, data: self)
83
+
84
+ protected
85
+
86
+ # Validates the hex value during initialization
87
+ #
88
+ # @api private
89
+ #
90
+ # @param [String] _hex_value The hex value of the key
91
+ #
92
+ # @raise [KeyValidationError] When the hex value is invalid
93
+ #
94
+ # @return [void]
95
+ #
96
+ def validate_hex_value(_hex_value)
97
+ raise 'Subclasses must implement this method'
98
+ end
99
+ end
100
+ end
@@ -10,7 +10,7 @@ module Nostr
10
10
  # @example
11
11
  # keypair.private_key # => '893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900'
12
12
  #
13
- # @return [String]
13
+ # @return [PrivateKey]
14
14
  #
15
15
  attr_reader :private_key
16
16
 
@@ -21,7 +21,7 @@ module Nostr
21
21
  # @example
22
22
  # keypair.public_key # => '2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558'
23
23
  #
24
- # @return [String]
24
+ # @return [PublicKey]
25
25
  #
26
26
  attr_reader :public_key
27
27
 
@@ -31,16 +31,40 @@ module Nostr
31
31
  #
32
32
  # @example
33
33
  # keypair = Nostr::KeyPair.new(
34
- # private_key: '893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900',
35
- # public_key: '2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558',
34
+ # private_key: Nostr::PrivateKey.new('893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900'),
35
+ # public_key: Nostr::PublicKey.new('2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558'),
36
36
  # )
37
37
  #
38
- # @param private_key [String] 32-bytes hex-encoded private key.
39
- # @param public_key [String] 32-bytes hex-encoded public key.
38
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
39
+ # @param public_key [PublicKey] 32-bytes hex-encoded public key.
40
+ #
41
+ # @raise ArgumentError when the private key is not a +PrivateKey+
42
+ # @raise ArgumentError when the public key is not a +PublicKey+
40
43
  #
41
44
  def initialize(private_key:, public_key:)
45
+ validate_keys(private_key, public_key)
46
+
42
47
  @private_key = private_key
43
48
  @public_key = public_key
44
49
  end
50
+
51
+ private
52
+
53
+ # Validates the keys
54
+ #
55
+ # @api private
56
+ #
57
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
58
+ # @param public_key [PublicKey] 32-bytes hex-encoded public key.
59
+ #
60
+ # @raise ArgumentError when the private key is not a +PrivateKey+
61
+ # @raise ArgumentError when the public key is not a +PublicKey+
62
+ #
63
+ # @return [void]
64
+ #
65
+ def validate_keys(private_key, public_key)
66
+ raise ArgumentError, 'private_key is not an instance of PrivateKey' unless private_key.is_a?(Nostr::PrivateKey)
67
+ raise ArgumentError, 'public_key is not an instance of PublicKey' unless public_key.is_a?(Nostr::PublicKey)
68
+ end
45
69
  end
46
70
  end