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.
- checksums.yaml +4 -4
- data/.editorconfig +1 -1
- data/.rubocop.yml +23 -0
- data/.tool-versions +2 -1
- data/CHANGELOG.md +36 -1
- data/README.md +92 -228
- 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 +11 -7
- 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 +3 -4
- data/lib/nostr/events/encrypted_direct_message.rb +4 -3
- 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 -8
- data/lib/nostr/version.rb +1 -1
- data/lib/nostr.rb +6 -0
- data/nostr.gemspec +9 -9
- data/sig/nostr/bech32.rbs +14 -0
- data/sig/nostr/client.rbs +5 -5
- data/sig/nostr/crypto.rbs +5 -5
- 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 +4 -4
- data/sig/nostr/events/encrypted_direct_message.rbs +2 -2
- 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 -8
- 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 +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
|
+
```
|
data/docs/core/client.md
ADDED
@@ -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) |
|