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,49 @@
1
+ ---
2
+ outline: deep
3
+ ---
4
+
5
+ # Runtime API Examples
6
+
7
+ This page demonstrates usage of some of the runtime APIs provided by VitePress.
8
+
9
+ The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files:
10
+
11
+ ```md
12
+ <script setup>
13
+ import { useData } from 'vitepress'
14
+
15
+ const { theme, page, frontmatter } = useData()
16
+ </script>
17
+
18
+ ## Results
19
+
20
+ ### Theme Data
21
+ <pre>{{ theme }}</pre>
22
+
23
+ ### Page Data
24
+ <pre>{{ page }}</pre>
25
+
26
+ ### Page Frontmatter
27
+ <pre>{{ frontmatter }}</pre>
28
+ ```
29
+
30
+ <script setup>
31
+ import { useData } from 'vitepress'
32
+
33
+ const { site, theme, page, frontmatter } = useData()
34
+ </script>
35
+
36
+ ## Results
37
+
38
+ ### Theme Data
39
+ <pre>{{ theme }}</pre>
40
+
41
+ ### Page Data
42
+ <pre>{{ page }}</pre>
43
+
44
+ ### Page Frontmatter
45
+ <pre>{{ frontmatter }}</pre>
46
+
47
+ ## More
48
+
49
+ Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata).
data/docs/bun.lockb ADDED
Binary file
@@ -0,0 +1,190 @@
1
+ # Encoding/decoding bech-32 strings (NIP-19)
2
+
3
+ [NIP-19](https://github.com/nostr-protocol/nips/blob/master/19.md) standardizes bech32-formatted strings that can be
4
+ used to display keys, ids and other information in clients. These formats are not meant to be used anywhere in the core
5
+ protocol, they are only meant for displaying to users, copy-pasting, sharing, rendering QR codes and inputting data.
6
+
7
+
8
+ In order to guarantee the deterministic nature of the documentation, the examples below assume that there is a `keypair`
9
+ variable with the following values:
10
+
11
+ ```ruby
12
+ keypair = Nostr::KeyPair.new(
13
+ private_key: Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
14
+ public_key: Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
15
+ )
16
+
17
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
18
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
19
+ ```
20
+
21
+ ## Public key (npub)
22
+
23
+ ### Encoding
24
+
25
+ ```ruby
26
+ npub = Nostr::Bech32.npub_encode('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e')
27
+ npub # => 'npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'
28
+ ```
29
+
30
+ ### Decoding
31
+
32
+ ```ruby
33
+ type, public_key = Nostr::Bech32.decode('npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg')
34
+ type # => 'npub'
35
+ public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
36
+ ```
37
+
38
+ ## Private key (nsec)
39
+
40
+ ### Encoding
41
+
42
+ ```ruby
43
+ nsec = Nostr::Bech32.nsec_encode('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa')
44
+ nsec # => 'nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'
45
+ ```
46
+
47
+ ### Decoding
48
+
49
+ ```ruby
50
+ type, private_key = Nostr::Bech32.decode('nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5')
51
+ type # => 'npub'
52
+ private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
53
+ ```
54
+
55
+ ## Relay (nrelay)
56
+
57
+ ### Encoding
58
+
59
+ ```ruby
60
+ nrelay = Nostr::Bech32.nrelay_encode('wss://relay.damus.io')
61
+ nrelay # => 'nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x'
62
+ ```
63
+
64
+ ### Decoding
65
+
66
+ ```ruby
67
+ type, data = Nostr::Bech32.decode('nrelay1qq28wumn8ghj7un9d3shjtnyv9kh2uewd9hsc5zt2x')
68
+
69
+ type # => 'nrelay'
70
+ data.entries.first.label # => 'relay'
71
+ data.entries.first.value # => 'wss://relay.damus.io'
72
+ ```
73
+
74
+ ## Event (nevent)
75
+
76
+ ### Encoding
77
+
78
+ ```ruby{8-12}
79
+ user = Nostr::User.new(keypair: keypair)
80
+ text_note_event = user.create_event(
81
+ kind: Nostr::EventKind::TEXT_NOTE,
82
+ created_at: 1700467997,
83
+ content: 'Your feedback is appreciated, now pay $8'
84
+ )
85
+
86
+ nevent = Nostr::Bech32.nevent_encode(
87
+ id: text_note_event.id,
88
+ relays: ['wss://relay.damus.io', 'wss://nos.lol'],
89
+ kind: Nostr::EventKind::TEXT_NOTE,
90
+ )
91
+
92
+ nevent # => 'nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3'
93
+ ```
94
+
95
+ ### Decoding
96
+
97
+ ```ruby
98
+ type, event = Nostr::Bech32.decode('nevent1qgsqlkuslr3rf56qpmd0m5ndfyl39m7q6l0zcmuly8ue0praxwkjagcpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqs03k8v3')
99
+
100
+ type # => 'nevent'
101
+ event.entries[0].label # => 'author'
102
+ event.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
103
+ event.entries[1].relay # => 'relay'
104
+ event.entries[1].value # => 'wss://relay.damus.io'
105
+ event.entries[2].label # => 'relay'
106
+ event.entries[2].value # => 'wss://nos.lol'
107
+ event.entries[3].label # => 'kind'
108
+ event.entries[3].value # => 1
109
+ ```
110
+
111
+ ## Address (naddr)
112
+
113
+ ### Encoding
114
+
115
+ ```ruby
116
+ naddr = Nostr::Bech32.naddr_encode(
117
+ pubkey: keypair.public_key,
118
+ relays: ['wss://relay.damus.io', 'wss://nos.lol'],
119
+ kind: Nostr::EventKind::TEXT_NOTE,
120
+ identifier: 'damus',
121
+ )
122
+
123
+ naddr # => 'naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p'
124
+ ```
125
+
126
+ ### Decoding
127
+
128
+ ```ruby
129
+ type, addr = Nostr::Bech32.decode('naddr1qgs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dspsgqqqqqqsqptyv9kh2uc3qfs2p')
130
+
131
+ type # => 'naddr'
132
+ addr.entries[0].label # => 'author'
133
+ addr.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
134
+ addr.entries[1].label # => 'relay'
135
+ addr.entries[1].value # => 'wss://relay.damus.io'
136
+ addr.entries[2].label # => 'relay'
137
+ addr.entries[2].value # => 'wss://nos.lol'
138
+ addr.entries[3].label # => 'kind'
139
+ addr.entries[3].value # => 1
140
+ addr.entries[4].label # => 'identifier'
141
+ addr.entries[4].value # => 'damus'
142
+ ```
143
+
144
+ ## Profile (nprofile)
145
+
146
+ ### Encoding
147
+ ```ruby
148
+ relay_urls = %w[wss://relay.damus.io wss://nos.lol]
149
+ nprofile = Nostr::Bech32.nprofile_encode(pubkey: keypair.public_key, relays: relay_urls)
150
+
151
+ nprofile # => nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5
152
+ ```
153
+
154
+ ### Decoding
155
+
156
+ ```ruby
157
+ type, profile = Nostr::Bech32.decode('nprofile1qqs8ul5ug253hlh3n75jne0a5xmjur4urfxpzst88cnegg6ds6ka7nspz3mhxue69uhhyetvv9ujuerpd46hxtnfduqs6amnwvaz7tmwdaejumr0dsxe58m5')
158
+
159
+ type # => 'nprofile'
160
+ profile.entries[0].label # => 'pubkey'
161
+ profile.entries[0].value # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
162
+ profile.entries[1].label # => 'relay'
163
+ profile.entries[1].value # => 'wss://relay.damus.io'
164
+ profile.entries[2].label # => 'relay'
165
+ profile.entries[2].value # => 'wss://nos.lol'
166
+ ```
167
+
168
+ ## Other simple types (note)
169
+
170
+ ### Encoding
171
+
172
+ ```ruby{8-9}
173
+ user = Nostr::User.new(keypair: keypair)
174
+ text_note_event = user.create_event(
175
+ kind: Nostr::EventKind::TEXT_NOTE,
176
+ created_at: 1700467997,
177
+ content: 'Your feedback is appreciated, now pay $8'
178
+ )
179
+
180
+ note = Nostr::Bech32.encode(hrp: 'note', data: text_note_event.id)
181
+ note # => 'note10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qnx3ujq'
182
+ ```
183
+
184
+ ### Decoding
185
+
186
+ ```ruby
187
+ type, note = Nostr::Bech32.decode('note1pldep78zxnf5qrk6lhfx6jflzthup47793he7g0ej7z86vad963s42v0rr')
188
+ type # => 'note'
189
+ note # => '0fdb90f8e234d3400edafdd26d493f12efc0d7de2c6f9f21f997847d33ad2ea3'
190
+ ```
@@ -0,0 +1,108 @@
1
+ # Client
2
+
3
+ Clients establish a WebSocket connection to [relays](../relays/connecting-to-a-relay). Through this connection, clients
4
+ communicate and subscribe to a range of [Nostr events](../events) based on specified subscription filters. These filters
5
+ define the Nostr events a client wishes to receive updates about.
6
+
7
+ ::: info
8
+ Clients do not need to sign up or create an account to use Nostr. Upon connecting to a relay, a client provides
9
+ its subscription filters. The relay then streams events that match these filters to the client for the duration of the
10
+ connection.
11
+ :::
12
+
13
+ ## WebSocket events
14
+
15
+ Communication between clients and relays happen via WebSockets. The client will emit WebSocket events when the
16
+ connection is __opened__, __closed__, when a __message__ is received or when there's an __error__.
17
+
18
+ ::: info
19
+ WebSocket events are not [Nostr events](https://nostr.com/the-protocol/events). They are events emitted by the
20
+ WebSocket connection. The WebSocket `:message` event, however, contains a Nostr event in its payload.
21
+ :::
22
+
23
+ ### connect
24
+
25
+ The `:connect` event is fired when a connection with a WebSocket is opened. You must call `Nostr::Client#connect` first.
26
+
27
+ ```ruby
28
+ client = Nostr::Client.new
29
+ relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
30
+
31
+ client.on :connect do
32
+ # When this block executes, you're connected to the relay
33
+ end
34
+
35
+ # Connect to a relay asynchronously
36
+ client.connect(relay)
37
+ ```
38
+
39
+ Once the connection is open, you can send events to the relay, manage subscriptions, etc.
40
+
41
+ ::: tip
42
+ Define the connection event handler before calling
43
+ [`Nostr::Client#connect`](https://www.rubydoc.info/gems/nostr/Nostr/Client#connect-instance_method). Otherwise,
44
+ you may miss the event.
45
+ :::
46
+
47
+ ### error
48
+
49
+ The `:error` event is fired when a connection with a WebSocket has been closed because of an error.
50
+
51
+ ```ruby
52
+ client.on :error do |error_message|
53
+ puts error_message
54
+ end
55
+
56
+ # > Network error: wss://rsslay.fiatjaf.com: Unable to verify the
57
+ # server certificate for 'rsslay.fiatjaf.com'
58
+ ```
59
+
60
+ ### message
61
+
62
+ The `:message` event is fired when data is received through a WebSocket.
63
+
64
+ ```ruby
65
+ client.on :message do |message|
66
+ puts message
67
+ end
68
+ ```
69
+
70
+ The message will be one of these 4 types, which must also be JSON arrays, according to the following patterns:
71
+ - `["EVENT", <subscription_id>, <event JSON>]`
72
+ - `["OK", <event_id>, <true|false>, <message>]`
73
+ - `["EOSE", <subscription_id>]`
74
+ - `["NOTICE", <message>]`
75
+
76
+ ::: details Click me to see how a WebSocket message looks like
77
+ ```ruby
78
+ [
79
+ "EVENT",
80
+ "d34107357089bfc9882146d3bfab0386",
81
+ {
82
+ "content": "",
83
+ "created_at": 1676456512,
84
+ "id": "18f63550da74454c5df7caa2a349edc5b2a6175ea4c5367fa4b4212781e5b310",
85
+ "kind": 3,
86
+ "pubkey": "117a121fa41dc2caa0b3d6c5b9f42f90d114f1301d39f9ee96b646ebfee75e36",
87
+ "sig": "d171420bd62cf981e8f86f2dd8f8f86737ea2bbe2d98da88db092991d125535860d982139a3c4be39886188613a9912ef380be017686a0a8b74231dc6e0b03cb",
88
+ "tags":[
89
+ ["p", "1cc821cc2d47191b15fcfc0f73afed39a86ac6fb34fbfa7993ee3e0f0186ef7c"]
90
+ ]
91
+ }
92
+ ]
93
+ ```
94
+ :::
95
+
96
+ ### close
97
+
98
+ The `:close` event is fired when a connection with a WebSocket is closed.
99
+
100
+ ```ruby
101
+ client.on :close do |code, reason|
102
+ puts "Error: #{code} - #{reason}"
103
+ end
104
+ ```
105
+
106
+ ::: tip
107
+ This handler is useful to attempt to reconnect to the relay.
108
+ :::
data/docs/core/keys.md ADDED
@@ -0,0 +1,136 @@
1
+ # Keys
2
+
3
+ To [sign events](#signing-an-event), you need a **private key**. To verify signatures, you need a **public key**. The combination of a
4
+ private and a public key is called a **keypair**.
5
+
6
+ Both public and private keys are 64-character hexadecimal strings. They can be represented in bech32 format,
7
+ which is a human-readable format that starts with `nsec` for private keys and `npub` for public keys.
8
+
9
+ There are a few ways to generate a keypair.
10
+
11
+ ## a) Generating a keypair
12
+
13
+ If you don't have any keys, you can generate a keypair using the
14
+ [`Nostr::Keygen`](https://www.rubydoc.info/gems/nostr/Nostr/Keygen) class:
15
+
16
+ ```ruby
17
+ keygen = Nostr::Keygen.new
18
+ keypair = keygen.generate_key_pair
19
+
20
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
21
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
22
+ ```
23
+
24
+ ## b) Generating a private key and a public key
25
+
26
+ Alternatively, if you have already generated a private key, you can extract the corresponding public key by calling
27
+ `Keygen#extract_public_key`:
28
+
29
+ ```ruby
30
+ keygen = Nostr::Keygen.new
31
+
32
+ private_key = keygen.generate_private_key
33
+ public_key = keygen.extract_public_key(private_key)
34
+
35
+ private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
36
+ public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
37
+ ```
38
+
39
+ ## c) Using existing hexadecimal keys
40
+
41
+ If you already have a private key and a public key in hexadecimal format, you can create a keypair using the
42
+ `Nostr::KeyPair` class:
43
+
44
+ ```ruby
45
+ keypair = Nostr::KeyPair.new(
46
+ private_key: Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
47
+ public_key: Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
48
+ )
49
+
50
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
51
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
52
+ ```
53
+
54
+ ### d) Use existing bech32 keys
55
+
56
+ If you already have a private key and a public key in bech32 format, you can create a keypair using the
57
+ `Nostr::KeyPair` class:
58
+
59
+ ```ruby
60
+ keypair = Nostr::KeyPair.new(
61
+ private_key: Nostr::PrivateKey.from_bech32('nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5'),
62
+ public_key: Nostr::PublicKey.from_bech32('npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg'),
63
+ )
64
+
65
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
66
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
67
+ ```
68
+
69
+ ## e) Using an existing hexadecimal private key
70
+
71
+ If you already have a private key in hexadecimal format, you can create a keypair using the method
72
+ [`Nostr::Keygen#get_key_pair_from_private_key`](https://www.rubydoc.info/gems/nostr/Nostr/Keygen#get_key_pair_from_private_key-instance_method):
73
+
74
+ ```ruby
75
+ private_key = Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa')
76
+
77
+ keygen= Nostr::Keygen.new
78
+ keypair = keygen.get_key_pair_from_private_key(private_key)
79
+
80
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
81
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
82
+ ```
83
+
84
+ ## f) Using an existing bech32 private key
85
+
86
+ If you already have a private key in bech32 format, you can create a keypair using the methods
87
+ [`Nostr::PrivateKey.from_bech32`](https://www.rubydoc.info/gems/nostr/Nostr/PrivateKey.from_bech32-class_method) and
88
+ [`Nostr::Keygen#get_key_pair_from_private_key`](https://www.rubydoc.info/gems/nostr/Nostr/Keygen#get_key_pair_from_private_key-instance_method):
89
+
90
+ ```ruby
91
+ private_key = Nostr::PrivateKey.from_bech32('nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5')
92
+
93
+ keygen= Nostr::Keygen.new
94
+ keypair = keygen.get_key_pair_from_private_key(private_key)
95
+
96
+ keypair.private_key # => '67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'
97
+ keypair.public_key # => '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'
98
+ ```
99
+
100
+ ## Signing an event
101
+
102
+ KeyPairs are used to sign [events](../events). To create a signed event, you need to instantiate a
103
+ [`Nostr::User`](https://www.rubydoc.info/gems/nostr/Nostr/User) with a keypair:
104
+
105
+ ```ruby{8,11-14}
106
+ # Use an existing keypair
107
+ keypair = Nostr::KeyPair.new(
108
+ private_key: Nostr::PrivateKey.new('67dea2ed018072d675f5415ecfaed7d2597555e202d85b3d65ea4e58d2d92ffa'),
109
+ public_key: Nostr::PublicKey.new('7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e'),
110
+ )
111
+
112
+ # Add the keypair to a user
113
+ user = Nostr::User.new(keypair: keypair)
114
+
115
+ # Create signed events
116
+ text_note = user.create_event(
117
+ kind: Nostr::EventKind::TEXT_NOTE,
118
+ content: 'Your feedback is appreciated, now pay $8'
119
+ )
120
+ ```
121
+
122
+ ::: details Click me to view the text_note
123
+
124
+ ```ruby
125
+ # text_note.to_h
126
+ {
127
+ id: '030fbc71151379e5b58e7428ed6e7f2884e5dfc9087fd64d1dc4cc677f5097c8',
128
+ pubkey: '7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e', # from the keypair
129
+ created_at: 1700119819,
130
+ kind: 1, # Nostr::EventKind::TEXT_NOTE,
131
+ tags: [],
132
+ content: 'Your feedback is appreciated, now pay $8',
133
+ sig: '586877896ef6f7d54fa4dd2ade04e3fdc4dfcd6166dd0df696b3c3c768868c0b690338f5baed6ab4fc717785333cb487363384de9fb0f740ac4775522cb4acb3' # signed with the private key from the keypair
134
+ }
135
+ ```
136
+ :::
data/docs/core/user.md ADDED
@@ -0,0 +1,43 @@
1
+ # User
2
+
3
+ The class [`Nostr::User`](https://www.rubydoc.info/gems/nostr/Nostr/User) is an abstraction to facilitate the creation
4
+ of signed events. It is not required to use it to create events, but it is recommended.
5
+
6
+ Here's an example of how to create a signed event without the class `Nostr::User`:
7
+
8
+ ```ruby
9
+ event = Nostr::Event.new(
10
+ pubkey: keypair.public_key,
11
+ kind: Nostr::EventKind::TEXT_NOTE,
12
+ tags: [],
13
+ content: 'Your feedback is appreciated, now pay $8',
14
+ )
15
+ event.sign(keypair.private_key)
16
+ ```
17
+
18
+ ::: details Click me to view the event
19
+
20
+ ```ruby
21
+ # event.to_h
22
+ {
23
+ id: '5feb10973dbcf5f210cfc1f0aa338fee62bed6a29696a67957713599b9baf0eb',
24
+ pubkey: 'b9b9821074d1b60b8fb4a3983632af3ef9669f55b20d515bf982cda5c439ad61',
25
+ created_at: 1699847447,
26
+ kind: 1, # Nostr::EventKind::TEXT_NOTE,
27
+ tags: [],
28
+ content: 'Your feedback is appreciated, now pay $8',
29
+ sig: 'e30f2f08331f224e41a4099d16aefc780bf9f2d1191b71777e1e1789e6b51fdf7bb956f25d4ea9a152d1c66717a9d68c081ce6c89c298c3c5e794914013381ab'
30
+ }
31
+ ```
32
+ :::
33
+
34
+ And here's how to create it with the class `Nostr::User`:
35
+
36
+ ```ruby
37
+ user = Nostr::User.new(keypair: keypair)
38
+
39
+ event = user.create_event(
40
+ kind: Nostr::EventKind::TEXT_NOTE,
41
+ content: 'Your feedback is appreciated, now pay $8'
42
+ )
43
+ ```
@@ -0,0 +1,29 @@
1
+ # Contact List
2
+
3
+ ## Creating/updating your contact list
4
+
5
+ Every new contact list that gets published overwrites the past ones, so it should contain all entries.
6
+
7
+ ```ruby
8
+ # Creating a contact list event with 2 contacts
9
+ update_contacts_event = user.create_event(
10
+ kind: Nostr::EventKind::CONTACT_LIST,
11
+ tags: [
12
+ [
13
+ "p", # mandatory
14
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", # public key of the user to add to the contacts
15
+ "wss://alicerelay.com/", # can be an empty string or can be omitted
16
+ "alice" # can be an empty string or can be omitted
17
+ ],
18
+ [
19
+ "p", # mandatory
20
+ "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", # public key of the user to add to the contacts
21
+ "wss://bobrelay.com/nostr", # can be an empty string or can be omitted
22
+ "bob" # can be an empty string or can be omitted
23
+ ],
24
+ ],
25
+ )
26
+
27
+ # Send it to the Relay
28
+ client.publish(update_contacts_event)
29
+ ```
@@ -0,0 +1,28 @@
1
+ # Encrypted Direct Message
2
+
3
+ ## Sending an encrypted direct message
4
+
5
+ ```ruby
6
+ sender_private_key = '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
7
+
8
+ encrypted_direct_message = Nostr::Events::EncryptedDirectMessage.new(
9
+ sender_private_key: sender_private_key,
10
+ recipient_public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
11
+ plain_text: 'Your feedback is appreciated, now pay $8',
12
+ previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460' # optional
13
+ )
14
+
15
+ encrypted_direct_message.sign(sender_private_key)
16
+
17
+ # #<Nostr::Events::EncryptedDirectMessage:0x0000000104c9fa68
18
+ # @content="mjIFNo1sSP3KROE6QqhWnPSGAZRCuK7Np9X+88HSVSwwtFyiZ35msmEVoFgRpKx4?iv=YckChfS2oWCGpMt1uQ4GbQ==",
19
+ # @created_at=1676456512,
20
+ # @id="daac98826d5eb29f7c013b6160986c4baf4fe6d4b995df67c1b480fab1839a9b",
21
+ # @kind=4,
22
+ # @pubkey="8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca",
23
+ # @sig="028bb5f5bab0396e2065000c84a4bcce99e68b1a79bb1b91a84311546f49c5b67570b48d4a328a1827e7a8419d74451347d4f55011a196e71edab31aa3d6bdac",
24
+ # @tags=[["p", "6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0"], ["e", "ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460"]]>
25
+
26
+ # Send it to the Relay
27
+ client.publish(encrypted_direct_message)
28
+ ```
@@ -0,0 +1,32 @@
1
+ # Recommend Server
2
+
3
+ The `Recommend Server` event, has a set of tags with the following structure `['e', <event-id>, <relay-url>, <marker>]`
4
+
5
+ Where:
6
+
7
+ - `<event-id>` is the id of the event being referenced.
8
+ - `<relay-url>` is the URL of a recommended relay associated with the reference. Clients SHOULD add a valid `<relay-URL>`
9
+ field, but may instead leave it as `''`.
10
+ - `<marker>` is optional and if present is one of `'reply'`, `'root'`, or `'mention'`.
11
+ Those marked with `'reply'` denote the id of the reply event being responded to. Those marked with `'root'` denote the
12
+ root id of the reply thread being responded to. For top level replies (those replying directly to the root event),
13
+ only the `'root'` marker should be used. Those marked with `'mention'` denote a quoted or reposted event id.
14
+
15
+ A direct reply to the root of a thread should have a single marked `'e'` tag of type `'root'`.
16
+
17
+ ## Recommending a server
18
+
19
+ ```ruby
20
+ recommend_server_event = user.create_event(
21
+ kind: Nostr::EventKind::RECOMMEND_SERVER,
22
+ tags: [
23
+ [
24
+ 'e',
25
+ '461544014d87c9eaf3e76e021240007dff2c7afb356319f99c741b45749bf82f',
26
+ 'wss://relay.damus.io'
27
+ ],
28
+ ]
29
+ )
30
+
31
+ client.publish(recommend_server_event)
32
+ ```
@@ -0,0 +1,20 @@
1
+ # Set Metadata
2
+
3
+ In the `Metadata` event, the `content` is set to a stringified JSON object
4
+ `{name: <username>, about: <string>, picture: <url, string>}` describing the [user](../core/user) who created the event. A relay may
5
+ delete older events once it gets a new one for the same pubkey.
6
+
7
+ ## Setting the user's metadata
8
+
9
+ ```ruby
10
+ metadata_event = user.create_event(
11
+ kind: Nostr::EventKind::SET_METADATA,
12
+ content: {
13
+ name: 'Wilson Silva',
14
+ about: 'Used to make hydrochloric acid bombs in high school.',
15
+ picture: 'https://thispersondoesnotexist.com/'
16
+ }
17
+ )
18
+
19
+ client.publish(metadata_event)
20
+ ```
@@ -0,0 +1,15 @@
1
+ # Text Note
2
+
3
+ In the `Text Note` event, the `content` is set to the plaintext content of a note (anything the user wants to say).
4
+ Content that must be parsed, such as Markdown and HTML, should not be used.
5
+
6
+ ## Sending a text note event
7
+
8
+ ```ruby
9
+ text_note_event = user.create_event(
10
+ kind: Nostr::EventKind::TEXT_NOTE,
11
+ content: 'Your feedback is appreciated, now pay $8'
12
+ )
13
+
14
+ client.publish(text_note_event)
15
+ ```
data/docs/events.md ADDED
@@ -0,0 +1,11 @@
1
+ # Events
2
+
3
+ ## Event Kinds
4
+
5
+ | kind | description | NIP |
6
+ | ------------- |----------------------------------------------------------------| -------------------------------------------------------------- |
7
+ | `0` | [Metadata](./events/set-metadata) | [1](https://github.com/nostr-protocol/nips/blob/master/01.md) |
8
+ | `1` | [Short Text Note](./events/text-note) | [1](https://github.com/nostr-protocol/nips/blob/master/01.md) |
9
+ | `2` | [Recommend Relay](./events/recommend-server) | |
10
+ | `3` | [Contacts](./events/contact-list) | [2](https://github.com/nostr-protocol/nips/blob/master/02.md) |
11
+ | `4` | [Encrypted Direct Messages](./events/encrypted-direct-message) | [4](https://github.com/nostr-protocol/nips/blob/master/04.md) |