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.
- checksums.yaml +4 -4
- data/.editorconfig +1 -1
- data/.rubocop.yml +26 -0
- data/.tool-versions +2 -1
- data/CHANGELOG.md +65 -1
- data/README.md +96 -183
- data/Steepfile +2 -0
- data/docs/.gitignore +4 -0
- data/docs/.vitepress/config.mjs +112 -0
- data/docs/README.md +44 -0
- data/docs/api-examples.md +49 -0
- data/docs/bun.lockb +0 -0
- data/docs/common-use-cases/bech32-encoding-and-decoding-(NIP-19).md +190 -0
- data/docs/core/client.md +108 -0
- data/docs/core/keys.md +136 -0
- data/docs/core/user.md +43 -0
- data/docs/events/contact-list.md +29 -0
- data/docs/events/encrypted-direct-message.md +28 -0
- data/docs/events/recommend-server.md +32 -0
- data/docs/events/set-metadata.md +20 -0
- data/docs/events/text-note.md +15 -0
- data/docs/events.md +11 -0
- data/docs/getting-started/installation.md +21 -0
- data/docs/getting-started/overview.md +170 -0
- data/docs/implemented-nips.md +9 -0
- data/docs/index.md +44 -0
- data/docs/markdown-examples.md +85 -0
- data/docs/package.json +12 -0
- data/docs/relays/connecting-to-a-relay.md +21 -0
- data/docs/relays/publishing-events.md +29 -0
- data/docs/relays/receiving-events.md +6 -0
- data/docs/subscriptions/creating-a-subscription.md +49 -0
- data/docs/subscriptions/deleting-a-subscription.md +10 -0
- data/docs/subscriptions/filtering-subscription-events.md +115 -0
- data/docs/subscriptions/updating-a-subscription.md +4 -0
- data/lib/nostr/bech32.rb +203 -0
- data/lib/nostr/client.rb +2 -1
- data/lib/nostr/crypto.rb +147 -0
- data/lib/nostr/errors/error.rb +7 -0
- data/lib/nostr/errors/invalid_hrp_error.rb +21 -0
- data/lib/nostr/errors/invalid_key_format_error.rb +20 -0
- data/lib/nostr/errors/invalid_key_length_error.rb +20 -0
- data/lib/nostr/errors/invalid_key_type_error.rb +18 -0
- data/lib/nostr/errors/key_validation_error.rb +6 -0
- data/lib/nostr/errors.rb +8 -0
- data/lib/nostr/event.rb +157 -12
- data/lib/nostr/event_kind.rb +8 -0
- data/lib/nostr/events/encrypted_direct_message.rb +54 -0
- data/lib/nostr/filter.rb +4 -4
- data/lib/nostr/key.rb +100 -0
- data/lib/nostr/key_pair.rb +30 -6
- data/lib/nostr/keygen.rb +43 -4
- data/lib/nostr/private_key.rb +36 -0
- data/lib/nostr/public_key.rb +36 -0
- data/lib/nostr/relay_message_type.rb +18 -0
- data/lib/nostr/subscription.rb +2 -2
- data/lib/nostr/user.rb +17 -36
- data/lib/nostr/version.rb +1 -1
- data/lib/nostr.rb +8 -1
- data/nostr.gemspec +9 -9
- data/sig/nostr/bech32.rbs +14 -0
- data/sig/nostr/client.rbs +5 -5
- data/sig/nostr/crypto.rbs +16 -0
- data/sig/nostr/errors/error.rbs +4 -0
- data/sig/nostr/errors/invalid_hrb_error.rbs +6 -0
- data/sig/nostr/errors/invalid_key_format_error.rbs +5 -0
- data/sig/nostr/errors/invalid_key_length_error.rbs +5 -0
- data/sig/nostr/errors/invalid_key_type_error.rbs +5 -0
- data/sig/nostr/errors/key_validation_error.rbs +4 -0
- data/sig/nostr/event.rbs +24 -9
- data/sig/nostr/event_kind.rbs +1 -0
- data/sig/nostr/events/encrypted_direct_message.rbs +12 -0
- data/sig/nostr/filter.rbs +3 -12
- data/sig/nostr/key.rbs +16 -0
- data/sig/nostr/key_pair.rbs +7 -3
- data/sig/nostr/keygen.rbs +5 -2
- data/sig/nostr/private_key.rbs +4 -0
- data/sig/nostr/public_key.rbs +4 -0
- data/sig/nostr/relay_message_type.rbs +8 -0
- data/sig/nostr/user.rbs +4 -10
- data/sig/vendor/bech32/nostr/entity.rbs +41 -0
- data/sig/vendor/bech32/nostr/nip19.rbs +20 -0
- data/sig/vendor/bech32/segwit_addr.rbs +21 -0
- data/sig/vendor/bech32.rbs +25 -0
- data/sig/vendor/event_emitter.rbs +10 -3
- data/sig/vendor/event_machine/channel.rbs +1 -1
- data/sig/vendor/faye/websocket/api.rbs +45 -0
- data/sig/vendor/faye/websocket/client.rbs +43 -0
- data/sig/vendor/faye/websocket.rbs +30 -0
- metadata +83 -23
- data/lib/nostr/event_fragment.rb +0 -111
- 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
|
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
|
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
|
111
|
-
# @option kwargs [Array<String>, nil] authors A list of pubkeys
|
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
|
data/lib/nostr/key_pair.rb
CHANGED
@@ -10,7 +10,7 @@ module Nostr
|
|
10
10
|
# @example
|
11
11
|
# keypair.private_key # => '893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900'
|
12
12
|
#
|
13
|
-
# @return [
|
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 [
|
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 [
|
39
|
-
# @param 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 [
|
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
|
-
# @
|
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
|
-
|
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
|
data/lib/nostr/subscription.rb
CHANGED
@@ -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
|
-
#
|
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]
|
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
|
51
|
-
# @
|
52
|
-
# @
|
53
|
-
# @
|
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(
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
Event.new(
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
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
|
-
|
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
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://
|
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.
|
35
|
-
spec.add_dependency 'bip-schnorr', '~> 0.
|
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', '~>
|
50
|
+
spec.add_development_dependency 'puma', '~> 6.4'
|
51
51
|
spec.add_development_dependency 'rack', '~> 3.0'
|
52
|
-
spec.add_development_dependency 'rake', '~> 13.
|
53
|
-
spec.add_development_dependency 'rbs', '~>
|
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.
|
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.
|
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.
|
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) ->
|
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:
|
15
|
-
attr_reader child_to_parent_channel:
|
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: { ->
|
18
|
-
def initialize_channels: ->
|
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
|