nostr 0.3.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +1 -1
  3. data/.rubocop.yml +26 -0
  4. data/.tool-versions +2 -1
  5. data/CHANGELOG.md +65 -1
  6. data/README.md +96 -183
  7. data/Steepfile +2 -0
  8. data/docs/.gitignore +4 -0
  9. data/docs/.vitepress/config.mjs +112 -0
  10. data/docs/README.md +44 -0
  11. data/docs/api-examples.md +49 -0
  12. data/docs/bun.lockb +0 -0
  13. data/docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md +190 -0
  14. data/docs/core/client.md +108 -0
  15. data/docs/core/keys.md +136 -0
  16. data/docs/core/user.md +43 -0
  17. data/docs/events/contact-list.md +29 -0
  18. data/docs/events/encrypted-direct-message.md +28 -0
  19. data/docs/events/recommend-server.md +32 -0
  20. data/docs/events/set-metadata.md +20 -0
  21. data/docs/events/text-note.md +15 -0
  22. data/docs/events.md +11 -0
  23. data/docs/getting-started/installation.md +21 -0
  24. data/docs/getting-started/overview.md +170 -0
  25. data/docs/implemented-nips.md +9 -0
  26. data/docs/index.md +44 -0
  27. data/docs/markdown-examples.md +85 -0
  28. data/docs/package.json +12 -0
  29. data/docs/relays/connecting-to-a-relay.md +21 -0
  30. data/docs/relays/publishing-events.md +29 -0
  31. data/docs/relays/receiving-events.md +6 -0
  32. data/docs/subscriptions/creating-a-subscription.md +49 -0
  33. data/docs/subscriptions/deleting-a-subscription.md +10 -0
  34. data/docs/subscriptions/filtering-subscription-events.md +115 -0
  35. data/docs/subscriptions/updating-a-subscription.md +4 -0
  36. data/lib/nostr/bech32.rb +203 -0
  37. data/lib/nostr/client.rb +2 -1
  38. data/lib/nostr/crypto.rb +147 -0
  39. data/lib/nostr/errors/error.rb +7 -0
  40. data/lib/nostr/errors/invalid_hrp_error.rb +21 -0
  41. data/lib/nostr/errors/invalid_key_format_error.rb +20 -0
  42. data/lib/nostr/errors/invalid_key_length_error.rb +20 -0
  43. data/lib/nostr/errors/invalid_key_type_error.rb +18 -0
  44. data/lib/nostr/errors/key_validation_error.rb +6 -0
  45. data/lib/nostr/errors.rb +8 -0
  46. data/lib/nostr/event.rb +157 -12
  47. data/lib/nostr/event_kind.rb +8 -0
  48. data/lib/nostr/events/encrypted_direct_message.rb +54 -0
  49. data/lib/nostr/filter.rb +4 -4
  50. data/lib/nostr/key.rb +100 -0
  51. data/lib/nostr/key_pair.rb +30 -6
  52. data/lib/nostr/keygen.rb +43 -4
  53. data/lib/nostr/private_key.rb +36 -0
  54. data/lib/nostr/public_key.rb +36 -0
  55. data/lib/nostr/relay_message_type.rb +18 -0
  56. data/lib/nostr/subscription.rb +2 -2
  57. data/lib/nostr/user.rb +17 -36
  58. data/lib/nostr/version.rb +1 -1
  59. data/lib/nostr.rb +8 -1
  60. data/nostr.gemspec +9 -9
  61. data/sig/nostr/bech32.rbs +14 -0
  62. data/sig/nostr/client.rbs +5 -5
  63. data/sig/nostr/crypto.rbs +16 -0
  64. data/sig/nostr/errors/error.rbs +4 -0
  65. data/sig/nostr/errors/invalid_hrb_error.rbs +6 -0
  66. data/sig/nostr/errors/invalid_key_format_error.rbs +5 -0
  67. data/sig/nostr/errors/invalid_key_length_error.rbs +5 -0
  68. data/sig/nostr/errors/invalid_key_type_error.rbs +5 -0
  69. data/sig/nostr/errors/key_validation_error.rbs +4 -0
  70. data/sig/nostr/event.rbs +24 -9
  71. data/sig/nostr/event_kind.rbs +1 -0
  72. data/sig/nostr/events/encrypted_direct_message.rbs +12 -0
  73. data/sig/nostr/filter.rbs +3 -12
  74. data/sig/nostr/key.rbs +16 -0
  75. data/sig/nostr/key_pair.rbs +7 -3
  76. data/sig/nostr/keygen.rbs +5 -2
  77. data/sig/nostr/private_key.rbs +4 -0
  78. data/sig/nostr/public_key.rbs +4 -0
  79. data/sig/nostr/relay_message_type.rbs +8 -0
  80. data/sig/nostr/user.rbs +4 -10
  81. data/sig/vendor/bech32/nostr/entity.rbs +41 -0
  82. data/sig/vendor/bech32/nostr/nip19.rbs +20 -0
  83. data/sig/vendor/bech32/segwit_addr.rbs +21 -0
  84. data/sig/vendor/bech32.rbs +25 -0
  85. data/sig/vendor/event_emitter.rbs +10 -3
  86. data/sig/vendor/event_machine/channel.rbs +1 -1
  87. data/sig/vendor/faye/websocket/api.rbs +45 -0
  88. data/sig/vendor/faye/websocket/client.rbs +43 -0
  89. data/sig/vendor/faye/websocket.rbs +30 -0
  90. metadata +83 -23
  91. data/lib/nostr/event_fragment.rb +0 -111
  92. data/sig/nostr/event_fragment.rbs +0 -12
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Classes of event kinds.
5
+ module Events
6
+ # An event whose +content+ is encrypted. It can only be decrypted by the owner of the private key that pairs
7
+ # the event's +pubkey+.
8
+ class EncryptedDirectMessage < Event
9
+ # Instantiates a new encrypted direct message
10
+ #
11
+ # @api public
12
+ #
13
+ # @example Instantiating a new encrypted direct message
14
+ # Nostr::Events::EncryptedDirectMessage.new(
15
+ # sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
16
+ # recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
17
+ # plain_text: 'Your feedback is appreciated, now pay $8',
18
+ # )
19
+ #
20
+ # @example Instantiating a new encrypted direct message that references a previous direct message
21
+ # Nostr::Events::EncryptedDirectMessage.new(
22
+ # sender_private_key: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
23
+ # recipient_public_key: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
24
+ # plain_text: 'Your feedback is appreciated, now pay $8',
25
+ # previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
26
+ # )
27
+ #
28
+ # @param plain_text [String] The +content+ 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.
32
+ # @param previous_direct_message [String] 32-bytes hex-encoded id identifying the previous message in a
33
+ # conversation or a message we are explicitly replying to (such that contextual, more organized conversations
34
+ # may happen
35
+ #
36
+ def initialize(plain_text:, sender_private_key:, recipient_public_key:, previous_direct_message: nil)
37
+ crypto = Crypto.new
38
+ keygen = Keygen.new
39
+
40
+ encrypted_content = crypto.encrypt_text(sender_private_key, recipient_public_key, plain_text)
41
+ sender_public_key = keygen.extract_public_key(sender_private_key)
42
+
43
+ super(
44
+ pubkey: sender_public_key,
45
+ kind: Nostr::EventKind::ENCRYPTED_DIRECT_MESSAGE,
46
+ content: encrypted_content,
47
+ )
48
+
49
+ add_pubkey_reference(recipient_public_key)
50
+ add_event_reference(previous_direct_message) if previous_direct_message
51
+ end
52
+ end
53
+ end
54
+ end
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
data/lib/nostr/keygen.rb CHANGED
@@ -44,10 +44,11 @@ module Nostr
44
44
  # private_key = keygen.generate_private_key
45
45
  # private_key # => '893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900'
46
46
  #
47
- # @return [String] A 32-bytes hex-encoded private key.
47
+ # @return [PrivateKey] A 32-bytes hex-encoded private key.
48
48
  #
49
49
  def generate_private_key
50
- (SecureRandom.random_number(group.order - 1) + 1).to_s(16)
50
+ hex_value = (SecureRandom.random_number(group.order - 1) + 1).to_s(16).rjust(64, '0')
51
+ PrivateKey.new(hex_value)
51
52
  end
52
53
 
53
54
  # Extracts a public key from a private key
@@ -59,10 +60,36 @@ module Nostr
59
60
  # public_key = keygen.extract_public_key(private_key)
60
61
  # public_key # => '2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558'
61
62
  #
62
- # @return [String] A 32-bytes hex-encoded public key.
63
+ # @param [PrivateKey] private_key A 32-bytes hex-encoded private key.
64
+ #
65
+ # @raise [ArgumentError] if the private key is not an instance of +PrivateKey+
66
+ #
67
+ # @return [PublicKey] A 32-bytes hex-encoded public key.
63
68
  #
64
69
  def extract_public_key(private_key)
65
- group.generator.multiply_by_scalar(private_key.to_i(16)).x.to_s(16)
70
+ validate_private_key(private_key)
71
+ hex_value = group.generator.multiply_by_scalar(private_key.to_i(16)).x.to_s(16).rjust(64, '0')
72
+ PublicKey.new(hex_value)
73
+ end
74
+
75
+ # Builds a key pair from an existing private key
76
+ #
77
+ # @api public
78
+ #
79
+ # @example
80
+ # private_key = Nostr::PrivateKey.new('893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900')
81
+ # keygen.get_key_pair_from_private_key(private_key)
82
+ #
83
+ # @param private_key [PrivateKey] 32-bytes hex-encoded private key.
84
+ #
85
+ # @raise [ArgumentError] if the private key is not an instance of +PrivateKey+
86
+ #
87
+ # @return [Nostr::KeyPair]
88
+ #
89
+ def get_key_pair_from_private_key(private_key)
90
+ validate_private_key(private_key)
91
+ public_key = extract_public_key(private_key)
92
+ KeyPair.new(private_key:, public_key:)
66
93
  end
67
94
 
68
95
  private
@@ -74,5 +101,17 @@ module Nostr
74
101
  # @return [ECDSA::Group]
75
102
  #
76
103
  attr_reader :group
104
+
105
+ # Validates that the private key is an instance of +PrivateKey+
106
+ #
107
+ # @api private
108
+ #
109
+ # @raise [ArgumentError] if the private key is not an instance of +PrivateKey+
110
+ #
111
+ # @return [void]
112
+ #
113
+ def validate_private_key(private_key)
114
+ raise ArgumentError, 'private_key is not an instance of PrivateKey' unless private_key.is_a?(Nostr::PrivateKey)
115
+ end
77
116
  end
78
117
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # 32-bytes lowercase hex-encoded private key
5
+ class PrivateKey < Key
6
+ # Human-readable part of the Bech32 encoded address
7
+ #
8
+ # @api private
9
+ #
10
+ # @return [String] The human-readable part of the Bech32 encoded address
11
+ #
12
+ def self.hrp
13
+ 'nsec'
14
+ end
15
+
16
+ private
17
+
18
+ # Validates the hex value of the private key
19
+ #
20
+ # @api private
21
+ #
22
+ # @param [String] hex_value The private key in hex format
23
+ #
24
+ # @raise InvalidKeyTypeError when the private key is not a string
25
+ # @raise InvalidKeyLengthError when the private key's length is not 64 characters
26
+ # @raise InvalidKeyFormatError when the private key is in an invalid format
27
+ #
28
+ # @return [void]
29
+ #
30
+ def validate_hex_value(hex_value)
31
+ raise InvalidKeyTypeError, 'private' unless hex_value.is_a?(String)
32
+ raise InvalidKeyLengthError, 'private' unless hex_value.size == Key::LENGTH
33
+ raise InvalidKeyFormatError, 'private' unless hex_value.match(Key::FORMAT)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # 32-bytes lowercase hex-encoded public key
5
+ class PublicKey < Key
6
+ # Human-readable part of the Bech32 encoded address
7
+ #
8
+ # @api private
9
+ #
10
+ # @return [String] The human-readable part of the Bech32 encoded address
11
+ #
12
+ def self.hrp
13
+ 'npub'
14
+ end
15
+
16
+ private
17
+
18
+ # Validates the hex value of the public key
19
+ #
20
+ # @api private
21
+ #
22
+ # @param [String] hex_value The public key in hex format
23
+ #
24
+ # @raise InvalidKeyTypeError when the public key is not a string
25
+ # @raise InvalidKeyLengthError when the public key's length is not 64 characters
26
+ # @raise InvalidKeyFormatError when the public key is in an invalid format
27
+ #
28
+ # @return [void]
29
+ #
30
+ def validate_hex_value(hex_value)
31
+ raise InvalidKeyTypeError, 'public' unless hex_value.is_a?(String)
32
+ raise InvalidKeyLengthError, 'public' unless hex_value.size == Key::LENGTH
33
+ raise InvalidKeyFormatError, 'public' unless hex_value.match(Key::FORMAT)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Clients can send 4 types of messages, which must be JSON arrays
5
+ module RelayMessageType
6
+ # @return [String] Used to notify clients all stored events have been sent
7
+ EOSE = 'EOSE'
8
+
9
+ # @return [String] Used to send events requested to clients
10
+ EVENT = 'EVENT'
11
+
12
+ # @return [String] Used to send human-readable messages to clients
13
+ NOTICE = 'NOTICE'
14
+
15
+ # @return [String] Used to notify clients if an EVENT was successful
16
+ OK = 'OK'
17
+ end
18
+ end
@@ -5,7 +5,7 @@ require 'securerandom'
5
5
  module Nostr
6
6
  # A subscription the result of a request to receive events from a relay
7
7
  class Subscription
8
- # A random string that should be used to represent a subscription
8
+ # An arbitrary, non-empty string of max length 64 chars used to represent a subscription
9
9
  #
10
10
  # @api public
11
11
  #
@@ -41,7 +41,7 @@ module Nostr
41
41
  # @example Subscribing to all events created after a certain time
42
42
  # subscription = Nostr::Subscription.new(filter: Nostr::Filter.new(since: 1230981305))
43
43
  #
44
- # @param id [String] A random string that should be used to represent a subscription
44
+ # @param id [String] An arbitrary, non-empty string of max length 64 chars used to represent a subscription
45
45
  # @param filter [Filter] An object that determines what events will be sent in that subscription
46
46
  #
47
47
  def initialize(filter:, id: SecureRandom.hex)
data/lib/nostr/user.rb CHANGED
@@ -47,46 +47,27 @@ module Nostr
47
47
  # content: 'Your feedback is appreciated, now pay $8'
48
48
  # )
49
49
  #
50
- # @param event_attributes [Hash]
51
- # @option event_attributes [String] :pubkey 32-bytes hex-encoded public key of the event creator.
52
- # @option event_attributes [Integer] :created_at Date of the creation of the vent. A UNIX timestamp, in seconds.
53
- # @option event_attributes [Integer] :kind The kind of the event. An integer from 0 to 3.
54
- # @option event_attributes [Array<Array>] :tags An array of tags. Each tag is an array of strings.
55
- # @option event_attributes [String] :content Arbitrary string.
50
+ # @param created_at [Integer] Date of the creation of the vent. A UNIX timestamp, in seconds.
51
+ # @param kind [Integer] The kind of the event. An integer from 0 to 3.
52
+ # @param tags [Array<Array>] An array of tags. Each tag is an array of strings.
53
+ # @param content [String] Arbitrary string.
56
54
  #
57
55
  # @return [Event]
58
56
  #
59
- def create_event(event_attributes)
60
- event_fragment = EventFragment.new(**event_attributes.merge(pubkey: keypair.public_key))
61
- event_sha256 = Digest::SHA256.hexdigest(JSON.dump(event_fragment.serialize))
62
-
63
- signature = sign(event_sha256)
64
-
65
- Event.new(
66
- id: event_sha256,
67
- pubkey: event_fragment.pubkey,
68
- created_at: event_fragment.created_at,
69
- kind: event_fragment.kind,
70
- tags: event_fragment.tags,
71
- content: event_fragment.content,
72
- sig: signature
57
+ def create_event(
58
+ kind:,
59
+ content:,
60
+ created_at: Time.now.to_i,
61
+ tags: []
62
+ )
63
+ event = Event.new(
64
+ pubkey: keypair.public_key,
65
+ kind:,
66
+ content:,
67
+ created_at:,
68
+ tags:
73
69
  )
74
- end
75
-
76
- private
77
-
78
- # Signs an event with the user's private key
79
- #
80
- # @api private
81
- #
82
- # @param event_sha256 [String] The SHA256 hash of the event.
83
- #
84
- # @return [String] The signature of the event.
85
- #
86
- def sign(event_sha256)
87
- hex_private_key = Array(keypair.private_key).pack('H*')
88
- hex_message = Array(event_sha256).pack('H*')
89
- Schnorr.sign(hex_message, hex_private_key).encode.unpack1('H*')
70
+ event.sign(keypair.private_key)
90
71
  end
91
72
  end
92
73
  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.3.0'
5
+ VERSION = '0.5.0'
6
6
  end
data/lib/nostr.rb CHANGED
@@ -1,17 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'nostr/errors'
4
+ require_relative 'nostr/bech32'
5
+ require_relative 'nostr/crypto'
3
6
  require_relative 'nostr/version'
4
7
  require_relative 'nostr/keygen'
5
8
  require_relative 'nostr/client_message_type'
6
9
  require_relative 'nostr/filter'
7
10
  require_relative 'nostr/subscription'
8
11
  require_relative 'nostr/relay'
12
+ require_relative 'nostr/relay_message_type'
9
13
  require_relative 'nostr/key_pair'
10
14
  require_relative 'nostr/event_kind'
11
- require_relative 'nostr/event_fragment'
12
15
  require_relative 'nostr/event'
16
+ require_relative 'nostr/events/encrypted_direct_message'
13
17
  require_relative 'nostr/client'
14
18
  require_relative 'nostr/user'
19
+ require_relative 'nostr/key'
20
+ require_relative 'nostr/private_key'
21
+ require_relative 'nostr/public_key'
15
22
 
16
23
  # Encapsulates all the gem's logic
17
24
  module Nostr
data/nostr.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = 'Client and relay implementation of the Nostr protocol.'
12
12
  spec.description = 'Client and relay implementation of the Nostr protocol.'
13
- spec.homepage = 'https://github.com/wilsonsilva/nostr'
13
+ spec.homepage = 'https://nostr-ruby.com/'
14
14
  spec.license = 'MIT'
15
15
  spec.required_ruby_version = '>= 3.2.0'
16
16
  spec.metadata['rubygems_mfa_required'] = 'true'
@@ -31,8 +31,8 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ['lib']
33
33
 
34
- spec.add_dependency 'bech32', '~> 1.3'
35
- spec.add_dependency 'bip-schnorr', '~> 0.4'
34
+ spec.add_dependency 'bech32', '~> 1.4'
35
+ spec.add_dependency 'bip-schnorr', '~> 0.6'
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'
@@ -47,17 +47,17 @@ Gem::Specification.new do |spec|
47
47
  spec.add_development_dependency 'guard-rubocop', '~> 1.5'
48
48
  spec.add_development_dependency 'overcommit', '~> 0.59'
49
49
  spec.add_development_dependency 'pry', '~> 0.14'
50
- spec.add_development_dependency 'puma', '~> 5.6'
50
+ spec.add_development_dependency 'puma', '~> 6.4'
51
51
  spec.add_development_dependency 'rack', '~> 3.0'
52
- spec.add_development_dependency 'rake', '~> 13.0'
53
- spec.add_development_dependency 'rbs', '~> 2.8'
52
+ spec.add_development_dependency 'rake', '~> 13.1'
53
+ spec.add_development_dependency 'rbs', '~> 3.3'
54
54
  spec.add_development_dependency 'rspec', '~> 3.12'
55
- spec.add_development_dependency 'rubocop', '~> 1.42'
55
+ spec.add_development_dependency 'rubocop', '~> 1.57'
56
56
  spec.add_development_dependency 'rubocop-rake', '~> 0.6'
57
- spec.add_development_dependency 'rubocop-rspec', '2.16'
57
+ spec.add_development_dependency 'rubocop-rspec', '2.25'
58
58
  spec.add_development_dependency 'simplecov', '= 0.17'
59
59
  spec.add_development_dependency 'simplecov-console', '~> 0.9'
60
- spec.add_development_dependency 'steep', '~> 1.3'
60
+ spec.add_development_dependency 'steep', '~> 1.6'
61
61
  spec.add_development_dependency 'typeprof', '~> 0.21'
62
62
  spec.add_development_dependency 'yard', '~> 0.9'
63
63
  spec.add_development_dependency 'yard-junk', '~> 0.0.9'
@@ -0,0 +1,14 @@
1
+ module Nostr
2
+ module Bech32
3
+ # Perhaps a bug in RBS/Steep. +decode+ and +encode+ are not recognized as public class methods.
4
+ def self?.decode: (String data) -> [String, String]
5
+ def self?.encode: (hrp: String, data: String) -> String
6
+
7
+ def naddr_encode: (pubkey: PublicKey, ?relays: Array[String], ?kind: Integer, ?identifier: String) -> String
8
+ def nevent_encode: (id: PublicKey, ?relays: Array[String], ?kind: Integer) -> String
9
+ def nprofile_encode: (pubkey: PublicKey, ?relays: Array[String]) -> String
10
+ def npub_encode: (String npub) -> String
11
+ def nrelay_encode: (String nrelay) -> String
12
+ def nsec_encode: (String nsec) -> String
13
+ end
14
+ end
data/sig/nostr/client.rbs CHANGED
@@ -5,16 +5,16 @@ module Nostr
5
5
  def initialize: -> void
6
6
  def connect: (Relay relay) -> Thread
7
7
  def subscribe: (?subscription_id: String, ?filter: Filter) -> Subscription
8
- def unsubscribe: (String subscription_id) -> untyped
8
+ def unsubscribe: (String subscription_id) -> void
9
9
  def publish: (Event event) -> untyped
10
10
 
11
11
  private
12
12
 
13
13
  attr_reader subscriptions: Hash[String, Subscription]
14
- attr_reader parent_to_child_channel: untyped
15
- attr_reader child_to_parent_channel: untyped
14
+ attr_reader parent_to_child_channel: EventMachine::Channel
15
+ attr_reader child_to_parent_channel: EventMachine::Channel
16
16
 
17
- def execute_within_an_em_thread: { -> untyped } -> Thread
18
- def initialize_channels: -> untyped
17
+ def execute_within_an_em_thread: { -> void } -> Thread
18
+ def initialize_channels: -> void
19
19
  end
20
20
  end
@@ -0,0 +1,16 @@
1
+ module Nostr
2
+ class Crypto
3
+ BN_BASE: 0 | 2 | 10 | 16
4
+ CIPHER_CURVE: String
5
+ CIPHER_ALGORITHM: String
6
+
7
+ def encrypt_text: (PrivateKey, PublicKey, String) -> String
8
+ def decrypt_text: (PrivateKey, PublicKey, String) -> String
9
+ def sign_event: (Event, PrivateKey) -> Event
10
+
11
+ private
12
+
13
+ def compute_shared_key: (PrivateKey, PublicKey) -> String
14
+ def hash_event:(Event) -> String
15
+ end
16
+ end
@@ -0,0 +1,4 @@
1
+ module Nostr
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Nostr
2
+ class InvalidHRPError < KeyValidationError
3
+ def initialize: (String, String) -> void
4
+ end
5
+ end
6
+
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidKeyFormatError < KeyValidationError
3
+ def initialize: (String) -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidKeyLengthError < KeyValidationError
3
+ def initialize: (String) -> void
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Nostr
2
+ class InvalidKeyTypeError < KeyValidationError
3
+ def initialize: (String) -> void
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Nostr
2
+ class KeyValidationError < Error
3
+ end
4
+ end