nostr 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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