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,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) |